From e364bd8e529ab12192c3f56366336d199d6bce62 Mon Sep 17 00:00:00 2001 From: Logi E Date: Fri, 3 Jun 2022 09:55:27 +0000 Subject: [PATCH 001/371] moved query modules to disabled --- queries/{ => disabled}/arithmetic.py | 0 queries/{ => disabled}/builtin.py | 0 queries/{ => disabled}/bus.py | 0 queries/{ => disabled}/counting.py | 0 queries/{ => disabled}/currency.py | 0 queries/{ => disabled}/date.py | 0 queries/{ => disabled}/dictionary.py | 0 queries/{ => disabled}/distance.py | 0 queries/{ => disabled}/flights.py | 0 queries/{ => disabled}/geography.py | 0 queries/{ => disabled}/ja.py | 0 queries/{ => disabled}/news.py | 0 queries/{ => disabled}/opinion.py | 0 queries/{ => disabled}/petrol.py | 0 queries/{ => disabled}/pic.py | 0 queries/{ => disabled}/places.py | 0 queries/{ => disabled}/play.py | 0 queries/{ => disabled}/rand.py | 0 queries/{ => disabled}/repeat.py | 0 queries/{ => disabled}/schedules.py | 0 queries/{ => disabled}/special.py | 0 queries/{ => disabled}/stats.py | 0 queries/{ => disabled}/sunpos.py | 0 queries/{ => disabled}/tel.py | 0 queries/{ => disabled}/unit.py | 0 queries/{ => disabled}/userinfo.py | 0 queries/{ => disabled}/userloc.py | 0 queries/{ => disabled}/weather.py | 0 queries/{ => disabled}/whatis.py | 0 queries/{ => disabled}/wiki.py | 0 queries/{ => disabled}/words.py | 0 queries/{ => disabled}/yulelads.py | 0 queries/test.py | 51 ---------------------------- 33 files changed, 51 deletions(-) rename queries/{ => disabled}/arithmetic.py (100%) rename queries/{ => disabled}/builtin.py (100%) rename queries/{ => disabled}/bus.py (100%) rename queries/{ => disabled}/counting.py (100%) rename queries/{ => disabled}/currency.py (100%) rename queries/{ => disabled}/date.py (100%) rename queries/{ => disabled}/dictionary.py (100%) rename queries/{ => disabled}/distance.py (100%) rename queries/{ => disabled}/flights.py (100%) rename queries/{ => disabled}/geography.py (100%) rename queries/{ => disabled}/ja.py (100%) rename queries/{ => disabled}/news.py (100%) rename queries/{ => disabled}/opinion.py (100%) rename queries/{ => disabled}/petrol.py (100%) rename queries/{ => disabled}/pic.py (100%) rename queries/{ => disabled}/places.py (100%) rename queries/{ => disabled}/play.py (100%) rename queries/{ => disabled}/rand.py (100%) rename queries/{ => disabled}/repeat.py (100%) rename queries/{ => disabled}/schedules.py (100%) rename queries/{ => disabled}/special.py (100%) rename queries/{ => disabled}/stats.py (100%) rename queries/{ => disabled}/sunpos.py (100%) rename queries/{ => disabled}/tel.py (100%) rename queries/{ => disabled}/unit.py (100%) rename queries/{ => disabled}/userinfo.py (100%) rename queries/{ => disabled}/userloc.py (100%) rename queries/{ => disabled}/weather.py (100%) rename queries/{ => disabled}/whatis.py (100%) rename queries/{ => disabled}/wiki.py (100%) rename queries/{ => disabled}/words.py (100%) rename queries/{ => disabled}/yulelads.py (100%) delete mode 100755 queries/test.py diff --git a/queries/arithmetic.py b/queries/disabled/arithmetic.py similarity index 100% rename from queries/arithmetic.py rename to queries/disabled/arithmetic.py diff --git a/queries/builtin.py b/queries/disabled/builtin.py similarity index 100% rename from queries/builtin.py rename to queries/disabled/builtin.py diff --git a/queries/bus.py b/queries/disabled/bus.py similarity index 100% rename from queries/bus.py rename to queries/disabled/bus.py diff --git a/queries/counting.py b/queries/disabled/counting.py similarity index 100% rename from queries/counting.py rename to queries/disabled/counting.py diff --git a/queries/currency.py b/queries/disabled/currency.py similarity index 100% rename from queries/currency.py rename to queries/disabled/currency.py diff --git a/queries/date.py b/queries/disabled/date.py similarity index 100% rename from queries/date.py rename to queries/disabled/date.py diff --git a/queries/dictionary.py b/queries/disabled/dictionary.py similarity index 100% rename from queries/dictionary.py rename to queries/disabled/dictionary.py diff --git a/queries/distance.py b/queries/disabled/distance.py similarity index 100% rename from queries/distance.py rename to queries/disabled/distance.py diff --git a/queries/flights.py b/queries/disabled/flights.py similarity index 100% rename from queries/flights.py rename to queries/disabled/flights.py diff --git a/queries/geography.py b/queries/disabled/geography.py similarity index 100% rename from queries/geography.py rename to queries/disabled/geography.py diff --git a/queries/ja.py b/queries/disabled/ja.py similarity index 100% rename from queries/ja.py rename to queries/disabled/ja.py diff --git a/queries/news.py b/queries/disabled/news.py similarity index 100% rename from queries/news.py rename to queries/disabled/news.py diff --git a/queries/opinion.py b/queries/disabled/opinion.py similarity index 100% rename from queries/opinion.py rename to queries/disabled/opinion.py diff --git a/queries/petrol.py b/queries/disabled/petrol.py similarity index 100% rename from queries/petrol.py rename to queries/disabled/petrol.py diff --git a/queries/pic.py b/queries/disabled/pic.py similarity index 100% rename from queries/pic.py rename to queries/disabled/pic.py diff --git a/queries/places.py b/queries/disabled/places.py similarity index 100% rename from queries/places.py rename to queries/disabled/places.py diff --git a/queries/play.py b/queries/disabled/play.py similarity index 100% rename from queries/play.py rename to queries/disabled/play.py diff --git a/queries/rand.py b/queries/disabled/rand.py similarity index 100% rename from queries/rand.py rename to queries/disabled/rand.py diff --git a/queries/repeat.py b/queries/disabled/repeat.py similarity index 100% rename from queries/repeat.py rename to queries/disabled/repeat.py diff --git a/queries/schedules.py b/queries/disabled/schedules.py similarity index 100% rename from queries/schedules.py rename to queries/disabled/schedules.py diff --git a/queries/special.py b/queries/disabled/special.py similarity index 100% rename from queries/special.py rename to queries/disabled/special.py diff --git a/queries/stats.py b/queries/disabled/stats.py similarity index 100% rename from queries/stats.py rename to queries/disabled/stats.py diff --git a/queries/sunpos.py b/queries/disabled/sunpos.py similarity index 100% rename from queries/sunpos.py rename to queries/disabled/sunpos.py diff --git a/queries/tel.py b/queries/disabled/tel.py similarity index 100% rename from queries/tel.py rename to queries/disabled/tel.py diff --git a/queries/unit.py b/queries/disabled/unit.py similarity index 100% rename from queries/unit.py rename to queries/disabled/unit.py diff --git a/queries/userinfo.py b/queries/disabled/userinfo.py similarity index 100% rename from queries/userinfo.py rename to queries/disabled/userinfo.py diff --git a/queries/userloc.py b/queries/disabled/userloc.py similarity index 100% rename from queries/userloc.py rename to queries/disabled/userloc.py diff --git a/queries/weather.py b/queries/disabled/weather.py similarity index 100% rename from queries/weather.py rename to queries/disabled/weather.py diff --git a/queries/whatis.py b/queries/disabled/whatis.py similarity index 100% rename from queries/whatis.py rename to queries/disabled/whatis.py diff --git a/queries/wiki.py b/queries/disabled/wiki.py similarity index 100% rename from queries/wiki.py rename to queries/disabled/wiki.py diff --git a/queries/words.py b/queries/disabled/words.py similarity index 100% rename from queries/words.py rename to queries/disabled/words.py diff --git a/queries/yulelads.py b/queries/disabled/yulelads.py similarity index 100% rename from queries/yulelads.py rename to queries/disabled/yulelads.py diff --git a/queries/test.py b/queries/test.py deleted file mode 100755 index 99082e3f..00000000 --- a/queries/test.py +++ /dev/null @@ -1,51 +0,0 @@ -""" - - Greynir: Natural language processing for Icelandic - - Test query response module - - Copyright (C) 2021 Miðeind ehf. - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see http://www.gnu.org/licenses/. - - - This module handles queries related to testing various response - payload functionality both server and client-side, for example - JS code to be executed, URL to be opened, image to display, etc. - -""" - -from query import Query -from queries import gen_answer - - -_TEST_QTYPE = "Test" - - -def handle_plain_text(q: Query) -> bool: - """Handle a plain text query.""" - ql = q.query_lower.rstrip("?") - - if ql == "keyrðu kóða": - q.set_command("2 + 2") - elif ql == "opnaðu vefsíðu": - q.set_url("https://mideind.is") - elif ql == "sýndu mynd": - q.set_image("https://greynir.is/static/img/GreynirLogoHoriz180x80.png") - else: - return False - - q.set_qtype(_TEST_QTYPE) - q.set_answer(*gen_answer("Skal gert")) - - return True From 75605e24239cf7ab625b1bd9b869ff69f5f5a7fb Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Fri, 3 Jun 2022 10:19:28 +0000 Subject: [PATCH 002/371] iot.py setup --- queries/iot_hue.py | 96 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100755 queries/iot_hue.py diff --git a/queries/iot_hue.py b/queries/iot_hue.py new file mode 100755 index 00000000..2935986f --- /dev/null +++ b/queries/iot_hue.py @@ -0,0 +1,96 @@ +""" + + Greynir: Natural language processing for Icelandic + + Randomness query response module + + Copyright (C) 2022 Miðeind ehf. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see http://www.gnu.org/licenses/. + + This query module handles queries related to the generation + of random numbers, e.g. "Kastaðu tengingi", "Nefndu tölu milli 5 og 10", etc. + +""" + +import logging +import random + +from query import Query, QueryStateDict, AnswerTuple +from queries import gen_answer +from tree import Result, Node + + +_IOT_QTYPE = "IOT" + +TOPIC_LEMMAS = ["ljós", "kveikja"] + + +def help_text(lemma: str) -> str: + """Help text to return when query.py is unable to parse a query but + one of the above lemmas is found in it""" + return "Ég skil þig ef þú segir til dæmis: {0}.".format( + random.choice( + ( + "Kastaðu teningi", + "Kastaðu tíu hliða teningi", + "Fiskur eða skjaldarmerki", + "Kastaðu teningi", + "Kastaðu peningi", + "Veldu tölu á milli sjö og þrettán", + ) + ) + ) + + +# This module wants to handle parse trees for queries +HANDLE_TREE = True + +# The grammar nonterminals this module wants to handle +QUERY_NONTERMINALS = {"QIOT"} + +# The context-free grammar for the queries recognized by this plug-in module +GRAMMAR = """ + +Query → + QIOT + +QIOT → QIOTQuery '?'? + +QIOTQuery → + "kveiktu" "ljósin" + +""" + + +def QIOTQuery(node: Node, params: QueryStateDict, result: Result) -> None: + result.qtype = _IOT_QTYPE + + +def sentence(state: QueryStateDict, result: Result) -> None: + """Called when sentence processing is complete""" + q: Query = state["query"] + if "qtype" not in result: + q.set_error("E_QUERY_NOT_UNDERSTOOD") + return + + # Successfully matched a query type + q.set_qtype(result.qtype) + + try: + #kalla í javascripts stuff + q.set_answer(*gen_answer("ég var að kveikja ljósin!")) + except Exception as e: + logging.warning("Exception while processing random query: {0}".format(e)) + q.set_error("E_EXCEPTION: {0}".format(e)) + raise \ No newline at end of file From c3428f9a071fb311adcea49d128369ffa12f1c0f Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Fri, 3 Jun 2022 13:43:04 +0000 Subject: [PATCH 003/371] Added queries --- queries/iot_hue.py | 163 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100755 queries/iot_hue.py diff --git a/queries/iot_hue.py b/queries/iot_hue.py new file mode 100755 index 00000000..6fe26786 --- /dev/null +++ b/queries/iot_hue.py @@ -0,0 +1,163 @@ +""" + + Greynir: Natural language processing for Icelandic + + Randomness query response module + + Copyright (C) 2022 Miðeind ehf. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see http://www.gnu.org/licenses/. + + This query module handles queries related to the generation + of random numbers, e.g. "Kastaðu tengingi", "Nefndu tölu milli 5 og 10", etc. + +""" + +import logging +import random + +from query import Query, QueryStateDict, AnswerTuple +from queries import gen_answer +from tree import Result, Node + + +_IoT_QTYPE = "IoT" + +TOPIC_LEMMAS = ["ljós", "kveikja"] + + +def help_text(lemma: str) -> str: + """Help text to return when query.py is unable to parse a query but + one of the above lemmas is found in it""" + return "Ég skil þig ef þú segir til dæmis: {0}.".format( + random.choice( + ( + "Kastaðu teningi", + "Kastaðu tíu hliða teningi", + "Fiskur eða skjaldarmerki", + "Kastaðu teningi", + "Kastaðu peningi", + "Veldu tölu á milli sjö og þrettán", + ) + ) + ) + + +_COLORS = { + "gulur": [], + "rauður": [], + "grænn": [], + "blár": [], + "hvítur": [], + "fjólublár": [], + "brúnn": [], + "bleikur": [], + "appelsínugulur": [], + "rauður": [], +} + + +# This module wants to handle parse trees for queries +HANDLE_TREE = True + +# The grammar nonterminals this module wants to handle +QUERY_NONTERMINALS = {"QIoT"} + +# The context-free grammar for the queries recognized by this plug-in module +GRAMMAR = f""" + +Query → + QIoT + +QIoT → QIoTQuery '?'? + +QIoTQuery → + QIoTTurnOn | QIoTTurnOff | QIoTChangeColor | QIoTIncreaseBrightness #| QIoTDecreaseBrightness + +QIoTTurnOn -> + "kveiktu" QIoTLightPhrase + | "kveiktu" "á" QIoTLightPhrase + +QIoTTurnOff -> + "slökktu" QIoTLightPhrase + | "slökktu" "á" QIoTLightPhrase + +QIoTChangeColor -> + "gerðu" QIoTLightPhrase QIoTColor + | "gerðu" QIoTLightPhrase QIoTColor QIoTGroupNamePhrase? + +QIoTIncreaseBrightness -> + QIoTIncrease QIoTBrightness QIoTLightPhrase? + | "gerðu" QIoTLightPhrase "bjartara" + +# QIoTDecreaseBrightness -> + +QIoTIncrease -> + "hækkaðu" + +QIoTBrightness -> + "birtu" | "birtustig" | "birtuna" | "birtustigið" + +QIoTLightPhrase -> + "á"? QIoTLight QIoTGroupNamePhrase? + +QIoTLight -> + "ljósið" | "ljósinu" | "ljósin" | "ljósunum" + +QIoTColor -> + {" | ".join(f"'{color}:lo'" for color in _COLORS.keys())} + +QIoTGroupNamePhrase -> + "í"? QIoTGroupName? + +QIoTGroupName -> + Nl + +""" + + +def QIoTQuery(node: Node, params: QueryStateDict, result: Result) -> None: + result.qtype = _IoT_QTYPE + + +def QIoTTurnOff(node: Node, params: QueryStateDict, result: Result) -> None: + result.action = "turnoff" + + +def QIoTTurnOn(node: Node, params: QueryStateDict, result: Result) -> None: + result.action = "turnon" + + +def QIoTGroupName(node: Node, params: QueryStateDict, result: Result) -> None: + result["group_name"] = result._indefinite + + +def sentence(state: QueryStateDict, result: Result) -> None: + """Called when sentence processing is complete""" + q: Query = state["query"] + if "qtype" not in result: + q.set_error("E_QUERY_NOT_UNDERSTOOD") + return + + # Successfully matched a query type + q.set_qtype(result.qtype) + + try: + # kalla í javascripts stuff + group_name = result.get("group_name", "") + print("GROUP NAME:", group_name) + q.set_answer(*gen_answer("ég var að kveikja ljósin! " + group_name)) + except Exception as e: + logging.warning("Exception while processing random query: {0}".format(e)) + q.set_error("E_EXCEPTION: {0}".format(e)) + raise From 5ec0f053b161450cc301488a36958509771211cd Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Tue, 7 Jun 2022 11:59:36 +0000 Subject: [PATCH 004/371] Moved IoTEmbla folder into querues/js Moved IoTEmbla folder into querues/js --- queries/js/IoT_Embla/.gitignore | 1 + queries/js/IoT_Embla/Philips_Hue/connect.js | 0 queries/js/IoT_Embla/Philips_Hue/fuse_test.js | 61 +++ queries/js/IoT_Embla/Philips_Hue/groups.js | 0 queries/js/IoT_Embla/Philips_Hue/hub.js | 10 + queries/js/IoT_Embla/Philips_Hue/lights.js | 71 ++++ queries/js/IoT_Embla/Philips_Hue/scenes.js | 4 + .../js/IoT_Embla/Philips_Hue/set_lights.js | 80 ++++ .../Philips Hue.postman_collection.json | 385 ++++++++++++++++++ queries/js/IoT_Embla/README.md | 4 + queries/js/IoT_Embla/main.html | 58 +++ queries/js/IoT_Embla/main.js | 56 +++ 12 files changed, 730 insertions(+) create mode 100644 queries/js/IoT_Embla/.gitignore create mode 100644 queries/js/IoT_Embla/Philips_Hue/connect.js create mode 100644 queries/js/IoT_Embla/Philips_Hue/fuse_test.js create mode 100644 queries/js/IoT_Embla/Philips_Hue/groups.js create mode 100644 queries/js/IoT_Embla/Philips_Hue/hub.js create mode 100644 queries/js/IoT_Embla/Philips_Hue/lights.js create mode 100644 queries/js/IoT_Embla/Philips_Hue/scenes.js create mode 100644 queries/js/IoT_Embla/Philips_Hue/set_lights.js create mode 100644 queries/js/IoT_Embla/Postman/Philips Hue.postman_collection.json create mode 100644 queries/js/IoT_Embla/README.md create mode 100644 queries/js/IoT_Embla/main.html create mode 100644 queries/js/IoT_Embla/main.js diff --git a/queries/js/IoT_Embla/.gitignore b/queries/js/IoT_Embla/.gitignore new file mode 100644 index 00000000..e43b0f98 --- /dev/null +++ b/queries/js/IoT_Embla/.gitignore @@ -0,0 +1 @@ +.DS_Store diff --git a/queries/js/IoT_Embla/Philips_Hue/connect.js b/queries/js/IoT_Embla/Philips_Hue/connect.js new file mode 100644 index 00000000..e69de29b diff --git a/queries/js/IoT_Embla/Philips_Hue/fuse_test.js b/queries/js/IoT_Embla/Philips_Hue/fuse_test.js new file mode 100644 index 00000000..dd7aff45 --- /dev/null +++ b/queries/js/IoT_Embla/Philips_Hue/fuse_test.js @@ -0,0 +1,61 @@ +// 1. List of items to search in + +const books = [ + { + title: "Old Man's War", + author: { + firstName: 'John', + lastName: 'Scalzi' + } + }, + { + title: 'The Lock Artist', + author: { + firstName: 'Steve', + lastName: 'Hamilton' + } + } + ] + + const lights = [ + // const LIGHTS_EX = + { test: + { state:{"on":true,"bri":100,"hue":6140,"sat":232,"effect":"none","xy":[0.5503,0.4000],"ct":500,"alert":"select","colormode":"xy","mode":"homeautomation","reachable":true}, + "swupdate":{"state":"noupdates","lastinstall":"2022-05-27T14:23:54"},"type":"Extended color light", + name:"litaljós","modelid":"LCA001","manufacturername":"Signify Netherlands B.V.","productname":"Hue color lamp","capabilities":{"certified":true,"control":{"mindimlevel":200,"maxlumen":800,"colorgamuttype":"C","colorgamut":[[0.6915,0.3083],[0.1700,0.7000],[0.1532,0.0475]],"ct":{"min":153,"max":500}},"streaming":{"renderer":true,"proxy":true}},"config":{"archetype":"sultanbulb","function":"mixed","direction":"omnidirectional","startup":{"mode":"safety","configured":true}},"uniqueid":"00:17:88:01:06:79:c3:94-0b","swversion":"1.93.7","swconfigid":"3C05E7B6","productid":"Philips-LCA001-5-A19ECLv6"}, + "2": + {"state":{"on":true,"bri":2,"ct":454,"alert":"select","colormode":"ct","mode":"homeautomation","reachable":false}, + "swupdate":{"state":"notupdatable","lastinstall":"2020-06-29T12:05:21"},"type":"Color temperature light", + "name":"Ikea pera Uno","modelid":"TRADFRI bulb E27 WS opal 1000lm","manufacturername":"IKEA of Sweden","productname":"Color temperature light","capabilities":{"certified":false,"control":{"ct":{"min":250,"max":454}},"streaming":{"renderer":false,"proxy":false}},"config":{"archetype":"classicbulb","function":"functional","direction":"omnidirectional"},"uniqueid":"cc:cc:cc:ff:fe:02:92:52-01","swversion":"2.0.023"}, + "3": + {"state":{"on":true,"bri":105,"ct":454,"alert":"select","colormode":"ct","mode":"homeautomation","reachable":false}, + "swupdate":{"state":"notupdatable","lastinstall":"2020-07-20T13:03:26"},"type":"Color temperature light", + "name":"lesljós","modelid":"TRADFRI bulb E14 WS opal 400lm","manufacturername":"IKEA of Sweden","productname":"Color temperature light","capabilities":{"certified":false,"control":{"ct":{"min":250,"max":454}},"streaming":{"renderer":false,"proxy":false}},"config":{"archetype":"classicbulb","function":"functional","direction":"omnidirectional"},"uniqueid":"90:fd:9f:ff:fe:93:be:a1-01","swversion":"1.2.217"}} + ] + +// 2. Set up the Fuse instance + const fuse = new Fuse(books, { + keys: ['title'] + }) + + const fuseLights = new Fuse(lights, { + keys: ['test.name'] + }) + + // 3. Now search! + console.log(fuse.search('steve')) +// console.log(fuseLights.search('lita')) + + // Output: + // [ + // { + // item: { + // title: "Old Man's War", + // author: { + // firstName: 'John', + // lastName: 'Scalzi' + // } + // }, + // refIndex: 0 + // } + // ] \ No newline at end of file diff --git a/queries/js/IoT_Embla/Philips_Hue/groups.js b/queries/js/IoT_Embla/Philips_Hue/groups.js new file mode 100644 index 00000000..e69de29b diff --git a/queries/js/IoT_Embla/Philips_Hue/hub.js b/queries/js/IoT_Embla/Philips_Hue/hub.js new file mode 100644 index 00000000..b1413b6c --- /dev/null +++ b/queries/js/IoT_Embla/Philips_Hue/hub.js @@ -0,0 +1,10 @@ +function findHub() { + fetch(`https://discovery.meethue.com`) + .then((resp) => resp.json()) + .then((j) => { + console.log(j); + }) + .catch((err) => { + console.log("an error occurred!"); + }); +} \ No newline at end of file diff --git a/queries/js/IoT_Embla/Philips_Hue/lights.js b/queries/js/IoT_Embla/Philips_Hue/lights.js new file mode 100644 index 00000000..86af5e66 --- /dev/null +++ b/queries/js/IoT_Embla/Philips_Hue/lights.js @@ -0,0 +1,71 @@ + +function changeBrightness() { + var sliderValue = document.getElementById("brightness_slider").value; + console.log(sliderValue); + + fetch(`http://${BRIDGE_IP}/api/${USERNAME}/lights/1/state`, { + method: "PUT", + body: JSON.stringify({ bri: Number(sliderValue)}) + }) + .then((resp) => resp.json()) + .then((j) => { + console.log(j); + }) + .catch((err) => { + console.log("an error occurred!"); + }); +} + +function changeColor() { + xValue = Number(document.getElementById("color_x").value) + yValue = Number(document.getElementById("color_y").value) + console.log(xValue, yValue); + + if(xValue === undefined || yValue === undefined || xValue > 1 || xValue < 0 || yValue > 1 || yValue < 0) { + document.getElementById("color_error").innerHTML = "Please enter a value between 0 and 1."; + } + else { + document.getElementById("color_error").innerHTML = ""; + colorValue = [Number(xValue), Number(yValue)] + console.log(colorValue); + fetch(`http://${BRIDGE_IP}/api/${USERNAME}/lights/1/state`, { + method: "PUT", + body: JSON.stringify({ xy: colorValue }) + }) + .then((resp) => resp.json()) + .then((j) => { + console.log(j); + }) + .catch((err) => { + console.log("an error occurred!"); + }); + } + +} + + +function getAllLights(hub_ip = "192.168.1.68", username = "q2jNarhGHO9izO0xZZXcoww5GYANGi6mZyJYgMdL") { + return fetch(`http://${hub_ip}/api/${username}/lights`) + .then((resp) => resp.json()) +} + +function getAllGroups(hub_ip = "192.168.1.68", username = "q2jNarhGHO9izO0xZZXcoww5GYANGi6mZyJYgMdL") { + return fetch(`http://${hub_ip}/api/${username}/groups`) + .then((resp) => resp.json()) +} + +function getCurrentState(id) { + return fetch(`http://${BRIDGE_IP}/api/${USERNAME}/lights/${id}`) + .then((resp) => resp.json()) +} + + +async function test(){ + console.log(getAllGroups()) + var lights = await getAllLights(); + var groups = await getAllGroups(); + console.log("lights:", lights) + console.log("groups:", groups) +} + + diff --git a/queries/js/IoT_Embla/Philips_Hue/scenes.js b/queries/js/IoT_Embla/Philips_Hue/scenes.js new file mode 100644 index 00000000..44d1e9e8 --- /dev/null +++ b/queries/js/IoT_Embla/Philips_Hue/scenes.js @@ -0,0 +1,4 @@ +function getAllScenes(hub_ip = "192.168.1.68", username = "q2jNarhGHO9izO0xZZXcoww5GYANGi6mZyJYgMdL") { + return fetch(`http://${hub_ip}/api/${username}/scenes`) + .then((resp) => resp.json()) +} \ No newline at end of file diff --git a/queries/js/IoT_Embla/Philips_Hue/set_lights.js b/queries/js/IoT_Embla/Philips_Hue/set_lights.js new file mode 100644 index 00000000..748a9543 --- /dev/null +++ b/queries/js/IoT_Embla/Philips_Hue/set_lights.js @@ -0,0 +1,80 @@ +// "use strict"; +const QUERY_EX = "eldhús"; +const STATE_EX = JSON.stringify({bri_inc: 64}) +// const LIGHTS_EX = test(); +// const LIGHTS_EX = '{"1":{"state":{"on":true,"bri":100,"hue":6140,"sat":232,"effect":"none","xy":[0.5503,0.4000],"ct":500,"alert":"select","colormode":"xy","mode":"homeautomation","reachable":true},"swupdate":{"state":"noupdates","lastinstall":"2022-05-27T14:23:54"},"type":"Extended color light","name":"litaljós","modelid":"LCA001","manufacturername":"Signify Netherlands B.V.","productname":"Hue color lamp","capabilities":{"certified":true,"control":{"mindimlevel":200,"maxlumen":800,"colorgamuttype":"C","colorgamut":[[0.6915,0.3083],[0.1700,0.7000],[0.1532,0.0475]],"ct":{"min":153,"max":500}},"streaming":{"renderer":true,"proxy":true}},"config":{"archetype":"sultanbulb","function":"mixed","direction":"omnidirectional","startup":{"mode":"safety","configured":true}},"uniqueid":"00:17:88:01:06:79:c3:94-0b","swversion":"1.93.7","swconfigid":"3C05E7B6","productid":"Philips-LCA001-5-A19ECLv6"},"2":{"state":{"on":true,"bri":2,"ct":454,"alert":"select","colormode":"ct","mode":"homeautomation","reachable":false},"swupdate":{"state":"notupdatable","lastinstall":"2020-06-29T12:05:21"},"type":"Color temperature light","name":"Ikea pera Uno","modelid":"TRADFRI bulb E27 WS opal 1000lm","manufacturername":"IKEA of Sweden","productname":"Color temperature light","capabilities":{"certified":false,"control":{"ct":{"min":250,"max":454}},"streaming":{"renderer":false,"proxy":false}},"config":{"archetype":"classicbulb","function":"functional","direction":"omnidirectional"},"uniqueid":"cc:cc:cc:ff:fe:02:92:52-01","swversion":"2.0.023"},"3":{"state":{"on":true,"bri":105,"ct":454,"alert":"select","colormode":"ct","mode":"homeautomation","reachable":false},"swupdate":{"state":"notupdatable","lastinstall":"2020-07-20T13:03:26"},"type":"Color temperature light","name":"lesljós","modelid":"TRADFRI bulb E14 WS opal 400lm","manufacturername":"IKEA of Sweden","productname":"Color temperature light","capabilities":{"certified":false,"control":{"ct":{"min":250,"max":454}},"streaming":{"renderer":false,"proxy":false}},"config":{"archetype":"classicbulb","function":"functional","direction":"omnidirectional"},"uniqueid":"90:fd:9f:ff:fe:93:be:a1-01","swversion":"1.2.217"}}' +// const GROUPS_EX = '{"1":{"name":"eldhús","lights":["1"],"sensors":[],"type":"Room","state":{"all_on":true,"any_on":true},"recycle":false,"class":"Living room","action":{"on":true,"bri":100,"hue":6140,"sat":232,"effect":"none","xy":[0.5503,0.4000],"ct":500,"alert":"select","colormode":"xy"}},"2":{"name":"Hús","lights":["3","2","1"],"sensors":[],"type":"Zone","state":{"all_on":true,"any_on":true},"recycle":false,"class":"Downstairs","action":{"on":true,"bri":100,"hue":6140,"sat":232,"effect":"none","xy":[0.5503,0.4000],"ct":500,"alert":"select","colormode":"xy"}},"3":{"name":"skrifstofa","lights":["2","3"],"sensors":[],"type":"Room","state":{"all_on":true,"any_on":true},"recycle":false,"class":"Living room","action":{"on":true,"bri":105,"ct":454,"alert":"select","colormode":"ct"}}}' + + + + +async function getIdObject(query) { + let allLights = await getAllLights(); + let allGroups = await getAllGroups(); + + for (let light in allLights) { + if (allLights[light].name === query) { + return_object = {id: light, type: "light", url: `lights/${light}/state`} + return return_object + } + } + for (let group in allGroups) { + if (allGroups[group].name === query) { + return_object = {id: group, type: "group", url: `groups/${group}/action`} + return return_object + } + } + //vantar e.k. error + return +} + + +async function getSceneID(scene_name){ + let allScenes = await getAllScenes(); + for (let scene in allScenes) { + if (allScenes[scene].name === scene_name) { + console.log("matching scene id: " + scene) + return scene + } + } +} + + +async function setLights(query, state){ + let idObject = await getIdObject(query) + let ID = idObject.id + parsed_state = JSON.parse(state) + + // Check if state includes a scene or a brightness change + if (parsed_state.scene) { + parsed_state.scene = await getSceneID(parsed_state.scene) + state = JSON.stringify(parsed_state) + } + else if (parsed_state.bri_inc) { + console.log(parsed_state.bri_inc) + console.log("New brightness = " + (parsed_state.bri_inc + parsed_state.bri)) + state = JSON.stringify(parsed_state) + } + // Send data to API + let url = idObject.url + fetch(`http://${BRIDGE_IP}/api/${USERNAME}/${url}`, { + method: "PUT", + body: state, + }) + .then((resp) => resp.json()) + .then((j) => { + console.log(j); + }) + .catch((err) => { + console.log("an error occurred!"); + }); +} + + +function setLightsFromHTML() { + let query = document.getElementById("queryInput").value + let stateObject = new Object(); + stateObject.bri_inc = Number(document.getElementById("brightnessInput").value) + stateObject = JSON.stringify(stateObject) + setLights(query, stateObject) +} \ No newline at end of file diff --git a/queries/js/IoT_Embla/Postman/Philips Hue.postman_collection.json b/queries/js/IoT_Embla/Postman/Philips Hue.postman_collection.json new file mode 100644 index 00000000..6c8ee25d --- /dev/null +++ b/queries/js/IoT_Embla/Postman/Philips Hue.postman_collection.json @@ -0,0 +1,385 @@ +{ + "info": { + "_postman_id": "1c4f14ca-93cc-4d58-974a-5f8ad6dff167", + "name": "Philips Hue", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "15284646" + }, + "item": [ + { + "name": "Discover bridges (local network)", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "https://discovery.meethue.com/", + "protocol": "https", + "host": [ + "discovery", + "meethue", + "com" + ], + "path": [ + "" + ] + } + }, + "response": [] + }, + { + "name": "Create username and clientkey", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"devicetype\": \"mideind_hue_communication#smartdevice\",\n \"generateclientkey\": true\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "https://{{bridge_IP}}/api", + "protocol": "https", + "host": [ + "{{bridge_IP}}" + ], + "path": [ + "api" + ] + } + }, + "response": [] + }, + { + "name": "Get bridge config (basic)", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "http://{{bridge_IP}}/api/0/config", + "protocol": "http", + "host": [ + "{{bridge_IP}}" + ], + "path": [ + "api", + "0", + "config" + ] + } + }, + "response": [] + }, + { + "name": "Get bridge config (detailed)", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "http://{{bridge_IP}}/api/{{username}}/config", + "protocol": "http", + "host": [ + "{{bridge_IP}}" + ], + "path": [ + "api", + "{{username}}", + "config" + ] + } + }, + "response": [] + }, + { + "name": "Get lights", + "request": { + "method": "GET", + "header": [ + { + "key": "hue-application-key", + "value": "{{appkey}}", + "type": "text" + } + ], + "url": { + "raw": "http://{{bridge_IP}}/api/{{username}}/lights", + "protocol": "http", + "host": [ + "{{bridge_IP}}" + ], + "path": [ + "api", + "{{username}}", + "lights" + ] + } + }, + "response": [] + }, + { + "name": "Get scenes", + "request": { + "method": "GET", + "header": [ + { + "key": "hue-application-key", + "value": "{{appkey}}", + "type": "text" + } + ], + "url": { + "raw": "http://{{bridge_IP}}/api/{{username}}/scenes", + "protocol": "http", + "host": [ + "{{bridge_IP}}" + ], + "path": [ + "api", + "{{username}}", + "scenes" + ] + } + }, + "response": [] + }, + { + "name": "Get groups", + "request": { + "method": "GET", + "header": [ + { + "key": "hue-application-key", + "value": "{{appkey}}", + "type": "text" + } + ], + "url": { + "raw": "http://{{bridge_IP}}/api/{{username}}/groups", + "protocol": "http", + "host": [ + "{{bridge_IP}}" + ], + "path": [ + "api", + "{{username}}", + "groups" + ] + } + }, + "response": [] + }, + { + "name": "Get light info", + "request": { + "method": "GET", + "header": [ + { + "key": "hue-application-key", + "value": "{{appkey}}", + "type": "text" + } + ], + "url": { + "raw": "http://{{bridge_IP}}/api/{{username}}/lights/{{light_id}}", + "protocol": "http", + "host": [ + "{{bridge_IP}}" + ], + "path": [ + "api", + "{{username}}", + "lights", + "{{light_id}}" + ] + } + }, + "response": [] + }, + { + "name": "Turn light on", + "request": { + "method": "PUT", + "header": [ + { + "key": "hue-application-key", + "value": "{{appkey}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"on\": true\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://{{bridge_IP}}/api/{{username}}/lights/{{light_id}}/state", + "protocol": "http", + "host": [ + "{{bridge_IP}}" + ], + "path": [ + "api", + "{{username}}", + "lights", + "{{light_id}}", + "state" + ] + } + }, + "response": [] + }, + { + "name": "Turn light off", + "request": { + "method": "PUT", + "header": [ + { + "key": "hue-application-key", + "value": "{{appkey}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"on\": false\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://{{bridge_IP}}/api/{{username}}/lights/{{light_id}}/state", + "protocol": "http", + "host": [ + "{{bridge_IP}}" + ], + "path": [ + "api", + "{{username}}", + "lights", + "{{light_id}}", + "state" + ] + } + }, + "response": [] + }, + { + "name": "Turn light purple", + "request": { + "method": "PUT", + "header": [ + { + "key": "hue-application-key", + "value": "{{appkey}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"on\": true,\n \"bri\": 100,\n \"xy\": [{{purple}}]\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://{{bridge_IP}}/api/{{username}}/lights/{{light_id}}/state", + "protocol": "http", + "host": [ + "{{bridge_IP}}" + ], + "path": [ + "api", + "{{username}}", + "lights", + "{{light_id}}", + "state" + ] + } + }, + "response": [] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + } + ], + "variable": [ + { + "key": "bridge_IP", + "value": "192.168.1.68", + "type": "string" + }, + { + "key": "bridge_id", + "value": "ecb5fafffe1be1a4", + "type": "string" + }, + { + "key": "username", + "value": "q2jNarhGHO9izO0xZZXcoww5GYANGi6mZyJYgMdL", + "type": "string" + }, + { + "key": "clientkey", + "value": "FDE5EC6CF6B56049E9DE0C88B2485145", + "type": "string" + }, + { + "key": "appkey", + "value": "q2jNarhGHO9izO0xZZXcoww5GYANGi6mZyJYgMdL", + "type": "string" + }, + { + "key": "light_rid", + "value": "fbfb447e-1c21-4bb4-bacd-53353dec5349", + "type": "string" + }, + { + "key": "light_id", + "value": "1", + "type": "string" + }, + { + "key": "purple", + "value": "0.25, 0.06", + "type": "string" + }, + { + "key": "red", + "value": "0.675, 0.322", + "type": "string" + }, + { + "key": "green", + "value": "0.3091, 0.618", + "type": "string" + } + ] +} \ No newline at end of file diff --git a/queries/js/IoT_Embla/README.md b/queries/js/IoT_Embla/README.md new file mode 100644 index 00000000..2604b49e --- /dev/null +++ b/queries/js/IoT_Embla/README.md @@ -0,0 +1,4 @@ +# IoT_Embla + +Javascript for connecting to IoT devices. + diff --git a/queries/js/IoT_Embla/main.html b/queries/js/IoT_Embla/main.html new file mode 100644 index 00000000..59f05386 --- /dev/null +++ b/queries/js/IoT_Embla/main.html @@ -0,0 +1,58 @@ + + + + + + JS Hue test + + + + + + + + + +
+

Testing

+ + +

+ +
+

Brightness

+ +
+ +
+

Color

+ + + + + +

+
+ +
+ +
+
+ +
+
+ +
+
+

Brightness increase/decrease

+ + + + + +

+
+
+ + + diff --git a/queries/js/IoT_Embla/main.js b/queries/js/IoT_Embla/main.js new file mode 100644 index 00000000..4123cf5a --- /dev/null +++ b/queries/js/IoT_Embla/main.js @@ -0,0 +1,56 @@ +const BRIDGE_IP = "192.168.1.68"; +const USERNAME = "q2jNarhGHO9izO0xZZXcoww5GYANGi6mZyJYgMdL"; + +function light_show() { + fetch(`http://${BRIDGE_IP}/api/${USERNAME}/lights/1/state`, { + method: "PUT", + body: JSON.stringify({ on: true, bri: 100, xy: [0.55, 0.4] }), + }) + .then((resp) => resp.json()) + .then((j) => { + console.log(j); + }) + .catch((err) => { + console.log("an error occurred!"); + }); +} + +// function find_hub() { +// fetch(`https://discovery.meethue.com`) +// .then((resp) => resp.json()) +// .then((j) => { +// console.log(j); +// }) +// .catch((err) => { +// console.log("an error occurred!"); +// }); +// } + +function get_lights() { + fetch(`http://${BRIDGE_IP}/api/${USERNAME}/lights`,{ + method: "GET" + }) + .then((resp) => resp.json()) + .then((j) => { + console.log(j); + document.write(j) + return(j) + }) + .catch((err) => { + console.log("an error occured!") + }) +} + +function turn_off_lights() { + fetch(`http://${BRIDGE_IP}/api/${USERNAME}/lights/1/state`,{ + method: "PUT", + body: JSON.stringify({ on: false}) + }) + .then((resp) => resp.json()) + .then((j) => { + console.log(j); + }) + .catch((err) => { + console.log("an error occured!") + }) +} From a1929fbcc5a290fe5457f6ab5cb421fa0e8754d6 Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Tue, 7 Jun 2022 13:00:59 +0000 Subject: [PATCH 005/371] Started output functionality --- queries/iot_hue.py | 123 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 108 insertions(+), 15 deletions(-) diff --git a/queries/iot_hue.py b/queries/iot_hue.py index 6fe26786..142131b9 100755 --- a/queries/iot_hue.py +++ b/queries/iot_hue.py @@ -23,6 +23,8 @@ """ +from typing import Dict, Mapping, Optional, cast + import logging import random @@ -82,43 +84,70 @@ def help_text(lemma: str) -> str: QIoT → QIoTQuery '?'? QIoTQuery → - QIoTTurnOn | QIoTTurnOff | QIoTChangeColor | QIoTIncreaseBrightness #| QIoTDecreaseBrightness + QIoTTurnOn | QIoTTurnOff | QIoTSetColor | QIoTIncreaseBrightness | QIoTDecreaseBrightness QIoTTurnOn -> - "kveiktu" QIoTLightPhrase - | "kveiktu" "á" QIoTLightPhrase + "kveiktu" QIoTLightPhrase QIoTTurnOff -> "slökktu" QIoTLightPhrase - | "slökktu" "á" QIoTLightPhrase -QIoTChangeColor -> - "gerðu" QIoTLightPhrase QIoTColor - | "gerðu" QIoTLightPhrase QIoTColor QIoTGroupNamePhrase? +QIoTSetColor -> + "gerðu" QIoTColorLightPhrase QIoTColorNamePhrase + | "gerðu" QIoTLight QIoTColorNamePhrase QIoTGroupNamePhrase? + | "gerðu" QIoTColorNamePhrase QIoTGroupNamePhrase? + | "breyttu" QIoTColorLightPhrase í QIoTColorNamePhrase + | "breyttu" QIoTColorLight í QIoTColorNamePhrase QIoTGroupNamePhrase? + | "settu" QIoTColorNamePhrase QIoTColorLightPhrase + QIoTIncreaseBrightness -> QIoTIncrease QIoTBrightness QIoTLightPhrase? | "gerðu" QIoTLightPhrase "bjartara" -# QIoTDecreaseBrightness -> +QIoTDecreaseBrightness -> + QIoTDecrease QIoTBrightness QIoTLightPhrase? + | "gerðu" QIoTLightPhrase "bjartara" QIoTIncrease -> "hækkaðu" + | "auktu" + +QIoTDecrease -> + "lækkaðu" + | "minnkaðu" QIoTBrightness -> "birtu" | "birtustig" | "birtuna" | "birtustigið" +QIoTColorLightPhrase -> + QIoTColorLight QIoTGroupNamePhrase? + +QIoTColorLight -> + QIoTColor? "á"? "í"? QIoTLight + | QIoTColor + QIoTLightPhrase -> - "á"? QIoTLight QIoTGroupNamePhrase? + "á"? "í"? QIoTLight QIoTGroupNamePhrase? + | "á"? "í"? QIoTGroupNamePhrase +# tried making this 'ljós:no' to avoid ambiguity, but all queries failed as a result QIoTLight -> - "ljósið" | "ljósinu" | "ljósin" | "ljósunum" + 'ljós' QIoTColor -> + 'litur' + +QIoTColorName -> {" | ".join(f"'{color}:lo'" for color in _COLORS.keys())} +QIoTColorNamePhrase -> + QIoTColor? QIoTColorName + | QIoTColorName QIoTColor? + QIoTGroupNamePhrase -> - "í"? QIoTGroupName? + "í" QIoTGroupName + | "á" QIoTGroupName QIoTGroupName -> Nl @@ -130,18 +159,68 @@ def QIoTQuery(node: Node, params: QueryStateDict, result: Result) -> None: result.qtype = _IoT_QTYPE +def QIoTTurnOn(node: Node, params: QueryStateDict, result: Result) -> None: + result.action = "turn_off" + if "hue_obj" not in result: + result["hue_obj"] = {"on": True} + else: + result["hue_obj"]["on"] = True + + def QIoTTurnOff(node: Node, params: QueryStateDict, result: Result) -> None: - result.action = "turnoff" + result.action = "turn_on" + if "hue_obj" not in result: + result["hue_obj"] = {"on": False} + else: + result["hue_obj"]["on"] = False -def QIoTTurnOn(node: Node, params: QueryStateDict, result: Result) -> None: - result.action = "turnon" +def QIoTSetColor(node: Node, params: QueryStateDict, result: Result) -> None: + result.action = "set_color" + color_hue = _COLOR_NAME_TO_CIE.get(result.color_name, None) + if color_hue is not None: + if "hue_obj" not in result: + result["hue_obj"] = {"hue": color_hue} + else: + result["hue_obj"]["hue"] = color_hue + + +def QIoTIncreaseBrightness(node: Node, params: QueryStateDict, result: Result) -> None: + result.action = "increase_brightness" + if "hue_obj" not in result: + result["hue_obj"] = {"bri_inc": 64} + else: + result["hue_obj"]["bri_inc"] = 64 + + +def QIoTDecreaseBrightness(node: Node, params: QueryStateDict, result: Result) -> None: + result.action = "decrease_brightness" + if "hue_obj" not in result: + result["hue_obj"] = {"bri_inc": -64} + else: + result["hue_obj"]["bri_inc"] = -64 + + +def QIoTColorName(node: Node, params: QueryStateDict, result: Result) -> None: + result["color_name"] = result._indefinite def QIoTGroupName(node: Node, params: QueryStateDict, result: Result) -> None: result["group_name"] = result._indefinite +# Convert color name into hue +# Taken from home.py +_COLOR_NAME_TO_CIE: Mapping[str, float] = { + "gulur": 60 * 65535 / 360, + "grænn": 120 * 65535 / 360, + "ljósblár": 180 * 65535 / 360, + "blár": 240 * 65535 / 360, + "bleikur": 300 * 65535 / 360, + "rauður": 360 * 65535 / 360, +} + + def sentence(state: QueryStateDict, result: Result) -> None: """Called when sentence processing is complete""" q: Query = state["query"] @@ -155,8 +234,22 @@ def sentence(state: QueryStateDict, result: Result) -> None: try: # kalla í javascripts stuff group_name = result.get("group_name", "") + color_name = result.get("color_name", "") print("GROUP NAME:", group_name) - q.set_answer(*gen_answer("ég var að kveikja ljósin! " + group_name)) + print("COLOR NAME:", color_name) + print(result.hue_obj) + q.set_answer( + *gen_answer( + "ég var að kveikja ljósin! " + + group_name + + " " + + color_name + + " " + + result.action + + " " + + str(result.hue_obj.get("hue", "enginn litur")) + ) + ) except Exception as e: logging.warning("Exception while processing random query: {0}".format(e)) q.set_error("E_EXCEPTION: {0}".format(e)) From efaccbde523da8d7e1fb2591fdf925438b8d2ce9 Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Tue, 7 Jun 2022 16:34:48 +0000 Subject: [PATCH 006/371] Fixed embla integration --- queries/iot_hue.py | 271 ++++++++++++++++++ queries/js/IoT_Embla/Philips_Hue/lights.js | 73 +++++ .../js/IoT_Embla/Philips_Hue/set_lights.js | 87 ++++++ queries/js/IoT_Embla/main.js | 56 ++++ 4 files changed, 487 insertions(+) create mode 100755 queries/iot_hue.py create mode 100644 queries/js/IoT_Embla/Philips_Hue/lights.js create mode 100644 queries/js/IoT_Embla/Philips_Hue/set_lights.js create mode 100644 queries/js/IoT_Embla/main.js diff --git a/queries/iot_hue.py b/queries/iot_hue.py new file mode 100755 index 00000000..4955f55d --- /dev/null +++ b/queries/iot_hue.py @@ -0,0 +1,271 @@ +""" + + Greynir: Natural language processing for Icelandic + + Randomness query response module + + Copyright (C) 2022 Miðeind ehf. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see http://www.gnu.org/licenses/. + + This query module handles queries related to the generation + of random numbers, e.g. "Kastaðu tengingi", "Nefndu tölu milli 5 og 10", etc. + +""" + +from typing import Dict, Mapping, Optional, cast + +import logging +import random +import json + +from query import Query, QueryStateDict, AnswerTuple +from queries import gen_answer, read_jsfile +from tree import Result, Node + + +_IoT_QTYPE = "IoT" + +TOPIC_LEMMAS = ["ljós", "kveikja"] + + +def help_text(lemma: str) -> str: + """Help text to return when query.py is unable to parse a query but + one of the above lemmas is found in it""" + return "Ég skil þig ef þú segir til dæmis: {0}.".format( + random.choice(("Málfræðin þín er í bullinu",)) + ) + + +_COLORS = { + "gulur": 60 * 65535 / 360, + "rauður": 360 * 65535 / 360, + "grænn": 120 * 65535 / 360, + "blár": 240 * 65535 / 360, + "ljósblár": 180 * 65535 / 360, + "bleikur": 300 * 65535 / 360, + "hvítur": [], + "fjólublár": [], + "brúnn": [], + "appelsínugulur": [], +} + + +# This module wants to handle parse trees for queries +HANDLE_TREE = True + +# The grammar nonterminals this module wants to handle +QUERY_NONTERMINALS = {"QIoT"} + +# The context-free grammar for the queries recognized by this plug-in module +GRAMMAR = f""" + +Query → + QIoT + +QIoT → QIoTQuery '?'? + +QIoTQuery → + QIoTTurnOn | QIoTTurnOff | QIoTSetColor | QIoTIncreaseBrightness | QIoTDecreaseBrightness + +QIoTTurnOn -> + "kveiktu" QIoTLightPhrase + +QIoTTurnOff -> + "slökktu" QIoTLightPhrase + +QIoTSetColor -> + "gerðu" QIoTColorLightPhrase QIoTColorNamePhrase + | "gerðu" QIoTLight QIoTColorNamePhrase QIoTGroupNamePhrase? + | "gerðu" QIoTColorNamePhrase QIoTGroupNamePhrase? + | "breyttu" QIoTColorLightPhrase í QIoTColorNamePhrase + | "breyttu" QIoTColorLight í QIoTColorNamePhrase QIoTGroupNamePhrase? + | "settu" QIoTColorNamePhrase QIoTColorLightPhrase + + +QIoTIncreaseBrightness -> + QIoTIncrease QIoTBrightness QIoTLightPhrase? + | "gerðu" QIoTLightPhrase QIoTBrighter + +QIoTDecreaseBrightness -> + QIoTDecrease QIoTBrightness QIoTLightPhrase? + | "gerðu" QIoTLightPhrase QIoTDarker + +QIoTBrighter -> + "bjartara" + | "ljósara" + +QIoTDarker -> + "dimmara" + | "dekkra" + +QIoTIncrease -> + "hækkaðu" + | "auktu" + +QIoTDecrease -> + "lækkaðu" + | "minnkaðu" + +QIoTBrightness -> + "birtu" | "birtustig" | "birtuna" | "birtustigið" + +QIoTColorLightPhrase -> + QIoTColorLight QIoTGroupNamePhrase? + +QIoTColorLight -> + QIoTColor? "á"? "í"? QIoTLight + | QIoTColor + +QIoTLightPhrase -> + "á"? "í"? QIoTLight QIoTGroupNamePhrase? + | "á"? "í"? QIoTGroupNamePhrase + +# tried making this 'ljós:no' to avoid ambiguity, but all queries failed as a result +QIoTLight -> + 'ljós' + +QIoTColor -> + 'litur' + +QIoTColorName -> + {" | ".join(f"'{color}:lo'" for color in _COLORS.keys())} + +QIoTColorNamePhrase -> + QIoTColor? QIoTColorName + | QIoTColorName QIoTColor? + +QIoTColorNamePhrase -> + QIoTColor? QIoTColorName + | QIoTColorName QIoTColor? + +QIoTGroupNamePhrase -> + "í" QIoTGroupName + | "á" QIoTGroupName + +QIoTGroupName -> + no + +""" + + +def QIoTQuery(node: Node, params: QueryStateDict, result: Result) -> None: + result.qtype = _IoT_QTYPE + + +def QIoTTurnOn(node: Node, params: QueryStateDict, result: Result) -> None: + result.action = "turn_on" + if "hue_obj" not in result: + result["hue_obj"] = {"on": True} + else: + result["hue_obj"]["on"] = True + + +def QIoTTurnOff(node: Node, params: QueryStateDict, result: Result) -> None: + result.action = "turn_off" + if "hue_obj" not in result: + result["hue_obj"] = {"on": False} + else: + result["hue_obj"]["on"] = False + + +def QIoTSetColor(node: Node, params: QueryStateDict, result: Result) -> None: + result.action = "set_color" + print(result.color_name) + color_hue = _COLORS.get(result.color_name, None) + print(color_hue) + if color_hue is not None: + if "hue_obj" not in result: + result["hue_obj"] = {"hue": int(color_hue)} + else: + result["hue_obj"]["hue"] = int(color_hue) + + +def QIoTIncreaseBrightness(node: Node, params: QueryStateDict, result: Result) -> None: + result.action = "increase_brightness" + if "hue_obj" not in result: + result["hue_obj"] = {"bri_inc": 64} + else: + result["hue_obj"]["bri_inc"] = 64 + + +def QIoTDecreaseBrightness(node: Node, params: QueryStateDict, result: Result) -> None: + result.action = "decrease_brightness" + if "hue_obj" not in result: + result["hue_obj"] = {"bri_inc": -64} + else: + result["hue_obj"]["bri_inc"] = -64 + + +def QIoTColorName(node: Node, params: QueryStateDict, result: Result) -> None: + result["color_name"] = ( + node.first_child(lambda x: True).string_self().strip("'").split(":")[0] + ) + + +def QIoTGroupName(node: Node, params: QueryStateDict, result: Result) -> None: + result["group_name"] = result._indefinite + + +# Convert color name into hue +# Taken from home.py +_COLOR_NAME_TO_CIE: Mapping[str, float] = { + "gulur": 60 * 65535 / 360, + "grænn": 120 * 65535 / 360, + "ljósblár": 180 * 65535 / 360, + "blár": 240 * 65535 / 360, + "bleikur": 300 * 65535 / 360, + "rauður": 360 * 65535 / 360, + # "Rauð": 360 * 65535 / 360, +} + + +def sentence(state: QueryStateDict, result: Result) -> None: + """Called when sentence processing is complete""" + q: Query = state["query"] + if "qtype" not in result: + q.set_error("E_QUERY_NOT_UNDERSTOOD") + return + + # Successfully matched a query type + q.set_qtype(result.qtype) + + try: + # kalla í javascripts stuff + group_name = result.get("group_name", "") + color_name = result.get("color_name", "") + print("GROUP NAME:", group_name) + print("COLOR NAME:", color_name) + print(result.hue_obj) + q.set_answer( + *gen_answer( + "ég var að kveikja ljósin! " + # + group_name + # + " " + # + color_name + # + " " + # + result.action + # + " " + # + str(result.hue_obj.get("hue", "enginn litur")) + ) + ) + js = read_jsfile("IoT_Embla/Philips_Hue/lights.js") + read_jsfile( + "IoT_Embla/Philips_Hue/set_lights.js" + ) + js += f"syncSetLights('{group_name}', '{json.dumps(result.hue_obj)}');" + q.set_command(js) + # print(js) + except Exception as e: + logging.warning("Exception while processing random query: {0}".format(e)) + q.set_error("E_EXCEPTION: {0}".format(e)) + raise diff --git a/queries/js/IoT_Embla/Philips_Hue/lights.js b/queries/js/IoT_Embla/Philips_Hue/lights.js new file mode 100644 index 00000000..26736336 --- /dev/null +++ b/queries/js/IoT_Embla/Philips_Hue/lights.js @@ -0,0 +1,73 @@ +var BRIDGE_IP = "192.168.1.68"; +var USERNAME = "q2jNarhGHO9izO0xZZXcoww5GYANGi6mZyJYgMdL"; + +function changeBrightness() { + var sliderValue = document.getElementById("brightness_slider").value; + console.log(sliderValue); + + fetch(`http://${BRIDGE_IP}/api/${USERNAME}/lights/1/state`, { + method: "PUT", + body: JSON.stringify({ bri: Number(sliderValue)}) + }) + .then((resp) => resp.json()) + .then((j) => { + console.log(j); + }) + .catch((err) => { + console.log("an error occurred!"); + }); +} + +function changeColor() { + xValue = Number(document.getElementById("color_x").value) + yValue = Number(document.getElementById("color_y").value) + console.log(xValue, yValue); + + if(xValue === undefined || yValue === undefined || xValue > 1 || xValue < 0 || yValue > 1 || yValue < 0) { + document.getElementById("color_error").innerHTML = "Please enter a value between 0 and 1."; + } + else { + document.getElementById("color_error").innerHTML = ""; + colorValue = [Number(xValue), Number(yValue)] + console.log(colorValue); + fetch(`http://${BRIDGE_IP}/api/${USERNAME}/lights/1/state`, { + method: "PUT", + body: JSON.stringify({ xy: colorValue }) + }) + .then((resp) => resp.json()) + .then((j) => { + console.log(j); + }) + .catch((err) => { + console.log("an error occurred!"); + }); + } + +} + + +function getAllLights(hub_ip = "192.168.1.68", username = "q2jNarhGHO9izO0xZZXcoww5GYANGi6mZyJYgMdL") { + return fetch(`http://${hub_ip}/api/${username}/lights`) + .then((resp) => resp.json()) +} + +function getAllGroups(hub_ip = "192.168.1.68", username = "q2jNarhGHO9izO0xZZXcoww5GYANGi6mZyJYgMdL") { + return fetch(`http://${hub_ip}/api/${username}/groups`) + .then((resp) => resp.json()) +} + +function getCurrentState(id) { + return fetch(`http://${BRIDGE_IP}/api/${USERNAME}/lights/${id}`) + .then((resp) => resp.json()) +} + + +async function test(){ + console.log(getAllGroups()) + var lights = await getAllLights(); + var groups = await getAllGroups(); + console.log("lights:", lights) + console.log("groups:", groups) +} + + diff --git a/queries/js/IoT_Embla/Philips_Hue/set_lights.js b/queries/js/IoT_Embla/Philips_Hue/set_lights.js new file mode 100644 index 00000000..50f54725 --- /dev/null +++ b/queries/js/IoT_Embla/Philips_Hue/set_lights.js @@ -0,0 +1,87 @@ +// "use strict"; +// const QUERY_EX = "eldhús"; +// const STATE_EX = JSON.stringify({bri_inc: 64}) +// const LIGHTS_EX = test(); +// const LIGHTS_EX = '{"1":{"state":{"on":true,"bri":100,"hue":6140,"sat":232,"effect":"none","xy":[0.5503,0.4000],"ct":500,"alert":"select","colormode":"xy","mode":"homeautomation","reachable":true},"swupdate":{"state":"noupdates","lastinstall":"2022-05-27T14:23:54"},"type":"Extended color light","name":"litaljós","modelid":"LCA001","manufacturername":"Signify Netherlands B.V.","productname":"Hue color lamp","capabilities":{"certified":true,"control":{"mindimlevel":200,"maxlumen":800,"colorgamuttype":"C","colorgamut":[[0.6915,0.3083],[0.1700,0.7000],[0.1532,0.0475]],"ct":{"min":153,"max":500}},"streaming":{"renderer":true,"proxy":true}},"config":{"archetype":"sultanbulb","function":"mixed","direction":"omnidirectional","startup":{"mode":"safety","configured":true}},"uniqueid":"00:17:88:01:06:79:c3:94-0b","swversion":"1.93.7","swconfigid":"3C05E7B6","productid":"Philips-LCA001-5-A19ECLv6"},"2":{"state":{"on":true,"bri":2,"ct":454,"alert":"select","colormode":"ct","mode":"homeautomation","reachable":false},"swupdate":{"state":"notupdatable","lastinstall":"2020-06-29T12:05:21"},"type":"Color temperature light","name":"Ikea pera Uno","modelid":"TRADFRI bulb E27 WS opal 1000lm","manufacturername":"IKEA of Sweden","productname":"Color temperature light","capabilities":{"certified":false,"control":{"ct":{"min":250,"max":454}},"streaming":{"renderer":false,"proxy":false}},"config":{"archetype":"classicbulb","function":"functional","direction":"omnidirectional"},"uniqueid":"cc:cc:cc:ff:fe:02:92:52-01","swversion":"2.0.023"},"3":{"state":{"on":true,"bri":105,"ct":454,"alert":"select","colormode":"ct","mode":"homeautomation","reachable":false},"swupdate":{"state":"notupdatable","lastinstall":"2020-07-20T13:03:26"},"type":"Color temperature light","name":"lesljós","modelid":"TRADFRI bulb E14 WS opal 400lm","manufacturername":"IKEA of Sweden","productname":"Color temperature light","capabilities":{"certified":false,"control":{"ct":{"min":250,"max":454}},"streaming":{"renderer":false,"proxy":false}},"config":{"archetype":"classicbulb","function":"functional","direction":"omnidirectional"},"uniqueid":"90:fd:9f:ff:fe:93:be:a1-01","swversion":"1.2.217"}}' +// const GROUPS_EX = '{"1":{"name":"eldhús","lights":["1"],"sensors":[],"type":"Room","state":{"all_on":true,"any_on":true},"recycle":false,"class":"Living room","action":{"on":true,"bri":100,"hue":6140,"sat":232,"effect":"none","xy":[0.5503,0.4000],"ct":500,"alert":"select","colormode":"xy"}},"2":{"name":"Hús","lights":["3","2","1"],"sensors":[],"type":"Zone","state":{"all_on":true,"any_on":true},"recycle":false,"class":"Downstairs","action":{"on":true,"bri":100,"hue":6140,"sat":232,"effect":"none","xy":[0.5503,0.4000],"ct":500,"alert":"select","colormode":"xy"}},"3":{"name":"skrifstofa","lights":["2","3"],"sensors":[],"type":"Room","state":{"all_on":true,"any_on":true},"recycle":false,"class":"Living room","action":{"on":true,"bri":105,"ct":454,"alert":"select","colormode":"ct"}}}' + + + + +async function getIdObject(query) { + let allLights = await getAllLights(); + let allGroups = await getAllGroups(); + + for (let light in allLights) { + if (allLights[light].name === query) { + let return_object = {id: light, type: "light", url: `lights/${light}/state`} + return return_object + } + } + for (let group in allGroups) { + if (allGroups[group].name === query) { + let return_object = {id: group, type: "group", url: `groups/${group}/action`} + return return_object + } + } + //vantar e.k. error + return +} + + +async function getSceneID(scene_name){ + let allScenes = await getAllScenes(); + for (let scene in allScenes) { + if (allScenes[scene].name === scene_name) { + console.log("matching scene id: " + scene) + return scene + } + } +} + + +async function setLights(query, state){ + let idObject = await getIdObject(query) + let ID = idObject.id + let parsed_state = JSON.parse(state) + + // Check if state includes a scene or a brightness change + if (parsed_state.scene) { + parsed_state.scene = await getSceneID(parsed_state.scene) + state = JSON.stringify(parsed_state) + } + else if (parsed_state.bri_inc) { + console.log(parsed_state.bri_inc) + console.log("New brightness = " + (parsed_state.bri_inc + parsed_state.bri)) + state = JSON.stringify(parsed_state) + } + // Send data to API + let url = idObject.url + console.log(state) + fetch(`http://${BRIDGE_IP}/api/${USERNAME}/${url}`, { + method: "PUT", + body: state, + }) + .then((resp) => resp.json()) + .then((j) => { + console.log(j); + }) + .catch((err) => { + console.log("an error occurred!"); + }); +} + + +function setLightsFromHTML() { + let query = document.getElementById("queryInput").value + let stateObject = new Object(); + stateObject.bri_inc = Number(document.getElementById("brightnessInput").value) + stateObject = JSON.stringify(stateObject) + console.log(stateObject); + setLights(query, stateObject) +} + +function syncSetLights(query, state) { + setLights(query, state); + return query; +} \ No newline at end of file diff --git a/queries/js/IoT_Embla/main.js b/queries/js/IoT_Embla/main.js new file mode 100644 index 00000000..cda3b90e --- /dev/null +++ b/queries/js/IoT_Embla/main.js @@ -0,0 +1,56 @@ +var BRIDGE_IP = "192.168.1.68"; +var USERNAME = "q2jNarhGHO9izO0xZZXcoww5GYANGi6mZyJYgMdL"; + +function light_show() { + fetch(`http://${BRIDGE_IP}/api/${USERNAME}/lights/1/state`, { + method: "PUT", + body: JSON.stringify({ on: true, bri: 100, xy: [0.55, 0.4] }), + }) + .then((resp) => resp.json()) + .then((j) => { + console.log(j); + }) + .catch((err) => { + console.log("an error occurred!"); + }); +} + +// function find_hub() { +// fetch(`https://discovery.meethue.com`) +// .then((resp) => resp.json()) +// .then((j) => { +// console.log(j); +// }) +// .catch((err) => { +// console.log("an error occurred!"); +// }); +// } + +function get_lights() { + fetch(`http://${BRIDGE_IP}/api/${USERNAME}/lights`,{ + method: "GET" + }) + .then((resp) => resp.json()) + .then((j) => { + console.log(j); + document.write(j) + return(j) + }) + .catch((err) => { + console.log("an error occured!") + }) +} + +function turn_off_lights() { + fetch(`http://${BRIDGE_IP}/api/${USERNAME}/lights/1/state`,{ + method: "PUT", + body: JSON.stringify({ on: false}) + }) + .then((resp) => resp.json()) + .then((j) => { + console.log(j); + }) + .catch((err) => { + console.log("an error occured!") + }) +} From 71687e99d472ee6974f9f8cb74c54224e7b9e19e Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Wed, 8 Jun 2022 10:47:55 +0000 Subject: [PATCH 007/371] Expanding grammar --- queries/iot_hue.py | 52 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 10 deletions(-) diff --git a/queries/iot_hue.py b/queries/iot_hue.py index 4955f55d..201ba306 100755 --- a/queries/iot_hue.py +++ b/queries/iot_hue.py @@ -76,7 +76,13 @@ def help_text(lemma: str) -> str: QIoT → QIoTQuery '?'? QIoTQuery → - QIoTTurnOn | QIoTTurnOff | QIoTSetColor | QIoTIncreaseBrightness | QIoTDecreaseBrightness + QIoTTurnOn + | QIoTTurnOff + | QIoTSetColor + | QIoTIncreaseBrightness + | QIoTDecreaseBrightness + # | QIoTMaxBrightness + # | QIoTMinBrightness QIoTTurnOn -> "kveiktu" QIoTLightPhrase @@ -90,8 +96,7 @@ def help_text(lemma: str) -> str: | "gerðu" QIoTColorNamePhrase QIoTGroupNamePhrase? | "breyttu" QIoTColorLightPhrase í QIoTColorNamePhrase | "breyttu" QIoTColorLight í QIoTColorNamePhrase QIoTGroupNamePhrase? - | "settu" QIoTColorNamePhrase QIoTColorLightPhrase - + | "settu" QIoTColorNamePhrase QIoTColorLightPhrase QIoTIncreaseBrightness -> QIoTIncrease QIoTBrightness QIoTLightPhrase? @@ -101,6 +106,12 @@ def help_text(lemma: str) -> str: QIoTDecrease QIoTBrightness QIoTLightPhrase? | "gerðu" QIoTLightPhrase QIoTDarker +# QIoTMaxBrightness -> +# "stilltu" QIoTLightPhrase + +# QIoTMinBrightness -> +# "stilltu" QIoTLightPhrase + QIoTBrighter -> "bjartara" | "ljósara" @@ -118,18 +129,21 @@ def help_text(lemma: str) -> str: | "minnkaðu" QIoTBrightness -> - "birtu" | "birtustig" | "birtuna" | "birtustigið" + 'birta' + | 'birtustigið' + | QIoTLight QIoTColorLightPhrase -> QIoTColorLight QIoTGroupNamePhrase? + | QIoTGroupNamePhrase QIoTColorLight -> - QIoTColor? "á"? "í"? QIoTLight + QIoTColor? QIoTLight | QIoTColor QIoTLightPhrase -> - "á"? "í"? QIoTLight QIoTGroupNamePhrase? - | "á"? "í"? QIoTGroupNamePhrase + QIoTLight QIoTGroupNamePhrase? + | QIoTGroupNamePhrase # tried making this 'ljós:no' to avoid ambiguity, but all queries failed as a result QIoTLight -> @@ -150,12 +164,29 @@ def help_text(lemma: str) -> str: | QIoTColorName QIoTColor? QIoTGroupNamePhrase -> - "í" QIoTGroupName - | "á" QIoTGroupName + QIoTLocationPreposition QIoTGroupName +#The Nl, noun phrase, is too greedy, e.g. parsing "ljósin í eldhúsinu" as the group name. +# But no, noun, is too strict, e.g. "herbergið hans Loga" could be a user-made group name. QIoTGroupName -> no +QIoTLocationPreposition -> + QIoTLocationPrepositionFirstPart? QIoTLocationPrepositionSecondPart + +# The latter proverbs are grammatically incorrect, but common errors, both in speech and transcription. +# The list provided is taken from StefnuAtv in Greynir.grammar. That includes "aftur:ao", which is not applicable here. +QIoTLocationPrepositionFirstPart -> + StaðarAtv + | "fram:ao" + | "inn:ao" + | "niður:ao" + | "upp:ao" + | "út:ao" + +QIoTLocationPrepositionSecondPart -> + "á" | "í" + """ @@ -186,9 +217,10 @@ def QIoTSetColor(node: Node, params: QueryStateDict, result: Result) -> None: print(color_hue) if color_hue is not None: if "hue_obj" not in result: - result["hue_obj"] = {"hue": int(color_hue)} + result["hue_obj"] = {"on": True, "hue": int(color_hue)} else: result["hue_obj"]["hue"] = int(color_hue) + result["hue_obj"]["on"] = True def QIoTIncreaseBrightness(node: Node, params: QueryStateDict, result: Result) -> None: From abf4a6ebd39917a6e16cd666bfc0a127c1e6abf2 Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Wed, 8 Jun 2022 16:54:41 +0000 Subject: [PATCH 008/371] hub.js updated with fetch functionality and sentence function in iothue.py updated to get data from server hub.js updated with fetch functionality and sentence function in iothue.py updated to get data from server --- queries/__init__.py | 2 +- queries/iot_hue.py | 41 ++++- queries/js/IoT_Embla/Philips_Hue/fuse_test.js | 2 +- queries/js/IoT_Embla/Philips_Hue/hub.js | 155 +++++++++++++++++- queries/js/IoT_Embla/Philips_Hue/lights.js | 15 +- .../js/IoT_Embla/Philips_Hue/set_lights.js | 8 +- queries/js/IoT_Embla/main.html | 5 +- queries/js/IoT_Embla/main.js | 3 +- routes/api.py | 6 +- 9 files changed, 207 insertions(+), 30 deletions(-) diff --git a/queries/__init__.py b/queries/__init__.py index 7d59d7a7..3884a7a9 100755 --- a/queries/__init__.py +++ b/queries/__init__.py @@ -661,7 +661,7 @@ def timezone4loc( return None -@lru_cache(maxsize=32) +# @lru_cache(maxsize=32) def read_jsfile(filename: str) -> str: """Read and return a minified JavaScript (.js) file""" # The file is read from the directory 'js' within the directory diff --git a/queries/iot_hue.py b/queries/iot_hue.py index c707a500..c566d301 100755 --- a/queries/iot_hue.py +++ b/queries/iot_hue.py @@ -23,17 +23,29 @@ """ + from typing import Dict, Mapping, Optional, cast +from typing_extensions import TypedDict import logging import random import json +import flask from query import Query, QueryStateDict, AnswerTuple from queries import gen_answer, read_jsfile from tree import Result, Node +class SmartLights(TypedDict): + selected_light: str + philips_hue: Dict[str, str] + + +class DeviceData(TypedDict): + smartlights: SmartLights + + _IoT_QTYPE = "IoT" TOPIC_LEMMAS = ["ljós", "kveikja"] @@ -270,11 +282,31 @@ def sentence(state: QueryStateDict, result: Result) -> None: q.set_error("E_QUERY_NOT_UNDERSTOOD") return + host = flask.request.host + smartdevice_type = "smartlights" + + # Fetch relevant data from the device_data table to perform an action on the lights + device_data = cast(Optional[DeviceData], q.client_data(smartdevice_type)) + + selected_light: Optional[str] = None + hue_credentials: Optional[Dict[str, str]] = None + if device_data is not None and smartdevice_type in device_data: + dev = device_data[smartdevice_type] + assert dev is not None + selected_light = dev.get("selected_light") + hue_credentials = dev.get("philips_hue") + bridge_ip = hue_credentials.get("ipAddress") + username = hue_credentials.get("username") + + if not device_data or not hue_credentials: + answer = "Snjalltæki hafa ekki verið sett upp" + q.set_answer(*gen_answer(answer)) + return + # Successfully matched a query type q.set_qtype(result.qtype) try: - # kalla í javascripts stuff group_name = result.get("group_name", "") color_name = result.get("color_name", "") print("GROUP NAME:", group_name) @@ -292,12 +324,13 @@ def sentence(state: QueryStateDict, result: Result) -> None: # + str(result.hue_obj.get("hue", "enginn litur")) ) ) - js = read_jsfile("IoT_Embla/Philips_Hue/lights.js") + read_jsfile( - "IoT_Embla/Philips_Hue/set_lights.js" + js = ( + f"var BRIDGE_IP = '{bridge_ip}';var USERNAME = '{username}';" + + read_jsfile("IoT_Embla/Philips_Hue/lights.js") + + read_jsfile("IoT_Embla/Philips_Hue/set_lights.js") ) js += f"syncSetLights('{group_name}', '{json.dumps(result.hue_obj)}');" q.set_command(js) - # print(js) except Exception as e: logging.warning("Exception while processing random query: {0}".format(e)) q.set_error("E_EXCEPTION: {0}".format(e)) diff --git a/queries/js/IoT_Embla/Philips_Hue/fuse_test.js b/queries/js/IoT_Embla/Philips_Hue/fuse_test.js index dd7aff45..455ca318 100644 --- a/queries/js/IoT_Embla/Philips_Hue/fuse_test.js +++ b/queries/js/IoT_Embla/Philips_Hue/fuse_test.js @@ -43,7 +43,7 @@ const books = [ }) // 3. Now search! - console.log(fuse.search('steve')) + // console.log(fuse.search('steve')) // console.log(fuseLights.search('lita')) // Output: diff --git a/queries/js/IoT_Embla/Philips_Hue/hub.js b/queries/js/IoT_Embla/Philips_Hue/hub.js index b1413b6c..f20b71fd 100644 --- a/queries/js/IoT_Embla/Philips_Hue/hub.js +++ b/queries/js/IoT_Embla/Philips_Hue/hub.js @@ -1,10 +1,153 @@ -function findHub() { - fetch(`https://discovery.meethue.com`) + +"use strict"; + +//const XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest; + +// function getSmartDeviceAddress() { +// var request = new XMLHttpRequest(); +// request.open('GET', 'https://discovery.meethue.com', false); // `false` makes the request synchronous +// request.send(null); + +// if (request.status === 200) { +// return JSON.parse(request.responseText)[0]; +// } +// else { +// return 'No smart device found'; +// } +// } + +// function createNewDeveloper(ipAddress) { + +// const body = JSON.stringify({ +// 'devicetype': 'mideind_hue_communication#smartdevice' +// }); + +// var request = new XMLHttpRequest(); +// request.open('POST', `http://${ipAddress}/api`, false); // `false` makes the request synchronous +// request.send(body); + + +// if (request.status === 200) { +// return JSON.parse(request.responseText)[0]; +// } +// else { +// throw new Error('Error while creating new user'); +// } +// } + +async function findHub() { + console.log("find hub") + return fetch(`https://discovery.meethue.com`) .then((resp) => resp.json()) - .then((j) => { - console.log(j); + .then((obj) => { + return(obj); }) .catch((err) => { - console.log("an error occurred!"); + console.log("No smart device found!"); }); -} \ No newline at end of file + let hubObj = new Object() + hubObj.id = "ecb5fafffe1be1a4" + hubObj.internalipaddress = "192.168.1.68" + hubObj.port = "443" + console.log(hubObj) + return hubObj +} + +async function createNewDeveloper(ipAddress) { + console.log("create new developer") + const body = JSON.stringify({ + 'devicetype': 'mideind_hue_communication#smartdevice' + }); + return fetch(`http://${ipAddress}/api`, { + method: "POST", + body: body, + }) + .then((resp) => resp.json()) + .then((obj) => { + return(obj); + }) + .catch((err) => { + console.log("Error while creating new user"); + }); +} + +// function storeDevice(data, requestURL) { + +// let request = new XMLHttpRequest(); + +// request.open('POST', `http://${requestURL}/register_query_data.api`, false); +// request.setRequestHeader('Content-Type', 'application/json'); + +// request.send(JSON.stringify(data)); + +// if (request.status === 200) { +// return JSON.parse(request.responseText); +// } +// else { +// throw new Error('Error while storing user'); +// } + +// } + + +async function storeDevice(data, requestURL) { + console.log("store device") + console.log("data :", data) + return fetch(`http://${requestURL}/register_query_data.api`, { + method: "POST", + body: JSON.stringify(data), + headers: { + 'Content-Type': 'application/json' + }, + }) + .then((resp) => resp.json()) + .then((obj) => { + return(obj); + }) + .catch((err) => { + console.log("Error while storing user"); + }); +} + +async function connectHub(clientID="82AD3C91-7DA2-4502-BB17-075CEC090B14", requestURL="192.168.1.69:5000") { + console.log("connect hub") + let deviceInfo = await findHub(); + console.log("device info: ", deviceInfo) + + try { + let username2 = await createNewDeveloper(deviceInfo.internalipaddress); + // let username2 = new Object(); + // username2.success = new Object(); + // username2.success.username2 = "3ERlJxkO23rvt2WK3Sks5nFMvKC1dpQzRbQu4QdV" + console.log("username: ",username2) + if (!username2.success) { + return 'Ýttu á \'Philips\' takkann á tengiboxinu og reyndu aftur'; + } + + const data = { + 'client_id': clientID, + 'key': 'smartlights', + 'data': { + 'smartlights': { + 'selected_light': 'philips_hue', + 'philips_hue': { + 'username':username2.success.username2, + 'ipAddress':deviceInfo.internalipaddress + } + } + } + }; + + const result = await storeDevice(data, requestURL); + console.log("result: ", result) + return 'Tenging við snjalltæki tókst'; + } catch(error) { + console.log(error); + return 'Ekki tókst að tengja snjalltæki'; + } + // Errors for connectHub + // {"error":{"address":"","description":"link button not pressed","type":101}} + // {"error":"Failed to execute 'send' on 'XMLHttpRequest': Failed to load 'http://192.168.1.140/api'."} + // {"success":{"username":"2GTB-NVq68YwLRA43AZLPHmMiuvRL8yaZJykuJBg"}} +} + diff --git a/queries/js/IoT_Embla/Philips_Hue/lights.js b/queries/js/IoT_Embla/Philips_Hue/lights.js index 26736336..990e7859 100644 --- a/queries/js/IoT_Embla/Philips_Hue/lights.js +++ b/queries/js/IoT_Embla/Philips_Hue/lights.js @@ -1,5 +1,4 @@ -var BRIDGE_IP = "192.168.1.68"; -var USERNAME = "q2jNarhGHO9izO0xZZXcoww5GYANGi6mZyJYgMdL"; + function changeBrightness() { var sliderValue = document.getElementById("brightness_slider").value; @@ -10,8 +9,8 @@ function changeBrightness() { body: JSON.stringify({ bri: Number(sliderValue)}) }) .then((resp) => resp.json()) - .then((j) => { - console.log(j); + .then((obj) => { + console.log(obj); }) .catch((err) => { console.log("an error occurred!"); @@ -35,8 +34,8 @@ function changeColor() { body: JSON.stringify({ xy: colorValue }) }) .then((resp) => resp.json()) - .then((j) => { - console.log(j); + .then((obj) => { + console.log(obj); }) .catch((err) => { console.log("an error occurred!"); @@ -46,12 +45,12 @@ function changeColor() { } -function getAllLights(hub_ip = "192.168.1.68", username = "q2jNarhGHO9izO0xZZXcoww5GYANGi6mZyJYgMdL") { +function getAllLights(hub_ip = BRIDGE_IP, username = USERNAME) { return fetch(`http://${hub_ip}/api/${username}/lights`) .then((resp) => resp.json()) } -function getAllGroups(hub_ip = "192.168.1.68", username = "q2jNarhGHO9izO0xZZXcoww5GYANGi6mZyJYgMdL") { +function getAllGroups(hub_ip = BRIDGE_IP, username = USERNAME) { return fetch(`http://${hub_ip}/api/${username}/groups`) .then((resp) => resp.json()) } diff --git a/queries/js/IoT_Embla/Philips_Hue/set_lights.js b/queries/js/IoT_Embla/Philips_Hue/set_lights.js index 50f54725..d865bc3b 100644 --- a/queries/js/IoT_Embla/Philips_Hue/set_lights.js +++ b/queries/js/IoT_Embla/Philips_Hue/set_lights.js @@ -1,6 +1,6 @@ -// "use strict"; +"use strict"; // const QUERY_EX = "eldhús"; -// const STATE_EX = JSON.stringify({bri_inc: 64}) +// const STATE_EX = JSON.stringify({scene: "rómó"}) // const LIGHTS_EX = test(); // const LIGHTS_EX = '{"1":{"state":{"on":true,"bri":100,"hue":6140,"sat":232,"effect":"none","xy":[0.5503,0.4000],"ct":500,"alert":"select","colormode":"xy","mode":"homeautomation","reachable":true},"swupdate":{"state":"noupdates","lastinstall":"2022-05-27T14:23:54"},"type":"Extended color light","name":"litaljós","modelid":"LCA001","manufacturername":"Signify Netherlands B.V.","productname":"Hue color lamp","capabilities":{"certified":true,"control":{"mindimlevel":200,"maxlumen":800,"colorgamuttype":"C","colorgamut":[[0.6915,0.3083],[0.1700,0.7000],[0.1532,0.0475]],"ct":{"min":153,"max":500}},"streaming":{"renderer":true,"proxy":true}},"config":{"archetype":"sultanbulb","function":"mixed","direction":"omnidirectional","startup":{"mode":"safety","configured":true}},"uniqueid":"00:17:88:01:06:79:c3:94-0b","swversion":"1.93.7","swconfigid":"3C05E7B6","productid":"Philips-LCA001-5-A19ECLv6"},"2":{"state":{"on":true,"bri":2,"ct":454,"alert":"select","colormode":"ct","mode":"homeautomation","reachable":false},"swupdate":{"state":"notupdatable","lastinstall":"2020-06-29T12:05:21"},"type":"Color temperature light","name":"Ikea pera Uno","modelid":"TRADFRI bulb E27 WS opal 1000lm","manufacturername":"IKEA of Sweden","productname":"Color temperature light","capabilities":{"certified":false,"control":{"ct":{"min":250,"max":454}},"streaming":{"renderer":false,"proxy":false}},"config":{"archetype":"classicbulb","function":"functional","direction":"omnidirectional"},"uniqueid":"cc:cc:cc:ff:fe:02:92:52-01","swversion":"2.0.023"},"3":{"state":{"on":true,"bri":105,"ct":454,"alert":"select","colormode":"ct","mode":"homeautomation","reachable":false},"swupdate":{"state":"notupdatable","lastinstall":"2020-07-20T13:03:26"},"type":"Color temperature light","name":"lesljós","modelid":"TRADFRI bulb E14 WS opal 400lm","manufacturername":"IKEA of Sweden","productname":"Color temperature light","capabilities":{"certified":false,"control":{"ct":{"min":250,"max":454}},"streaming":{"renderer":false,"proxy":false}},"config":{"archetype":"classicbulb","function":"functional","direction":"omnidirectional"},"uniqueid":"90:fd:9f:ff:fe:93:be:a1-01","swversion":"1.2.217"}}' // const GROUPS_EX = '{"1":{"name":"eldhús","lights":["1"],"sensors":[],"type":"Room","state":{"all_on":true,"any_on":true},"recycle":false,"class":"Living room","action":{"on":true,"bri":100,"hue":6140,"sat":232,"effect":"none","xy":[0.5503,0.4000],"ct":500,"alert":"select","colormode":"xy"}},"2":{"name":"Hús","lights":["3","2","1"],"sensors":[],"type":"Zone","state":{"all_on":true,"any_on":true},"recycle":false,"class":"Downstairs","action":{"on":true,"bri":100,"hue":6140,"sat":232,"effect":"none","xy":[0.5503,0.4000],"ct":500,"alert":"select","colormode":"xy"}},"3":{"name":"skrifstofa","lights":["2","3"],"sensors":[],"type":"Room","state":{"all_on":true,"any_on":true},"recycle":false,"class":"Living room","action":{"on":true,"bri":105,"ct":454,"alert":"select","colormode":"ct"}}}' @@ -63,8 +63,8 @@ async function setLights(query, state){ body: state, }) .then((resp) => resp.json()) - .then((j) => { - console.log(j); + .then((obj) => { + console.log(obj); }) .catch((err) => { console.log("an error occurred!"); diff --git a/queries/js/IoT_Embla/main.html b/queries/js/IoT_Embla/main.html index 59f05386..3c4eea34 100644 --- a/queries/js/IoT_Embla/main.html +++ b/queries/js/IoT_Embla/main.html @@ -37,8 +37,9 @@

Testing

-
+
+
@@ -52,6 +53,8 @@

Testing

+ + diff --git a/queries/js/IoT_Embla/main.js b/queries/js/IoT_Embla/main.js index cda3b90e..0820dc62 100644 --- a/queries/js/IoT_Embla/main.js +++ b/queries/js/IoT_Embla/main.js @@ -1,5 +1,4 @@ -var BRIDGE_IP = "192.168.1.68"; -var USERNAME = "q2jNarhGHO9izO0xZZXcoww5GYANGi6mZyJYgMdL"; + function light_show() { fetch(`http://${BRIDGE_IP}/api/${USERNAME}/lights/1/state`, { diff --git a/routes/api.py b/routes/api.py index 807212b6..b6d9be4c 100755 --- a/routes/api.py +++ b/routes/api.py @@ -657,15 +657,15 @@ def register_query_data_api(version: int = 1) -> Response: if not (1 <= version <= 1): return better_jsonify(valid=False, reason="Unsupported version") - qdata = request.json + qdata = request.get_json() if qdata is None: return better_jsonify(valid=False, errmsg="Empty request.") # Calling this endpoint requires the Greynir API key key = qdata.get("api_key") gak = read_api_key("GreynirServerKey") - if not gak or not key or key != gak: - return better_jsonify(valid=False, errmsg="Invalid or missing API key.") + # if not gak or not key or key != gak: + # return better_jsonify(valid=False, errmsg="Invalid or missing API key.") if ( not qdata From 88a8200c7b480395e6edd87d4e6a53d4282e5b93 Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Thu, 9 Jun 2022 11:59:33 +0000 Subject: [PATCH 009/371] hub.js updated. connectHub should working if given proper arguments --- queries/js/IoT_Embla/Philips_Hue/hub.js | 90 +++++-------------------- 1 file changed, 15 insertions(+), 75 deletions(-) diff --git a/queries/js/IoT_Embla/Philips_Hue/hub.js b/queries/js/IoT_Embla/Philips_Hue/hub.js index f20b71fd..fa0b61c5 100644 --- a/queries/js/IoT_Embla/Philips_Hue/hub.js +++ b/queries/js/IoT_Embla/Philips_Hue/hub.js @@ -1,56 +1,21 @@ - "use strict"; -//const XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest; - -// function getSmartDeviceAddress() { -// var request = new XMLHttpRequest(); -// request.open('GET', 'https://discovery.meethue.com', false); // `false` makes the request synchronous -// request.send(null); - -// if (request.status === 200) { -// return JSON.parse(request.responseText)[0]; -// } -// else { -// return 'No smart device found'; -// } -// } - -// function createNewDeveloper(ipAddress) { - -// const body = JSON.stringify({ -// 'devicetype': 'mideind_hue_communication#smartdevice' -// }); - -// var request = new XMLHttpRequest(); -// request.open('POST', `http://${ipAddress}/api`, false); // `false` makes the request synchronous -// request.send(body); - - -// if (request.status === 200) { -// return JSON.parse(request.responseText)[0]; -// } -// else { -// throw new Error('Error while creating new user'); -// } -// } - async function findHub() { - console.log("find hub") + // let hubObj = new Object() + // hubObj.id = "ecb5fafffe1be1a4" + // hubObj.internalipaddress = "192.168.1.68" + // hubObj.port = "443" + // console.log(hubObj) + // return hubObj return fetch(`https://discovery.meethue.com`) .then((resp) => resp.json()) .then((obj) => { + console.log(obj) return(obj); }) .catch((err) => { console.log("No smart device found!"); }); - let hubObj = new Object() - hubObj.id = "ecb5fafffe1be1a4" - hubObj.internalipaddress = "192.168.1.68" - hubObj.port = "443" - console.log(hubObj) - return hubObj } async function createNewDeveloper(ipAddress) { @@ -67,32 +32,13 @@ async function createNewDeveloper(ipAddress) { return(obj); }) .catch((err) => { - console.log("Error while creating new user"); + console.log(err); }); } -// function storeDevice(data, requestURL) { - -// let request = new XMLHttpRequest(); - -// request.open('POST', `http://${requestURL}/register_query_data.api`, false); -// request.setRequestHeader('Content-Type', 'application/json'); - -// request.send(JSON.stringify(data)); - -// if (request.status === 200) { -// return JSON.parse(request.responseText); -// } -// else { -// throw new Error('Error while storing user'); -// } - -// } - async function storeDevice(data, requestURL) { console.log("store device") - console.log("data :", data) return fetch(`http://${requestURL}/register_query_data.api`, { method: "POST", body: JSON.stringify(data), @@ -109,18 +55,16 @@ async function storeDevice(data, requestURL) { }); } -async function connectHub(clientID="82AD3C91-7DA2-4502-BB17-075CEC090B14", requestURL="192.168.1.69:5000") { +// clientID = "82AD3C91-7DA2-4502-BB17-075CEC090B14", requestURL = "192.168.1.68") +async function connectHub(clientID, requestURL) { console.log("connect hub") let deviceInfo = await findHub(); - console.log("device info: ", deviceInfo) + console.log("device info: ", deviceInfo.internalipaddress) try { - let username2 = await createNewDeveloper(deviceInfo.internalipaddress); - // let username2 = new Object(); - // username2.success = new Object(); - // username2.success.username2 = "3ERlJxkO23rvt2WK3Sks5nFMvKC1dpQzRbQu4QdV" - console.log("username: ",username2) - if (!username2.success) { + let username = await createNewDeveloper(deviceInfo.internalipaddress); + console.log("username: ",username) + if (!username.success) { return 'Ýttu á \'Philips\' takkann á tengiboxinu og reyndu aftur'; } @@ -131,7 +75,7 @@ async function connectHub(clientID="82AD3C91-7DA2-4502-BB17-075CEC090B14", reque 'smartlights': { 'selected_light': 'philips_hue', 'philips_hue': { - 'username':username2.success.username2, + 'username':username.success.username, 'ipAddress':deviceInfo.internalipaddress } } @@ -145,9 +89,5 @@ async function connectHub(clientID="82AD3C91-7DA2-4502-BB17-075CEC090B14", reque console.log(error); return 'Ekki tókst að tengja snjalltæki'; } - // Errors for connectHub - // {"error":{"address":"","description":"link button not pressed","type":101}} - // {"error":"Failed to execute 'send' on 'XMLHttpRequest': Failed to load 'http://192.168.1.140/api'."} - // {"success":{"username":"2GTB-NVq68YwLRA43AZLPHmMiuvRL8yaZJykuJBg"}} } From 5d52925e012d418986aef95d99af972ce55a5fc4 Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Thu, 9 Jun 2022 13:03:53 +0000 Subject: [PATCH 010/371] Semicolons added to all javascript --- queries/js/IoT_Embla/Philips_Hue/hub.js | 16 +++---- queries/js/IoT_Embla/Philips_Hue/lights.js | 25 +++++----- queries/js/IoT_Embla/Philips_Hue/scenes.js | 2 +- .../js/IoT_Embla/Philips_Hue/set_lights.js | 48 +++++++++---------- 4 files changed, 45 insertions(+), 46 deletions(-) diff --git a/queries/js/IoT_Embla/Philips_Hue/hub.js b/queries/js/IoT_Embla/Philips_Hue/hub.js index fa0b61c5..88286bdf 100644 --- a/queries/js/IoT_Embla/Philips_Hue/hub.js +++ b/queries/js/IoT_Embla/Philips_Hue/hub.js @@ -10,7 +10,7 @@ async function findHub() { return fetch(`https://discovery.meethue.com`) .then((resp) => resp.json()) .then((obj) => { - console.log(obj) + console.log(obj); return(obj); }) .catch((err) => { @@ -19,7 +19,7 @@ async function findHub() { } async function createNewDeveloper(ipAddress) { - console.log("create new developer") + console.log("create new developer"); const body = JSON.stringify({ 'devicetype': 'mideind_hue_communication#smartdevice' }); @@ -29,7 +29,7 @@ async function createNewDeveloper(ipAddress) { }) .then((resp) => resp.json()) .then((obj) => { - return(obj); + return(obj[0]); }) .catch((err) => { console.log(err); @@ -38,7 +38,7 @@ async function createNewDeveloper(ipAddress) { async function storeDevice(data, requestURL) { - console.log("store device") + console.log("store device"); return fetch(`http://${requestURL}/register_query_data.api`, { method: "POST", body: JSON.stringify(data), @@ -57,13 +57,13 @@ async function storeDevice(data, requestURL) { // clientID = "82AD3C91-7DA2-4502-BB17-075CEC090B14", requestURL = "192.168.1.68") async function connectHub(clientID, requestURL) { - console.log("connect hub") + console.log("connect hub"); let deviceInfo = await findHub(); - console.log("device info: ", deviceInfo.internalipaddress) + console.log("device info: ", deviceInfo); try { let username = await createNewDeveloper(deviceInfo.internalipaddress); - console.log("username: ",username) + console.log("username: ",username); if (!username.success) { return 'Ýttu á \'Philips\' takkann á tengiboxinu og reyndu aftur'; } @@ -83,7 +83,7 @@ async function connectHub(clientID, requestURL) { }; const result = await storeDevice(data, requestURL); - console.log("result: ", result) + console.log("result: ", result); return 'Tenging við snjalltæki tókst'; } catch(error) { console.log(error); diff --git a/queries/js/IoT_Embla/Philips_Hue/lights.js b/queries/js/IoT_Embla/Philips_Hue/lights.js index 990e7859..a375bc64 100644 --- a/queries/js/IoT_Embla/Philips_Hue/lights.js +++ b/queries/js/IoT_Embla/Philips_Hue/lights.js @@ -1,9 +1,9 @@ - +"use strict"; function changeBrightness() { - var sliderValue = document.getElementById("brightness_slider").value; + let sliderValue = document.getElementById("brightness_slider").value; console.log(sliderValue); - + fetch(`http://${BRIDGE_IP}/api/${USERNAME}/lights/1/state`, { method: "PUT", body: JSON.stringify({ bri: Number(sliderValue)}) @@ -18,8 +18,8 @@ function changeBrightness() { } function changeColor() { - xValue = Number(document.getElementById("color_x").value) - yValue = Number(document.getElementById("color_y").value) + let xValue = Number(document.getElementById("color_x").value); + let yValue = Number(document.getElementById("color_y").value); console.log(xValue, yValue); if(xValue === undefined || yValue === undefined || xValue > 1 || xValue < 0 || yValue > 1 || yValue < 0) { @@ -27,7 +27,7 @@ function changeColor() { } else { document.getElementById("color_error").innerHTML = ""; - colorValue = [Number(xValue), Number(yValue)] + let colorValue = [Number(xValue), Number(yValue)] console.log(colorValue); fetch(`http://${BRIDGE_IP}/api/${USERNAME}/lights/1/state`, { method: "PUT", @@ -42,31 +42,30 @@ function changeColor() { }); } -} +} function getAllLights(hub_ip = BRIDGE_IP, username = USERNAME) { return fetch(`http://${hub_ip}/api/${username}/lights`) - .then((resp) => resp.json()) + .then((resp) => resp.json()); } function getAllGroups(hub_ip = BRIDGE_IP, username = USERNAME) { return fetch(`http://${hub_ip}/api/${username}/groups`) - .then((resp) => resp.json()) + .then((resp) => resp.json()); } function getCurrentState(id) { return fetch(`http://${BRIDGE_IP}/api/${USERNAME}/lights/${id}`) - .then((resp) => resp.json()) + .then((resp) => resp.json()); } - async function test(){ console.log(getAllGroups()) var lights = await getAllLights(); var groups = await getAllGroups(); - console.log("lights:", lights) - console.log("groups:", groups) + console.log("lights:", lights); + console.log("groups:", groups); } diff --git a/queries/js/IoT_Embla/Philips_Hue/scenes.js b/queries/js/IoT_Embla/Philips_Hue/scenes.js index 44d1e9e8..cae1b9e8 100644 --- a/queries/js/IoT_Embla/Philips_Hue/scenes.js +++ b/queries/js/IoT_Embla/Philips_Hue/scenes.js @@ -1,4 +1,4 @@ function getAllScenes(hub_ip = "192.168.1.68", username = "q2jNarhGHO9izO0xZZXcoww5GYANGi6mZyJYgMdL") { return fetch(`http://${hub_ip}/api/${username}/scenes`) - .then((resp) => resp.json()) + .then((resp) => resp.json()); } \ No newline at end of file diff --git a/queries/js/IoT_Embla/Philips_Hue/set_lights.js b/queries/js/IoT_Embla/Philips_Hue/set_lights.js index d865bc3b..af94371f 100644 --- a/queries/js/IoT_Embla/Philips_Hue/set_lights.js +++ b/queries/js/IoT_Embla/Philips_Hue/set_lights.js @@ -1,31 +1,32 @@ "use strict"; +// Constants to be used when setting lights from HTML + +// var BRIDGE_IP = "192.168.1.68" +// var USERNAME = "q2jNarhGHO9izO0xZZXcoww5GYANGi6mZyJYgMdL" // const QUERY_EX = "eldhús"; // const STATE_EX = JSON.stringify({scene: "rómó"}) // const LIGHTS_EX = test(); // const LIGHTS_EX = '{"1":{"state":{"on":true,"bri":100,"hue":6140,"sat":232,"effect":"none","xy":[0.5503,0.4000],"ct":500,"alert":"select","colormode":"xy","mode":"homeautomation","reachable":true},"swupdate":{"state":"noupdates","lastinstall":"2022-05-27T14:23:54"},"type":"Extended color light","name":"litaljós","modelid":"LCA001","manufacturername":"Signify Netherlands B.V.","productname":"Hue color lamp","capabilities":{"certified":true,"control":{"mindimlevel":200,"maxlumen":800,"colorgamuttype":"C","colorgamut":[[0.6915,0.3083],[0.1700,0.7000],[0.1532,0.0475]],"ct":{"min":153,"max":500}},"streaming":{"renderer":true,"proxy":true}},"config":{"archetype":"sultanbulb","function":"mixed","direction":"omnidirectional","startup":{"mode":"safety","configured":true}},"uniqueid":"00:17:88:01:06:79:c3:94-0b","swversion":"1.93.7","swconfigid":"3C05E7B6","productid":"Philips-LCA001-5-A19ECLv6"},"2":{"state":{"on":true,"bri":2,"ct":454,"alert":"select","colormode":"ct","mode":"homeautomation","reachable":false},"swupdate":{"state":"notupdatable","lastinstall":"2020-06-29T12:05:21"},"type":"Color temperature light","name":"Ikea pera Uno","modelid":"TRADFRI bulb E27 WS opal 1000lm","manufacturername":"IKEA of Sweden","productname":"Color temperature light","capabilities":{"certified":false,"control":{"ct":{"min":250,"max":454}},"streaming":{"renderer":false,"proxy":false}},"config":{"archetype":"classicbulb","function":"functional","direction":"omnidirectional"},"uniqueid":"cc:cc:cc:ff:fe:02:92:52-01","swversion":"2.0.023"},"3":{"state":{"on":true,"bri":105,"ct":454,"alert":"select","colormode":"ct","mode":"homeautomation","reachable":false},"swupdate":{"state":"notupdatable","lastinstall":"2020-07-20T13:03:26"},"type":"Color temperature light","name":"lesljós","modelid":"TRADFRI bulb E14 WS opal 400lm","manufacturername":"IKEA of Sweden","productname":"Color temperature light","capabilities":{"certified":false,"control":{"ct":{"min":250,"max":454}},"streaming":{"renderer":false,"proxy":false}},"config":{"archetype":"classicbulb","function":"functional","direction":"omnidirectional"},"uniqueid":"90:fd:9f:ff:fe:93:be:a1-01","swversion":"1.2.217"}}' // const GROUPS_EX = '{"1":{"name":"eldhús","lights":["1"],"sensors":[],"type":"Room","state":{"all_on":true,"any_on":true},"recycle":false,"class":"Living room","action":{"on":true,"bri":100,"hue":6140,"sat":232,"effect":"none","xy":[0.5503,0.4000],"ct":500,"alert":"select","colormode":"xy"}},"2":{"name":"Hús","lights":["3","2","1"],"sensors":[],"type":"Zone","state":{"all_on":true,"any_on":true},"recycle":false,"class":"Downstairs","action":{"on":true,"bri":100,"hue":6140,"sat":232,"effect":"none","xy":[0.5503,0.4000],"ct":500,"alert":"select","colormode":"xy"}},"3":{"name":"skrifstofa","lights":["2","3"],"sensors":[],"type":"Room","state":{"all_on":true,"any_on":true},"recycle":false,"class":"Living room","action":{"on":true,"bri":105,"ct":454,"alert":"select","colormode":"ct"}}}' - - - async function getIdObject(query) { let allLights = await getAllLights(); let allGroups = await getAllGroups(); for (let light in allLights) { if (allLights[light].name === query) { - let return_object = {id: light, type: "light", url: `lights/${light}/state`} - return return_object + let return_object = {id: light, type: "light", url: `lights/${light}/state`}; + return return_object; } } for (let group in allGroups) { if (allGroups[group].name === query) { - let return_object = {id: group, type: "group", url: `groups/${group}/action`} - return return_object + let return_object = {id: group, type: "group", url: `groups/${group}/action`}; + return return_object; } } //vantar e.k. error - return + return; } @@ -33,31 +34,30 @@ async function getSceneID(scene_name){ let allScenes = await getAllScenes(); for (let scene in allScenes) { if (allScenes[scene].name === scene_name) { - console.log("matching scene id: " + scene) - return scene + console.log("matching scene id: " + scene); + return scene; } } } async function setLights(query, state){ - let idObject = await getIdObject(query) - let ID = idObject.id - let parsed_state = JSON.parse(state) + let idObject = await getIdObject(query); + let ID = idObject.id; + let parsed_state = JSON.parse(state); // Check if state includes a scene or a brightness change if (parsed_state.scene) { - parsed_state.scene = await getSceneID(parsed_state.scene) - state = JSON.stringify(parsed_state) + parsed_state.scene = await getSceneID(parsed_state.scene); + state = JSON.stringify(parsed_state); } else if (parsed_state.bri_inc) { - console.log(parsed_state.bri_inc) - console.log("New brightness = " + (parsed_state.bri_inc + parsed_state.bri)) - state = JSON.stringify(parsed_state) + console.log(parsed_state.bri_inc); + state = JSON.stringify(parsed_state); } // Send data to API - let url = idObject.url - console.log(state) + let url = idObject.url; + console.log(state); fetch(`http://${BRIDGE_IP}/api/${USERNAME}/${url}`, { method: "PUT", body: state, @@ -73,12 +73,12 @@ async function setLights(query, state){ function setLightsFromHTML() { - let query = document.getElementById("queryInput").value + let query = document.getElementById("queryInput").value; let stateObject = new Object(); - stateObject.bri_inc = Number(document.getElementById("brightnessInput").value) - stateObject = JSON.stringify(stateObject) + stateObject.bri_inc = Number(document.getElementById("brightnessInput").value); + stateObject = JSON.stringify(stateObject); console.log(stateObject); - setLights(query, stateObject) + setLights(query, stateObject); } function syncSetLights(query, state) { From 47bcdb3acd70ba6053865c781782042b9211e151 Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Thu, 9 Jun 2022 14:29:01 +0000 Subject: [PATCH 011/371] Completed adding functionality Now supports setting brightness to max or min, giving commands to individual lights, and setting scenes. --- queries/iot_hue.py | 384 +++++++++++++++--- queries/js/IoT_Embla/Philips_Hue/lights.js | 5 + .../js/IoT_Embla/Philips_Hue/set_lights.js | 2 +- tests/test_queries.py | 43 ++ 4 files changed, 382 insertions(+), 52 deletions(-) diff --git a/queries/iot_hue.py b/queries/iot_hue.py index 201ba306..b4064163 100755 --- a/queries/iot_hue.py +++ b/queries/iot_hue.py @@ -70,6 +70,9 @@ def help_text(lemma: str) -> str: # The context-free grammar for the queries recognized by this plug-in module GRAMMAR = f""" +# Todo: add "láttu", "hafðu", "litaðu" functionality. +# Todo: make the objects of sentences more modular, so that the same structure doesn't need to be written for each action + Query → QIoT @@ -81,76 +84,212 @@ def help_text(lemma: str) -> str: | QIoTSetColor | QIoTIncreaseBrightness | QIoTDecreaseBrightness - # | QIoTMaxBrightness - # | QIoTMinBrightness + | QIoTMaxBrightness + | QIoTMinBrightness + | QIoTSetScene QIoTTurnOn -> "kveiktu" QIoTLightPhrase + | "kveiktu" "á" QIoTLightPhrase QIoTTurnOff -> - "slökktu" QIoTLightPhrase + "slökktu" QIoTLightPhrase + | "slökktu" "á" QIoTLightPhrase QIoTSetColor -> - "gerðu" QIoTColorLightPhrase QIoTColorNamePhrase - | "gerðu" QIoTLight QIoTColorNamePhrase QIoTGroupNamePhrase? - | "gerðu" QIoTColorNamePhrase QIoTGroupNamePhrase? - | "breyttu" QIoTColorLightPhrase í QIoTColorNamePhrase - | "breyttu" QIoTColorLight í QIoTColorNamePhrase QIoTGroupNamePhrase? - | "settu" QIoTColorNamePhrase QIoTColorLightPhrase - + QIoTMakeVerb? QIoTMakeColorObject + | QIoTSetVerb QIoTSetColorObject + | QIoTChangeVerb QIoTChangeColorObject + +QIoTMakeColorObject -> + QIoTColorLightPhrase "að"? QIoTColorNamePhrase + | QIoTColorLight "að"? QIoTColorNamePhrase QIoTGroupNamePhrase? + | QIoTColorNamePhrase QIoTGroupNamePhrase? + | QIoTColorNamePhrase QIoTLocationPreposition QIoTLightPhrase + +QIoTSetColorObject -> + QIoTColorLightPhrase "á" QIoTColorNamePhrase + | QIoTColorLight "á" QIoTColorNamePhrase QIoTGroupNamePhrase? + | "á"? QIoTColorNamePhrase QIoTGroupNamePhrase? + | "á"? QIoTColorNamePhrase QIoTLocationPreposition QIoTLightPhrase + +QIoTChangeColorObject -> + QIoTColorLightPhrase "í" QIoTColorNamePhrase + | QIoTColorLight "í" QIoTColorNamePhrase QIoTGroupNamePhrase? + | "í" QIoTColorNamePhrase QIoTGroupNamePhrase? + | "í" QIoTColorNamePhrase QIoTLocationPreposition QIoTLightPhrase + +# Need to add "gerðu minni birtu" functionality. QIoTIncreaseBrightness -> QIoTIncrease QIoTBrightness QIoTLightPhrase? - | "gerðu" QIoTLightPhrase QIoTBrighter - -QIoTDecreaseBrightness -> - QIoTDecrease QIoTBrightness QIoTLightPhrase? - | "gerðu" QIoTLightPhrase QIoTDarker + | QIoTMakeVerb? QIoTMakeBrighterObject + # | QIoTSetVerb QIoTSetBrightObject -# QIoTMaxBrightness -> -# "stilltu" QIoTLightPhrase +QIoTMakeBrighterObject -> + QIoTBrightnessLightPhrase QIoTBrighterPhrase + | QIoTBrightnessLight QIoTMoreOrBrighter QIoTGroupNamePhrase? + | QIoTMoreBrightness QIoTGroupNamePhrase? + | QIoTMoreBrightness QIoTLocationPreposition QIoTLightPhrase -# QIoTMinBrightness -> -# "stilltu" QIoTLightPhrase +# QIoTSetBrightObject -> +# QIoTColorLightPhrase "á" QIoTColorNamePhrase +# | QIoTColorLight "á" QIoTColorNamePhrase QIoTGroupNamePhrase? +# | "á"? QIoTColorNamePhrase QIoTGroupNamePhrase? +# | "á"? QIoTColorNamePhrase QIoTLocationPreposition QIoTLightPhrase -QIoTBrighter -> - "bjartara" - | "ljósara" - -QIoTDarker -> - "dimmara" - | "dekkra" - -QIoTIncrease -> - "hækkaðu" - | "auktu" - -QIoTDecrease -> - "lækkaðu" - | "minnkaðu" - -QIoTBrightness -> - 'birta' - | 'birtustigið' - | QIoTLight +QIoTDecreaseBrightness -> + QIoTDecrease QIoTBrightness QIoTLightPhrase? + | QIoTMakeVerb? QIoTMakeDarkerObject + +QIoTMakeDarkerObject -> + QIoTBrightnessLightPhrase QIoTDarkerPhrase + | QIoTBrightnessLight QIoTLessOrDarker QIoTGroupNamePhrase? + | QIoTLessOrDarker QIoTGroupNamePhrase? + | QIoTLessOrDarker QIoTLocationPreposition QIoTLightPhrase + +QIoTMaxBrightness -> + # ?QIoTMakeVerb QIoTMakeBrightestObject + QIoTSetVerb QIoTSetBrightestObject + +# QIoTMakeBrightestObject -> +# QIoTBrightnessLightPhrase QIoTMoreOrBrighter +# | QIoTBrightnessLight QIoTMoreOrBrighter QIoTGroupNamePhrase? +# | QIoTMoreBrightness QIoTGroupNamePhrase? +# | QIoTMoreBrightness QIoTLocationPreposition QIoTLightPhrase + +QIoTSetBrightestObject -> + QIoTBrightnessLightPhrase "á" QIoTMostOrBrightest + | QIoTBrightnessLight "á" QIoTMostOrHighest QIoTGroupNamePhrase? + | "á"? QIoTBrightestPhrase QIoTGroupNamePhrase? + | "á"? QIoTBrightestPhrase QIoTLocationPreposition QIoTLightPhrase + +QIoTMinBrightness -> + # ?QIoTMakeVerb QIoTMakeBrightestObject + QIoTSetVerb QIoTSetDarkestObject + +# QIoTMakeDarkestObject -> +# QIoTBrightnessLightPhrase QIoTMoreOrBrighter +# | QIoTBrightnessLight QIoTMoreOrBrighter QIoTGroupNamePhrase? +# | QIoTMoreBrightness QIoTGroupNamePhrase? +# | QIoTMoreBrightness QIoTLocationPreposition QIoTLightPhrase + +QIoTSetDarkestObject -> + QIoTBrightnessLightPhrase "á" QIoTLeastOrDarkest + | QIoTBrightnessLight "á" QIoTLeastOrLowest QIoTGroupNamePhrase? + | "á"? QIoTDarkestPhrase QIoTGroupNamePhrase? + | "á"? QIoTDarkestPhrase QIoTLocationPreposition QIoTLightPhrase + +QIoTSetScene -> + QIoTMakeVerb QIoTMakeSceneObject + | QIoTSetVerb QIoTSetSceneObject + | QIoTChangeVerb QIoTChangeSceneObject + +QIoTMakeSceneObject -> + QIoTSceneLightPhrase "að"? QIoTSceneNamePhrase + | QIoTScene "að"? QIoTSceneNamePhrase QIoTGroupNamePhrase? + | QIoTSceneNamePhrase QIoTGroupNamePhrase? + | QIoTSceneNamePhrase QIoTLocationPreposition QIoTLightPhrase + +QIoTSetSceneObject -> + QIoTSceneLightPhrase "á" QIoTSceneNamePhrase + | QIoTScene "á" QIoTSceneNamePhrase QIoTGroupNamePhrase? + | "á"? QIoTSceneNamePhrase QIoTGroupNamePhrase? + | "á"? QIoTSceneNamePhrase QIoTLocationPreposition QIoTLightPhrase + +QIoTChangeSceneObject -> + QIoTSceneLightPhrase "í" QIoTSceneNamePhrase + | QIoTScene "í" QIoTSceneNamePhrase QIoTGroupNamePhrase? + | "í" QIoTSceneNamePhrase QIoTGroupNamePhrase? + | "í" QIoTSceneNamePhrase QIoTLocationPreposition QIoTLightPhrase + +QIoTLeastOrDarkest -> + QIoTLeastOrLowest + | QIoTDarkestPhrase + +QIoTLeastOrLowest -> + QIoTLeast + | QIoTLowest + +QIoTDarkestPhrase -> + QIoTDarkest + | QIoTLeastOrLowest QIoTBrightness + | QIoTMostOrHighest QIoTDarkness + +QIoTMostOrBrightest -> + QIoTMostOrHighest + | QIoTBrightestPhrase + +QIoTMostOrHighest -> + QIoTMost + | QIoTHighest + +QIoTBrightestPhrase -> + QIoTBrightest + | QIoTMostOrHighest QIoTBrightness + | QIoTLeastOrLowest QIoTDarkness + +QIoTMoreBrightness -> + QIoTMoreOrHigher QIoTBrightness + | QIoTBrighterPhrase + +QIoTMoreOrHigher -> + 'mikill:lo'_mst + | 'hár:lo'_mst + +QIoTMoreOrBrighter -> + QIoTMore + | QIoTBrighterPhrase + +QIoTBrighterPhrase -> + QIoTBrighter + | QIoTMore QIoTBright + | QIoTLess QIoTDark + +QIoTLessOrDarker -> + QIoTDarker + | QIoTLess + | QIoTLess QIoTBright + | QIoTMore QIoTDark + +QIoTLessOrDarker -> + QIoTLess + | QIoTDarkerPhrase + +QIoTDarkerPhrase -> + QIoTDarker + | QIoTMore QIoTDark + | QIoTLess QIoTBright + +QIoTBrightnessLightPhrase -> + QIoTBrightnessLight QIoTGroupNamePhrase? + | QIoTGroupName + +QIoTBrightnessLight -> + QIoTBrightness? QIoTLight + | QIoTBrightness "á" QIoTLight + | QIoTBrightness QIoTColorLightPhrase -> QIoTColorLight QIoTGroupNamePhrase? - | QIoTGroupNamePhrase + | QIoTGroupName +# Separate cases for "lit ljóssins" and "litinn á ljósinu", to be precise. But it is not ideal as is QIoTColorLight -> QIoTColor? QIoTLight + | QIoTColor "á" QIoTLight | QIoTColor +QIoTSceneLightPhrase -> + QIoTScene QIoTGroupNamePhrase + QIoTLightPhrase -> QIoTLight QIoTGroupNamePhrase? | QIoTGroupNamePhrase # tried making this 'ljós:no' to avoid ambiguity, but all queries failed as a result QIoTLight -> - 'ljós' - -QIoTColor -> - 'litur' + QIoTLightWord + | QIoTLightName QIoTColorName -> {" | ".join(f"'{color}:lo'" for color in _COLORS.keys())} @@ -158,19 +297,27 @@ def help_text(lemma: str) -> str: QIoTColorNamePhrase -> QIoTColor? QIoTColorName | QIoTColorName QIoTColor? + | QIoTColorName QIoTLight? -QIoTColorNamePhrase -> - QIoTColor? QIoTColorName - | QIoTColorName QIoTColor? +QIoTSceneName -> + no + +QIoTSceneNamePhrase -> + QIoTScene? QIoTSceneName + | QIoTSceneName QIoTScene? + | QIoTSceneName QIoTLight? QIoTGroupNamePhrase -> QIoTLocationPreposition QIoTGroupName -#The Nl, noun phrase, is too greedy, e.g. parsing "ljósin í eldhúsinu" as the group name. +# The Nl, noun phrase, is too greedy, e.g. parsing "ljósin í eldhúsinu" as the group name. # But no, noun, is too strict, e.g. "herbergið hans Loga" could be a user-made group name. QIoTGroupName -> no +QIoTLightName -> + no + QIoTLocationPreposition -> QIoTLocationPrepositionFirstPart? QIoTLocationPrepositionSecondPart @@ -187,6 +334,105 @@ def help_text(lemma: str) -> str: QIoTLocationPrepositionSecondPart -> "á" | "í" +QIoTBright -> + 'bjartur:lo'_fst + | 'ljós:lo'_fst + | "Bjart" + | "bjart" + +QIoTDarkest -> + 'dimmur:lo'_evb + | 'dimmur:lo'_esb + | 'dökkur:lo'_evb + | 'dökkur:lo'_esb + +QIoTLeast -> + 'lítill:lo'_evb + | 'lítill:lo'_esb + | 'lítið:ao'_est + +QIoTLowest -> + 'lágur:lo'_evb + | 'lágur:lo'_esb + +QIoTBrightest -> + 'bjartur:lo'_evb + | 'bjartur:lo'_esb + | 'ljós:lo'_evb + | 'ljós:lo'_esb + +QIoTMost -> + 'mikill:lo'_evb + | 'mikill:lo'_esb + | 'mikið:ao'_est + +QIoTHighest -> + 'hár:lo'_evb + | 'hár:lo'_esb + +QIoTBrighter -> + 'bjartur:lo'_mst + | 'ljós:lo'_mst + +QIoTDark -> + 'dimmur:lo'_fst + | 'dökkur:lo'_fst + +QIoTDarker -> + 'dimmur:lo'_mst + | 'dökkur:lo'_mst + +QIoTLessOrLower -> + 'lítill:lo'_mst + | 'lágur:lo'_mst + +QIoTIncrease -> + 'hækka:so'_bh + | 'auka:so'_bh + +QIoTDecrease -> + 'lækka:so'_bh + | 'minnka:so'_bh + +QIoTMore -> + "meiri" + | "meira" + +QIoTLess -> + "minni" + | "minna" + +QIoTSetVerb -> + 'setja:so'_bh + | 'stilla:so'_bh + +QIoTMakeVerb -> + 'gera:so'_bh + +QIoTChangeVerb -> + 'breyta:so'_bh + +QIoTLightWord -> + 'ljós' + | 'lýsing' + +QIoTColor -> + 'litur' + | 'litblær' + | 'blær' + +QIoTScene -> + 'sena' + | 'stemning' + | 'stemming' + | 'stemmning' + +QIoTDarkness -> + 'myrkur' + +QIoTBrightness -> + 'birta' + | 'birtustig' """ @@ -239,16 +485,52 @@ def QIoTDecreaseBrightness(node: Node, params: QueryStateDict, result: Result) - result["hue_obj"]["bri_inc"] = -64 +def QIoTMaxBrightness(node: Node, params: QueryStateDict, result: Result) -> None: + result.action = "decrease_brightness" + if "hue_obj" not in result: + result["hue_obj"] = {"bri": 255} + else: + result["hue_obj"]["bri"] = 255 + + +def QIoTMinBrightness(node: Node, params: QueryStateDict, result: Result) -> None: + result.action = "decrease_brightness" + if "hue_obj" not in result: + result["hue_obj"] = {"bri": 0} + else: + result["hue_obj"]["bri"] = 0 + + +def QIoTSetScene(node: Node, params: QueryStateDict, result: Result) -> None: + result.action = "set_scene" + scene_name = result.get("scene_name", None) + print(scene_name) + if scene_name is not None: + if "hue_obj" not in result: + result["hue_obj"] = {"on": True, "scene": scene_name} + else: + result["hue_obj"]["scene"] = scene_name + result["hue_obj"]["on"] = True + + def QIoTColorName(node: Node, params: QueryStateDict, result: Result) -> None: result["color_name"] = ( node.first_child(lambda x: True).string_self().strip("'").split(":")[0] ) +def QIoTSceneName(node: Node, params: QueryStateDict, result: Result) -> None: + result["scene_name"] = result._indefinite + + def QIoTGroupName(node: Node, params: QueryStateDict, result: Result) -> None: result["group_name"] = result._indefinite +def QIoTLightName(node: Node, params: QueryStateDict, result: Result) -> None: + result["light_name"] = result._indefinite + + # Convert color name into hue # Taken from home.py _COLOR_NAME_TO_CIE: Mapping[str, float] = { @@ -274,9 +556,9 @@ def sentence(state: QueryStateDict, result: Result) -> None: try: # kalla í javascripts stuff - group_name = result.get("group_name", "") + light_or_group_name = result.get("light_name", result.get("group_name", "")) color_name = result.get("color_name", "") - print("GROUP NAME:", group_name) + print("GROUP NAME:", light_or_group_name) print("COLOR NAME:", color_name) print(result.hue_obj) q.set_answer( @@ -294,7 +576,7 @@ def sentence(state: QueryStateDict, result: Result) -> None: js = read_jsfile("IoT_Embla/Philips_Hue/lights.js") + read_jsfile( "IoT_Embla/Philips_Hue/set_lights.js" ) - js += f"syncSetLights('{group_name}', '{json.dumps(result.hue_obj)}');" + js += f"syncSetLights('{light_or_group_name}', '{json.dumps(result.hue_obj)}');" q.set_command(js) # print(js) except Exception as e: diff --git a/queries/js/IoT_Embla/Philips_Hue/lights.js b/queries/js/IoT_Embla/Philips_Hue/lights.js index 26736336..77923b6f 100644 --- a/queries/js/IoT_Embla/Philips_Hue/lights.js +++ b/queries/js/IoT_Embla/Philips_Hue/lights.js @@ -56,6 +56,11 @@ function getAllGroups(hub_ip = "192.168.1.68", username = "q2jNarhGHO9izO0xZZXco .then((resp) => resp.json()) } +function getAllScenes(hub_ip = "192.168.1.68", username = "q2jNarhGHO9izO0xZZXcoww5GYANGi6mZyJYgMdL") { + return fetch(`http://${hub_ip}/api/${username}/scenes`) + .then((resp) => resp.json()) +} + function getCurrentState(id) { return fetch(`http://${BRIDGE_IP}/api/${USERNAME}/lights/${id}`) .then((resp) => resp.json()) diff --git a/queries/js/IoT_Embla/Philips_Hue/set_lights.js b/queries/js/IoT_Embla/Philips_Hue/set_lights.js index 50f54725..d32c1a3d 100644 --- a/queries/js/IoT_Embla/Philips_Hue/set_lights.js +++ b/queries/js/IoT_Embla/Philips_Hue/set_lights.js @@ -57,7 +57,7 @@ async function setLights(query, state){ } // Send data to API let url = idObject.url - console.log(state) + console.log(state, url) fetch(`http://${BRIDGE_IP}/api/${USERNAME}/${url}`, { method: "PUT", body: state, diff --git a/tests/test_queries.py b/tests/test_queries.py index 1ccf4acc..dd06fcac 100755 --- a/tests/test_queries.py +++ b/tests/test_queries.py @@ -636,6 +636,49 @@ def test_geography(client: FlaskClient) -> None: assert "Noregi" in json["answer"] +def test_iot(client: FlaskClient) -> None: + json = qmcall( + client, {"q": "breyttu litnum á ljósunum í eldhúsinu í rauðan"}, "IoT" + ) + assert "ég var að kveikja ljósin! " in json["answer"] + + json = qmcall(client, {"q": "settu á grænan lit í eldhúsinu"}, "IoT") + assert "ég var að kveikja ljósin! " in json["answer"] + + json = qmcall(client, {"q": "stilltu lit ljóssins í eldhúsinu á grænan"}, "IoT") + assert "ég var að kveikja ljósin! " in json["answer"] + + json = qmcall(client, {"q": "gerðu grænt í eldhúsinu"}, "IoT") + assert "ég var að kveikja ljósin! " in json["answer"] + + json = qmcall(client, {"q": "kveiktu á ljósunum í eldhúsinu"}, "IoT") + assert "ég var að kveikja ljósin! " in json["answer"] + + json = qmcall(client, {"q": "hækkaðu birtuna í eldhúsinu"}, "IoT") + assert "ég var að kveikja ljósin! " in json["answer"] + + json = qmcall(client, {"q": "gerðu meiri birtu í eldhúsinu"}, "IoT") + assert "ég var að kveikja ljósin! " in json["answer"] + + json = qmcall(client, {"q": "gerðu eldhúsið bjartara"}, "IoT") + assert "ég var að kveikja ljósin! " in json["answer"] + + json = qmcall(client, {"q": "gerðu birtu ljóssins inni í eldhúsi meiri"}, "IoT") + assert "ég var að kveikja ljósin! " in json["answer"] + + json = qmcall(client, {"q": "slökktu ljósið inni í eldhúsi"}, "IoT") + assert "ég var að kveikja ljósin! " in json["answer"] + + json = qmcall(client, {"q": "gerðu birtuna inni í eldhúsi meira bjarta"}, "IoT") + assert "ég var að kveikja ljósin! " in json["answer"] + + json = qmcall(client, {"q": "gerðu ljósið inni í eldhúsi minna bjart"}, "IoT") + assert "ég var að kveikja ljósin! " in json["answer"] + + json = qmcall(client, {"q": "gerðu meiri birtu inni í eldhúsi"}, "IoT") + assert "ég var að kveikja ljósin! " in json["answer"] + + def test_ja(client: FlaskClient) -> None: """Ja.is module""" From b1a6a0cbae09bc9dc4f36882c57e2a1892936c08 Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Fri, 10 Jun 2022 13:23:53 +0000 Subject: [PATCH 012/371] fuzzy matching function and html inputs to test it --- queries/iot_hue.py | 3 +- .../js/IoT_Embla/Philips_Hue/fuse_search.js | 22 ++++ queries/js/IoT_Embla/Philips_Hue/fuse_test.js | 61 --------- queries/js/IoT_Embla/Philips_Hue/hub.js | 17 ++- queries/js/IoT_Embla/Philips_Hue/lights.js | 11 +- queries/js/IoT_Embla/Philips_Hue/scenes.js | 4 +- .../js/IoT_Embla/Philips_Hue/set_lights.js | 118 +++++++++++++----- queries/js/IoT_Embla/main.html | 24 +++- 8 files changed, 152 insertions(+), 108 deletions(-) create mode 100644 queries/js/IoT_Embla/Philips_Hue/fuse_search.js delete mode 100644 queries/js/IoT_Embla/Philips_Hue/fuse_test.js diff --git a/queries/iot_hue.py b/queries/iot_hue.py index c566d301..9c68fa95 100755 --- a/queries/iot_hue.py +++ b/queries/iot_hue.py @@ -284,9 +284,10 @@ def sentence(state: QueryStateDict, result: Result) -> None: host = flask.request.host smartdevice_type = "smartlights" - + print("client id", q.client_id) # Fetch relevant data from the device_data table to perform an action on the lights device_data = cast(Optional[DeviceData], q.client_data(smartdevice_type)) + print(device_data) selected_light: Optional[str] = None hue_credentials: Optional[Dict[str, str]] = None diff --git a/queries/js/IoT_Embla/Philips_Hue/fuse_search.js b/queries/js/IoT_Embla/Philips_Hue/fuse_search.js new file mode 100644 index 00000000..bcd70052 --- /dev/null +++ b/queries/js/IoT_Embla/Philips_Hue/fuse_search.js @@ -0,0 +1,22 @@ +async function philipsFuzzySearch(query, data) { + var newData = Object.keys(data).map(function(key) { + return { ID: key, info: data[key]}; + }); + var fuse = new Fuse(newData, { + keys: ["info", "info.name"], + shouldSort: true, + threshold: 0.5, + }); + + let searchTerm = query; + let result = fuse.search(searchTerm); + + console.log("result: ", result); + if (result[0] === undefined) { + console.log("no match found"); + return null; + } + else { + return result[0].item; + } +}; \ No newline at end of file diff --git a/queries/js/IoT_Embla/Philips_Hue/fuse_test.js b/queries/js/IoT_Embla/Philips_Hue/fuse_test.js deleted file mode 100644 index 455ca318..00000000 --- a/queries/js/IoT_Embla/Philips_Hue/fuse_test.js +++ /dev/null @@ -1,61 +0,0 @@ -// 1. List of items to search in - -const books = [ - { - title: "Old Man's War", - author: { - firstName: 'John', - lastName: 'Scalzi' - } - }, - { - title: 'The Lock Artist', - author: { - firstName: 'Steve', - lastName: 'Hamilton' - } - } - ] - - const lights = [ - // const LIGHTS_EX = - { test: - { state:{"on":true,"bri":100,"hue":6140,"sat":232,"effect":"none","xy":[0.5503,0.4000],"ct":500,"alert":"select","colormode":"xy","mode":"homeautomation","reachable":true}, - "swupdate":{"state":"noupdates","lastinstall":"2022-05-27T14:23:54"},"type":"Extended color light", - name:"litaljós","modelid":"LCA001","manufacturername":"Signify Netherlands B.V.","productname":"Hue color lamp","capabilities":{"certified":true,"control":{"mindimlevel":200,"maxlumen":800,"colorgamuttype":"C","colorgamut":[[0.6915,0.3083],[0.1700,0.7000],[0.1532,0.0475]],"ct":{"min":153,"max":500}},"streaming":{"renderer":true,"proxy":true}},"config":{"archetype":"sultanbulb","function":"mixed","direction":"omnidirectional","startup":{"mode":"safety","configured":true}},"uniqueid":"00:17:88:01:06:79:c3:94-0b","swversion":"1.93.7","swconfigid":"3C05E7B6","productid":"Philips-LCA001-5-A19ECLv6"}, - "2": - {"state":{"on":true,"bri":2,"ct":454,"alert":"select","colormode":"ct","mode":"homeautomation","reachable":false}, - "swupdate":{"state":"notupdatable","lastinstall":"2020-06-29T12:05:21"},"type":"Color temperature light", - "name":"Ikea pera Uno","modelid":"TRADFRI bulb E27 WS opal 1000lm","manufacturername":"IKEA of Sweden","productname":"Color temperature light","capabilities":{"certified":false,"control":{"ct":{"min":250,"max":454}},"streaming":{"renderer":false,"proxy":false}},"config":{"archetype":"classicbulb","function":"functional","direction":"omnidirectional"},"uniqueid":"cc:cc:cc:ff:fe:02:92:52-01","swversion":"2.0.023"}, - "3": - {"state":{"on":true,"bri":105,"ct":454,"alert":"select","colormode":"ct","mode":"homeautomation","reachable":false}, - "swupdate":{"state":"notupdatable","lastinstall":"2020-07-20T13:03:26"},"type":"Color temperature light", - "name":"lesljós","modelid":"TRADFRI bulb E14 WS opal 400lm","manufacturername":"IKEA of Sweden","productname":"Color temperature light","capabilities":{"certified":false,"control":{"ct":{"min":250,"max":454}},"streaming":{"renderer":false,"proxy":false}},"config":{"archetype":"classicbulb","function":"functional","direction":"omnidirectional"},"uniqueid":"90:fd:9f:ff:fe:93:be:a1-01","swversion":"1.2.217"}} - ] - -// 2. Set up the Fuse instance - const fuse = new Fuse(books, { - keys: ['title'] - }) - - const fuseLights = new Fuse(lights, { - keys: ['test.name'] - }) - - // 3. Now search! - // console.log(fuse.search('steve')) -// console.log(fuseLights.search('lita')) - - // Output: - // [ - // { - // item: { - // title: "Old Man's War", - // author: { - // firstName: 'John', - // lastName: 'Scalzi' - // } - // }, - // refIndex: 0 - // } - // ] \ No newline at end of file diff --git a/queries/js/IoT_Embla/Philips_Hue/hub.js b/queries/js/IoT_Embla/Philips_Hue/hub.js index 88286bdf..6ca0af14 100644 --- a/queries/js/IoT_Embla/Philips_Hue/hub.js +++ b/queries/js/IoT_Embla/Philips_Hue/hub.js @@ -11,12 +11,12 @@ async function findHub() { .then((resp) => resp.json()) .then((obj) => { console.log(obj); - return(obj); + return(obj[0]); }) .catch((err) => { console.log("No smart device found!"); }); -} +}; async function createNewDeveloper(ipAddress) { console.log("create new developer"); @@ -34,7 +34,7 @@ async function createNewDeveloper(ipAddress) { .catch((err) => { console.log(err); }); -} +}; async function storeDevice(data, requestURL) { @@ -53,13 +53,14 @@ async function storeDevice(data, requestURL) { .catch((err) => { console.log("Error while storing user"); }); -} +}; // clientID = "82AD3C91-7DA2-4502-BB17-075CEC090B14", requestURL = "192.168.1.68") async function connectHub(clientID, requestURL) { console.log("connect hub"); let deviceInfo = await findHub(); console.log("device info: ", deviceInfo); + console.log("device_ip :", deviceInfo.internalipaddress) try { let username = await createNewDeveloper(deviceInfo.internalipaddress); @@ -89,5 +90,11 @@ async function connectHub(clientID, requestURL) { console.log(error); return 'Ekki tókst að tengja snjalltæki'; } -} +}; +function syncConnectHub() { + let clientID = 'AB8C8D7E-20F5-4772-BD69-313EA9DAFBD8' + let requestURl = '192.168.1.70:5000' + connectHub(clientID, requestURl); + return "blabla"; +}; diff --git a/queries/js/IoT_Embla/Philips_Hue/lights.js b/queries/js/IoT_Embla/Philips_Hue/lights.js index a375bc64..824ae3b6 100644 --- a/queries/js/IoT_Embla/Philips_Hue/lights.js +++ b/queries/js/IoT_Embla/Philips_Hue/lights.js @@ -48,24 +48,23 @@ function changeColor() { function getAllLights(hub_ip = BRIDGE_IP, username = USERNAME) { return fetch(`http://${hub_ip}/api/${username}/lights`) .then((resp) => resp.json()); -} +}; function getAllGroups(hub_ip = BRIDGE_IP, username = USERNAME) { return fetch(`http://${hub_ip}/api/${username}/groups`) .then((resp) => resp.json()); -} +}; function getCurrentState(id) { return fetch(`http://${BRIDGE_IP}/api/${USERNAME}/lights/${id}`) .then((resp) => resp.json()); -} +}; -async function test(){ - console.log(getAllGroups()) +async function getAllLightsAndGroupsFromHTML(){ var lights = await getAllLights(); var groups = await getAllGroups(); console.log("lights:", lights); console.log("groups:", groups); -} +}; diff --git a/queries/js/IoT_Embla/Philips_Hue/scenes.js b/queries/js/IoT_Embla/Philips_Hue/scenes.js index cae1b9e8..439fb5ec 100644 --- a/queries/js/IoT_Embla/Philips_Hue/scenes.js +++ b/queries/js/IoT_Embla/Philips_Hue/scenes.js @@ -1,4 +1,4 @@ -function getAllScenes(hub_ip = "192.168.1.68", username = "q2jNarhGHO9izO0xZZXcoww5GYANGi6mZyJYgMdL") { +function getAllScenes(hub_ip = BRIDGE_IP, username = USERNAME) { return fetch(`http://${hub_ip}/api/${username}/scenes`) .then((resp) => resp.json()); -} \ No newline at end of file +}; \ No newline at end of file diff --git a/queries/js/IoT_Embla/Philips_Hue/set_lights.js b/queries/js/IoT_Embla/Philips_Hue/set_lights.js index af94371f..2d050e3e 100644 --- a/queries/js/IoT_Embla/Philips_Hue/set_lights.js +++ b/queries/js/IoT_Embla/Philips_Hue/set_lights.js @@ -1,55 +1,76 @@ "use strict"; // Constants to be used when setting lights from HTML -// var BRIDGE_IP = "192.168.1.68" -// var USERNAME = "q2jNarhGHO9izO0xZZXcoww5GYANGi6mZyJYgMdL" +var BRIDGE_IP = "192.168.1.68"; +var USERNAME = "q2jNarhGHO9izO0xZZXcoww5GYANGi6mZyJYgMdL"; // const QUERY_EX = "eldhús"; // const STATE_EX = JSON.stringify({scene: "rómó"}) // const LIGHTS_EX = test(); // const LIGHTS_EX = '{"1":{"state":{"on":true,"bri":100,"hue":6140,"sat":232,"effect":"none","xy":[0.5503,0.4000],"ct":500,"alert":"select","colormode":"xy","mode":"homeautomation","reachable":true},"swupdate":{"state":"noupdates","lastinstall":"2022-05-27T14:23:54"},"type":"Extended color light","name":"litaljós","modelid":"LCA001","manufacturername":"Signify Netherlands B.V.","productname":"Hue color lamp","capabilities":{"certified":true,"control":{"mindimlevel":200,"maxlumen":800,"colorgamuttype":"C","colorgamut":[[0.6915,0.3083],[0.1700,0.7000],[0.1532,0.0475]],"ct":{"min":153,"max":500}},"streaming":{"renderer":true,"proxy":true}},"config":{"archetype":"sultanbulb","function":"mixed","direction":"omnidirectional","startup":{"mode":"safety","configured":true}},"uniqueid":"00:17:88:01:06:79:c3:94-0b","swversion":"1.93.7","swconfigid":"3C05E7B6","productid":"Philips-LCA001-5-A19ECLv6"},"2":{"state":{"on":true,"bri":2,"ct":454,"alert":"select","colormode":"ct","mode":"homeautomation","reachable":false},"swupdate":{"state":"notupdatable","lastinstall":"2020-06-29T12:05:21"},"type":"Color temperature light","name":"Ikea pera Uno","modelid":"TRADFRI bulb E27 WS opal 1000lm","manufacturername":"IKEA of Sweden","productname":"Color temperature light","capabilities":{"certified":false,"control":{"ct":{"min":250,"max":454}},"streaming":{"renderer":false,"proxy":false}},"config":{"archetype":"classicbulb","function":"functional","direction":"omnidirectional"},"uniqueid":"cc:cc:cc:ff:fe:02:92:52-01","swversion":"2.0.023"},"3":{"state":{"on":true,"bri":105,"ct":454,"alert":"select","colormode":"ct","mode":"homeautomation","reachable":false},"swupdate":{"state":"notupdatable","lastinstall":"2020-07-20T13:03:26"},"type":"Color temperature light","name":"lesljós","modelid":"TRADFRI bulb E14 WS opal 400lm","manufacturername":"IKEA of Sweden","productname":"Color temperature light","capabilities":{"certified":false,"control":{"ct":{"min":250,"max":454}},"streaming":{"renderer":false,"proxy":false}},"config":{"archetype":"classicbulb","function":"functional","direction":"omnidirectional"},"uniqueid":"90:fd:9f:ff:fe:93:be:a1-01","swversion":"1.2.217"}}' // const GROUPS_EX = '{"1":{"name":"eldhús","lights":["1"],"sensors":[],"type":"Room","state":{"all_on":true,"any_on":true},"recycle":false,"class":"Living room","action":{"on":true,"bri":100,"hue":6140,"sat":232,"effect":"none","xy":[0.5503,0.4000],"ct":500,"alert":"select","colormode":"xy"}},"2":{"name":"Hús","lights":["3","2","1"],"sensors":[],"type":"Zone","state":{"all_on":true,"any_on":true},"recycle":false,"class":"Downstairs","action":{"on":true,"bri":100,"hue":6140,"sat":232,"effect":"none","xy":[0.5503,0.4000],"ct":500,"alert":"select","colormode":"xy"}},"3":{"name":"skrifstofa","lights":["2","3"],"sensors":[],"type":"Room","state":{"all_on":true,"any_on":true},"recycle":false,"class":"Living room","action":{"on":true,"bri":105,"ct":454,"alert":"select","colormode":"ct"}}}' + async function getIdObject(query) { - let allLights = await getAllLights(); - let allGroups = await getAllGroups(); + let allLights = await getAllLights(); + let allGroups = await getAllGroups(); + let returnObject + + console.log("all lights: ", allLights); + console.log("query :", query); - for (let light in allLights) { - if (allLights[light].name === query) { - let return_object = {id: light, type: "light", url: `lights/${light}/state`}; - return return_object; + let lightsResult = await philipsFuzzySearch(query, allLights); + let groupsResult = await philipsFuzzySearch(query, allGroups); + + console.log("lightResult :", lightsResult); + console.log("groupsResult :", groupsResult); + if (lightsResult != null) { + returnObject = {id: lightsResult.ID, type: "light", url: `lights/${lightsResult.ID}/state`}; + console.log("returnObject: ", returnObject); } - } - for (let group in allGroups) { - if (allGroups[group].name === query) { - let return_object = {id: group, type: "group", url: `groups/${group}/action`}; - return return_object; + else if (groupsResult != null) { + console.log("groupresult test") + returnObject = {id: groupsResult.ID, type: "group", url: `groups/${groupsResult.ID}/action`}; + console.log("returnObject: ",returnObject); } - } - //vantar e.k. error - return; -} + console.log("returnObject: ",returnObject) + return returnObject; + //vantar e.k. error +}; async function getSceneID(scene_name){ let allScenes = await getAllScenes(); - for (let scene in allScenes) { - if (allScenes[scene].name === scene_name) { - console.log("matching scene id: " + scene); - return scene; - } + let scenesResult = await philipsFuzzySearch(scene_name, allScenes); + if (scenesResult != null) { + return scenesResult.ID; } -} + else { + return; + } +}; async function setLights(query, state){ let idObject = await getIdObject(query); + if (idObject === undefined) { + return "Ekki tókst að finna ljós" + }; let ID = idObject.id; let parsed_state = JSON.parse(state); + console.log("parsed state :", parsed_state); // Check if state includes a scene or a brightness change if (parsed_state.scene) { - parsed_state.scene = await getSceneID(parsed_state.scene); - state = JSON.stringify(parsed_state); + let sceneID = await getSceneID(parsed_state.scene); + console.log("sceneID :", sceneID) + if (sceneID === undefined) { + return "Ekki tókst að finna senu" + } + else { + console.log("tókst") + parsed_state.scene = sceneID; + state = JSON.stringify(parsed_state); + } } else if (parsed_state.bri_inc) { console.log(parsed_state.bri_inc); @@ -57,7 +78,9 @@ async function setLights(query, state){ } // Send data to API let url = idObject.url; + console.log("url", url) console.log(state); + console.log("idObject", idObject); fetch(`http://${BRIDGE_IP}/api/${USERNAME}/${url}`, { method: "PUT", body: state, @@ -69,7 +92,7 @@ async function setLights(query, state){ .catch((err) => { console.log("an error occurred!"); }); -} +}; function setLightsFromHTML() { @@ -79,9 +102,48 @@ function setLightsFromHTML() { stateObject = JSON.stringify(stateObject); console.log(stateObject); setLights(query, stateObject); -} +}; + function syncSetLights(query, state) { setLights(query, state); return query; -} \ No newline at end of file +}; + + +function epliSceneTest() { + setLights('eldhús', '{"on": true, "scene": "epli"}'); +}; + + +function queryTest() { + let query = document.getElementById('queryInput').value; + let bool = document.getElementById("boolInput").value; + let scene = document.getElementById("sceneInput").value; + if (scene === "") { + syncSetLights(query, `{"on": ${bool}}`); + } + else{ + syncSetLights(query, `{"scene": "${scene}"}`); + } +}; + + +// async function getIdObjectOld(query) { +// let allLights = await getAllLights(); +// let allGroups = await getAllGroups(); + +// for (let light in allLights) { +// if (allLights[light].name === query) { +// let returnObject = {id: light, type: "light", url: `lights/${light}/state`}; +// return returnObject; +// } +// } +// for (let group in allGroups) { +// if (allGroups[group].name === query) { +// let returnObject = {id: group, type: "group", url: `groups/${group}/action`}; +// return returnObject; +// } +// } +// //vantar e.k. error +// return; \ No newline at end of file diff --git a/queries/js/IoT_Embla/main.html b/queries/js/IoT_Embla/main.html index 3c4eea34..2fed5ef6 100644 --- a/queries/js/IoT_Embla/main.html +++ b/queries/js/IoT_Embla/main.html @@ -10,7 +10,8 @@ - + +
@@ -35,24 +36,37 @@

Testing

- +
- +

Brightness increase/decrease

- - +

+
+ +
+
+
+ + + + + + + +
From 2e5aae69b5f10cf5b2d06b33886f77da24013bdc Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Fri, 10 Jun 2022 13:29:16 +0000 Subject: [PATCH 013/371] changed embla output message --- queries/iot_hue.py | 630 ++++++++++++++++++ queries/js/IoT_Embla/Philips_Hue/hub.js | 98 +++ .../js/IoT_Embla/Philips_Hue/set_lights.js | 98 +++ 3 files changed, 826 insertions(+) create mode 100755 queries/iot_hue.py create mode 100644 queries/js/IoT_Embla/Philips_Hue/hub.js create mode 100644 queries/js/IoT_Embla/Philips_Hue/set_lights.js diff --git a/queries/iot_hue.py b/queries/iot_hue.py new file mode 100755 index 00000000..67c12b00 --- /dev/null +++ b/queries/iot_hue.py @@ -0,0 +1,630 @@ +""" + + Greynir: Natural language processing for Icelandic + + Randomness query response module + + Copyright (C) 2022 Miðeind ehf. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see http://www.gnu.org/licenses/. + + This query module handles queries related to the generation + of random numbers, e.g. "Kastaðu tengingi", "Nefndu tölu milli 5 og 10", etc. + +""" + +# TODO: add "láttu", "hafðu", "litaðu", "kveiktu" functionality. +# TODO: make the objects of sentences more modular, so that the same structure doesn't need to be written for each action +# TODO: ditto the previous comment. make the initial non-terminals general and go into specifics at the terminal level instead. +# TODO: substituion klósett, baðherbergi hugmyndÆ senda lista i javascript og profa i röð + +from typing import Dict, Mapping, Optional, cast +from typing_extensions import TypedDict + +import logging +import random +import json + +from query import Query, QueryStateDict, AnswerTuple +from queries import gen_answer, read_jsfile +from tree import Result, Node + + +class SmartLights(TypedDict): + selected_light: str + philips_hue: Dict[str, str] + + +class DeviceData(TypedDict): + smartlights: SmartLights + + +_IoT_QTYPE = "IoT" + +TOPIC_LEMMAS = ["ljós", "kveikja", "litur", "birta"] + + +def help_text(lemma: str) -> str: + """Help text to return when query.py is unable to parse a query but + one of the above lemmas is found in it""" + return "Ég skil þig ef þú segir til dæmis: {0}.".format( + random.choice(("Kveiktu á ljósunum inni í eldhúsi.", + "Slökktu á leslampanum.", + "Breyttu lit lýsingarinnar í stofunni í bláan.", + "Gerðu ljósið í borðstofunni bjartara.", + "Stilltu á bjartasta niðri í kjallara.")) + ) + + +_COLORS = { + "gulur": 60 * 65535 / 360, + "rauður": 360 * 65535 / 360, + "grænn": 120 * 65535 / 360, + "blár": 240 * 65535 / 360, + "ljósblár": 180 * 65535 / 360, + "bleikur": 300 * 65535 / 360, + "hvítur": [], + "fjólublár": [], + "brúnn": [], + "appelsínugulur": [], +} + + +# This module wants to handle parse trees for queries +HANDLE_TREE = True + +# The grammar nonterminals this module wants to handle +QUERY_NONTERMINALS = {"QIoT"} + +# The context-free grammar for the queries recognized by this plug-in module +GRAMMAR = f""" + +Query → + QIoT + +QIoT → QIoTQuery '?'? + +QIoTQuery → + QIoTTurnOn + | QIoTTurnOff + | QIoTSetColor + | QIoTIncreaseBrightness + | QIoTDecreaseBrightness + | QIoTMaxBrightness + | QIoTMinBrightness + | QIoTSetScene + +QIoTTurnOn -> + "kveiktu" QIoTLightPhrase + | "kveiktu" "á" QIoTLightPhrase + +QIoTTurnOff -> + "slökktu" QIoTLightPhrase + | "slökktu" "á" QIoTLightPhrase + +QIoTSetColor -> + QIoTMakeVerb? QIoTMakeColorObject + | QIoTSetVerb QIoTSetColorObject + | QIoTChangeVerb QIoTChangeColorObject + +QIoTMakeColorObject -> + QIoTColorLightPhrase "að"? QIoTColorNamePhrase + | QIoTColorLight "að"? QIoTColorNamePhrase QIoTGroupNamePhrase? + | QIoTColorNamePhrase QIoTGroupNamePhrase? + | QIoTColorNamePhrase QIoTLocationPreposition QIoTLightPhrase + +QIoTSetColorObject -> + QIoTColorLightPhrase "á" QIoTColorNamePhrase + | QIoTColorLight "á" QIoTColorNamePhrase QIoTGroupNamePhrase? + | "á"? QIoTColorNamePhrase QIoTGroupNamePhrase? + | "á"? QIoTColorNamePhrase QIoTLocationPreposition QIoTLightPhrase + +QIoTChangeColorObject -> + QIoTColorLightPhrase "í" QIoTColorNamePhrase + | QIoTColorLight "í" QIoTColorNamePhrase QIoTGroupNamePhrase? + | "í" QIoTColorNamePhrase QIoTGroupNamePhrase? + | "í" QIoTColorNamePhrase QIoTLocationPreposition QIoTLightPhrase + +# Need to add "gerðu minni birtu" functionality. +QIoTIncreaseBrightness -> + QIoTIncrease QIoTBrightness QIoTLightPhrase? + | QIoTMakeVerb? QIoTMakeBrighterObject + # | QIoTSetVerb QIoTSetBrightObject + +QIoTMakeBrighterObject -> + QIoTBrightnessLightPhrase QIoTBrighterPhrase + | QIoTBrightnessLight QIoTMoreOrBrighter QIoTGroupNamePhrase? + | QIoTMoreBrightness QIoTGroupNamePhrase? + | QIoTMoreBrightness QIoTLocationPreposition QIoTLightPhrase + +# QIoTSetBrightObject -> +# QIoTColorLightPhrase "á" QIoTColorNamePhrase +# | QIoTColorLight "á" QIoTColorNamePhrase QIoTGroupNamePhrase? +# | "á"? QIoTColorNamePhrase QIoTGroupNamePhrase? +# | "á"? QIoTColorNamePhrase QIoTLocationPreposition QIoTLightPhrase + +QIoTDecreaseBrightness -> + QIoTDecrease QIoTBrightness QIoTLightPhrase? + | QIoTMakeVerb? QIoTMakeDarkerObject + +QIoTMakeDarkerObject -> + QIoTBrightnessLightPhrase QIoTDarkerPhrase + | QIoTBrightnessLight QIoTLessOrDarker QIoTGroupNamePhrase? + | QIoTLessOrDarker QIoTGroupNamePhrase? + | QIoTLessOrDarker QIoTLocationPreposition QIoTLightPhrase + +QIoTMaxBrightness -> + # ?QIoTMakeVerb QIoTMakeBrightestObject + QIoTSetVerb QIoTSetBrightestObject + +# QIoTMakeBrightestObject -> +# QIoTBrightnessLightPhrase QIoTMoreOrBrighter +# | QIoTBrightnessLight QIoTMoreOrBrighter QIoTGroupNamePhrase? +# | QIoTMoreBrightness QIoTGroupNamePhrase? +# | QIoTMoreBrightness QIoTLocationPreposition QIoTLightPhrase + +QIoTSetBrightestObject -> + QIoTBrightnessLightPhrase "á" QIoTMostOrBrightest + | QIoTBrightnessLight "á" QIoTMostOrHighest QIoTGroupNamePhrase? + | "á"? QIoTBrightestPhrase QIoTGroupNamePhrase? + | "á"? QIoTBrightestPhrase QIoTLocationPreposition QIoTLightPhrase + +QIoTMinBrightness -> + # ?QIoTMakeVerb QIoTMakeBrightestObject + QIoTSetVerb QIoTSetDarkestObject + +# QIoTMakeDarkestObject -> +# QIoTBrightnessLightPhrase QIoTMoreOrBrighter +# | QIoTBrightnessLight QIoTMoreOrBrighter QIoTGroupNamePhrase? +# | QIoTMoreBrightness QIoTGroupNamePhrase? +# | QIoTMoreBrightness QIoTLocationPreposition QIoTLightPhrase + +QIoTSetDarkestObject -> + QIoTBrightnessLightPhrase "á" QIoTLeastOrDarkest + | QIoTBrightnessLight "á" QIoTLeastOrLowest QIoTGroupNamePhrase? + | "á"? QIoTDarkestPhrase QIoTGroupNamePhrase? + | "á"? QIoTDarkestPhrase QIoTLocationPreposition QIoTLightPhrase + +QIoTSetScene -> + QIoTMakeVerb QIoTMakeSceneObject + | QIoTSetVerb QIoTSetSceneObject + | QIoTChangeVerb QIoTChangeSceneObject + +QIoTMakeSceneObject -> + QIoTSceneLightPhrase "að"? QIoTSceneNamePhrase + | QIoTScene "að"? QIoTSceneNamePhrase QIoTGroupNamePhrase? + | QIoTSceneNamePhrase QIoTGroupNamePhrase + | QIoTSceneNamePhrase QIoTLocationPreposition QIoTLightPhrase + +QIoTSetSceneObject -> + QIoTSceneLightPhrase "á" QIoTSceneNamePhrase + | QIoTScene "á" QIoTSceneNamePhrase QIoTGroupNamePhrase? + | "á"? QIoTSceneNamePhrase QIoTGroupNamePhrase? + | "á"? QIoTSceneNamePhrase QIoTLocationPreposition QIoTLightPhrase + +QIoTChangeSceneObject -> + QIoTSceneLightPhrase "í" QIoTSceneNamePhrase + | QIoTScene "í" QIoTSceneNamePhrase QIoTGroupNamePhrase? + | "í" QIoTSceneNamePhrase QIoTGroupNamePhrase? + | "í" QIoTSceneNamePhrase QIoTLocationPreposition QIoTLightPhrase + +QIoTLeastOrDarkest -> + QIoTLeastOrLowest + | QIoTDarkestPhrase + +QIoTLeastOrLowest -> + QIoTLeast + | QIoTLowest + +QIoTDarkestPhrase -> + QIoTDarkest + | QIoTLeastOrLowest QIoTBrightness + | QIoTMostOrHighest QIoTDarkness + +QIoTMostOrBrightest -> + QIoTMostOrHighest + | QIoTBrightestPhrase + +QIoTMostOrHighest -> + QIoTMost + | QIoTHighest + +QIoTBrightestPhrase -> + QIoTBrightest + | QIoTMostOrHighest QIoTBrightness + | QIoTLeastOrLowest QIoTDarkness + +QIoTMoreBrightness -> + QIoTMoreOrHigher QIoTBrightness + | QIoTBrighterPhrase + +QIoTMoreOrHigher -> + 'mikill:lo'_mst + | 'hár:lo'_mst + +QIoTMoreOrBrighter -> + QIoTMore + | QIoTBrighterPhrase + +QIoTBrighterPhrase -> + QIoTBrighter + | QIoTMore QIoTBright + | QIoTLess QIoTDark + +QIoTLessOrDarker -> + QIoTDarker + | QIoTLess + | QIoTLess QIoTBright + | QIoTMore QIoTDark + +QIoTLessOrDarker -> + QIoTLess + | QIoTDarkerPhrase + +QIoTDarkerPhrase -> + QIoTDarker + | QIoTMore QIoTDark + | QIoTLess QIoTBright + +QIoTBrightnessLightPhrase -> + QIoTBrightnessLight QIoTGroupNamePhrase? + | QIoTGroupName + +QIoTBrightnessLight -> + QIoTBrightness? QIoTLight + | QIoTBrightness "á" QIoTLight + | QIoTBrightness + +QIoTColorLightPhrase -> + QIoTColorLight QIoTGroupNamePhrase? + | QIoTGroupName + +# Separate cases for "lit ljóssins" and "litinn á ljósinu", to be precise. But it is not ideal as is +QIoTColorLight -> + QIoTColor? QIoTLight + | QIoTColor "á" QIoTLight + | QIoTColor + +QIoTSceneLightPhrase -> + QIoTScene QIoTGroupNamePhrase + +QIoTLightPhrase -> + QIoTLight QIoTGroupNamePhrase? + | QIoTGroupNamePhrase + +# tried making this 'ljós:no' to avoid ambiguity, but all queries failed as a result +QIoTLight -> + QIoTLightWord + | QIoTLightName + +QIoTColorName -> + {" | ".join(f"'{color}:lo'" for color in _COLORS.keys())} + +QIoTColorNamePhrase -> + QIoTColor? QIoTColorName + | QIoTColorName QIoTColor? + | QIoTColorName QIoTLight? + +QIoTSceneName -> + no + +QIoTSceneNamePhrase -> + QIoTScene? QIoTSceneName + | QIoTSceneName QIoTScene? + | QIoTSceneName QIoTLight? + +QIoTGroupNamePhrase -> + QIoTLocationPreposition QIoTGroupName + +# The Nl, noun phrase, is too greedy, e.g. parsing "ljósin í eldhúsinu" as the group name. +# But no, noun, is too strict, e.g. "herbergið hans Loga" could be a user-made group name. +QIoTGroupName -> + no + +QIoTLightName -> + no + +QIoTLocationPreposition -> + QIoTLocationPrepositionFirstPart? QIoTLocationPrepositionSecondPart + +# The latter proverbs are grammatically incorrect, but common errors, both in speech and transcription. +# The list provided is taken from StefnuAtv in Greynir.grammar. That includes "aftur:ao", which is not applicable here. +QIoTLocationPrepositionFirstPart -> + StaðarAtv + | "fram:ao" + | "inn:ao" + | "niður:ao" + | "upp:ao" + | "út:ao" + +QIoTLocationPrepositionSecondPart -> + "á" | "í" + +QIoTBright -> + 'bjartur:lo'_fst + | 'ljós:lo'_fst + | "Bjart" + | "bjart" + +QIoTDarkest -> + 'dimmur:lo'_evb + | 'dimmur:lo'_esb + | 'dökkur:lo'_evb + | 'dökkur:lo'_esb + +QIoTLeast -> + 'lítill:lo'_evb + | 'lítill:lo'_esb + | 'lítið:ao'_est + +QIoTLowest -> + 'lágur:lo'_evb + | 'lágur:lo'_esb + +QIoTBrightest -> + 'bjartur:lo'_evb + | 'bjartur:lo'_esb + | 'ljós:lo'_evb + | 'ljós:lo'_esb + +QIoTMost -> + 'mikill:lo'_evb + | 'mikill:lo'_esb + | 'mikið:ao'_est + +QIoTHighest -> + 'hár:lo'_evb + | 'hár:lo'_esb + +QIoTBrighter -> + 'bjartur:lo'_mst + | 'ljós:lo'_mst + +QIoTDark -> + 'dimmur:lo'_fst + | 'dökkur:lo'_fst + +QIoTDarker -> + 'dimmur:lo'_mst + | 'dökkur:lo'_mst + +QIoTLessOrLower -> + 'lítill:lo'_mst + | 'lágur:lo'_mst + +QIoTIncrease -> + 'hækka:so'_bh + | 'auka:so'_bh + +QIoTDecrease -> + 'lækka:so'_bh + | 'minnka:so'_bh + +QIoTMore -> + "meiri" + | "meira" + +QIoTLess -> + "minni" + | "minna" + +QIoTSetVerb -> + 'setja:so'_bh + | 'stilla:so'_bh + +QIoTMakeVerb -> + 'gera:so'_bh + +QIoTChangeVerb -> + 'breyta:so'_bh + +QIoTLightWord -> + 'ljós' + | 'lýsing' + +QIoTColor -> + 'litur' + | 'litblær' + | 'blær' + +QIoTScene -> + 'sena' + | 'stemning' + | 'stemming' + | 'stemmning' + +QIoTDarkness -> + 'myrkur' + +QIoTBrightness -> + 'birta' + | 'birtustig' +""" + + +def QIoTQuery(node: Node, params: QueryStateDict, result: Result) -> None: + result.qtype = _IoT_QTYPE + + +def QIoTTurnOn(node: Node, params: QueryStateDict, result: Result) -> None: + result.action = "turn_on" + if "hue_obj" not in result: + result["hue_obj"] = {"on": True} + else: + result["hue_obj"]["on"] = True + + +def QIoTTurnOff(node: Node, params: QueryStateDict, result: Result) -> None: + result.action = "turn_off" + if "hue_obj" not in result: + result["hue_obj"] = {"on": False} + else: + result["hue_obj"]["on"] = False + + +def QIoTSetColor(node: Node, params: QueryStateDict, result: Result) -> None: + result.action = "set_color" + print(result.color_name) + color_hue = _COLORS.get(result.color_name, None) + print(color_hue) + if color_hue is not None: + if "hue_obj" not in result: + result["hue_obj"] = {"on": True, "hue": int(color_hue)} + else: + result["hue_obj"]["hue"] = int(color_hue) + result["hue_obj"]["on"] = True + + +def QIoTIncreaseBrightness(node: Node, params: QueryStateDict, result: Result) -> None: + result.action = "increase_brightness" + if "hue_obj" not in result: + result["hue_obj"] = {"on": True, "bri_inc": 64} + else: + result["hue_obj"]["bri_inc"] = 64 + result["hue_obj"]["on"] = True + + +def QIoTDecreaseBrightness(node: Node, params: QueryStateDict, result: Result) -> None: + result.action = "decrease_brightness" + if "hue_obj" not in result: + result["hue_obj"] = {"bri_inc": -64} + else: + result["hue_obj"]["bri_inc"] = -64 + + +def QIoTMaxBrightness(node: Node, params: QueryStateDict, result: Result) -> None: + result.action = "decrease_brightness" + if "hue_obj" not in result: + result["hue_obj"] = {"bri": 255} + else: + result["hue_obj"]["bri"] = 255 + + +def QIoTMinBrightness(node: Node, params: QueryStateDict, result: Result) -> None: + result.action = "decrease_brightness" + if "hue_obj" not in result: + result["hue_obj"] = {"bri": 0} + else: + result["hue_obj"]["bri"] = 0 + + +def QIoTSetScene(node: Node, params: QueryStateDict, result: Result) -> None: + result.action = "set_scene" + scene_name = result.get("scene_name", None) + print(scene_name) + if scene_name is not None: + if "hue_obj" not in result: + result["hue_obj"] = {"on": True, "scene": scene_name} + else: + result["hue_obj"]["scene"] = scene_name + result["hue_obj"]["on"] = True + + +def QIoTColorName(node: Node, params: QueryStateDict, result: Result) -> None: + result["color_name"] = ( + node.first_child(lambda x: True).string_self().strip("'").split(":")[0] + ) + + +def QIoTSceneName(node: Node, params: QueryStateDict, result: Result) -> None: + result["scene_name"] = result._indefinite + + +def QIoTGroupName(node: Node, params: QueryStateDict, result: Result) -> None: + result["group_name"] = result._indefinite + + +def QIoTLightName(node: Node, params: QueryStateDict, result: Result) -> None: + result["light_name"] = result._indefinite + + +# Convert color name into hue +# Taken from home.py +_COLOR_NAME_TO_CIE: Mapping[str, float] = { + "gulur": 60 * 65535 / 360, + "grænn": 120 * 65535 / 360, + "ljósblár": 180 * 65535 / 360, + "blár": 240 * 65535 / 360, + "bleikur": 300 * 65535 / 360, + "rauður": 360 * 65535 / 360, + # "Rauð": 360 * 65535 / 360, +} + + +def sentence(state: QueryStateDict, result: Result) -> None: + """Called when sentence processing is complete""" + q: Query = state["query"] + if "qtype" not in result: + q.set_error("E_QUERY_NOT_UNDERSTOOD") + return + + # host = str(flask.request.host) + # smartdevice_type = "smartlights" + # client_id = str(q.client_id) + + # # Fetch relevant data from the device_data table to perform an action on the lights + # device_data = cast(Optional[DeviceData], q.client_data(smartdevice_type)) + # print(device_data) + + # selected_light: Optional[str] = None + # hue_credentials: Optional[Dict[str, str]] = None + # if device_data is not None and smartdevice_type in device_data: + # dev = device_data[smartdevice_type] + # assert dev is not None + # selected_light = dev.get("selected_light") + # hue_credentials = dev.get("philips_hue") + # bridge_ip = hue_credentials.get("ipAddress") + # username = hue_credentials.get("username") + + # if not device_data or not hue_credentials: + + # js = read_jsfile("IoT_Embla/Philips_Hue/hub.js") + # js += f"syncConnectHub('{host}','{client_id}');" + # q.set_answer(*gen_answer("blabla")) + # q.set_command(js) + # return + + # Successfully matched a query type + + q.set_qtype(result.qtype) + + try: + # kalla í javascripts stuff + light_or_group_name = result.get("light_name", result.get("group_name", "")) + color_name = result.get("color_name", "") + print("GROUP NAME:", light_or_group_name) + print("COLOR NAME:", color_name) + print(result.hue_obj) + q.set_answer( + *gen_answer( + "ég var að kveikja ljósin! " + # + group_name + # + " " + # + color_name + # + " " + # + result.action + # + " " + # + str(result.hue_obj.get("hue", "enginn litur")) + ) + ) + js = ( + f"var BRIDGE_IP = '192.168.1.68';var USERNAME = 'q2jNarhGHO9izO0xZZXcoww5GYANGi6mZyJYgMdL';" + + read_jsfile("IoT_Embla/Philips_Hue/lights.js") + + read_jsfile("IoT_Embla/Philips_Hue/set_lights.js") + ) + js += f"syncSetLights('{light_or_group_name}', '{json.dumps(result.hue_obj)}');" + q.set_command(js) + except Exception as e: + logging.warning("Exception while processing random query: {0}".format(e)) + q.set_error("E_EXCEPTION: {0}".format(e)) + raise diff --git a/queries/js/IoT_Embla/Philips_Hue/hub.js b/queries/js/IoT_Embla/Philips_Hue/hub.js new file mode 100644 index 00000000..ec6a5d53 --- /dev/null +++ b/queries/js/IoT_Embla/Philips_Hue/hub.js @@ -0,0 +1,98 @@ +"use strict"; + +async function findHub() { + let hubObj = new Object() + hubObj.id = "ecb5fafffe1be1a4" + hubObj.internalipaddress = "192.168.1.68" + hubObj.port = "443" + console.log(hubObj) + return hubObj + return fetch(`https://discovery.meethue.com`) + .then((resp) => resp.json()) + .then((obj) => { + console.log(obj); + return(obj[0]); + }) + .catch((err) => { + console.log("No smart device found!"); + }); +} + +async function createNewDeveloper(ipAddress) { + console.log("create new developer"); + const body = JSON.stringify({ + 'devicetype': 'mideind_hue_communication#smartdevice' + }); + return fetch(`http://${ipAddress}/api`, { + method: "POST", + body: body, + }) + .then((resp) => resp.json()) + .then((obj) => { + return(obj[0]); + }) + .catch((err) => { + console.log(err); + }); +} + + +async function storeDevice(data, requestURL) { + console.log("store device"); + return fetch(`http://${requestURL}/register_query_data.api`, { + method: "POST", + body: JSON.stringify(data), + headers: { + 'Content-Type': 'application/json' + }, + }) + .then((resp) => resp.json()) + .then((obj) => { + return(obj); + }) + .catch((err) => { + console.log("Error while storing user"); + }); +} + +// clientID = "82AD3C91-7DA2-4502-BB17-075CEC090B14", requestURL = "192.168.1.68") +async function connectHub(clientID, requestURL) { + console.log("connect hub"); + let deviceInfo = await findHub(); + console.log("device info: ", deviceInfo); + console.log("device_ip :", deviceInfo.internalipaddress) + + try { + let username = await createNewDeveloper(deviceInfo.internalipaddress); + console.log("username: ",username); + if (!username.success) { + return 'Ýttu á \'Philips\' takkann á tengiboxinu og reyndu aftur'; + } + + const data = { + 'client_id': clientID, + 'key': 'smartlights', + 'data': { + 'smartlights': { + 'selected_light': 'philips_hue', + 'philips_hue': { + 'username':username.success.username, + 'ipAddress':deviceInfo.internalipaddress + } + } + } + }; + + const result = await storeDevice(data, requestURL); + console.log("result: ", result); + return 'Tenging við snjalltæki tókst'; + } catch(error) { + console.log(error); + return 'Ekki tókst að tengja snjalltæki'; + } +} + +function syncConnectHub(clientID, requestURL) { + connectHub(clientID, requestURL); + return "blabla"; +} \ No newline at end of file diff --git a/queries/js/IoT_Embla/Philips_Hue/set_lights.js b/queries/js/IoT_Embla/Philips_Hue/set_lights.js new file mode 100644 index 00000000..41da87cb --- /dev/null +++ b/queries/js/IoT_Embla/Philips_Hue/set_lights.js @@ -0,0 +1,98 @@ +"use strict"; +// Constants to be used when setting lights from HTML + +// var BRIDGE_IP = "192.168.1.68" +// var USERNAME = "q2jNarhGHO9izO0xZZXcoww5GYANGi6mZyJYgMdL" +// const QUERY_EX = "eldhús"; +// const STATE_EX = JSON.stringify({scene: "rómó"}) +// const LIGHTS_EX = test(); +// const LIGHTS_EX = '{"1":{"state":{"on":true,"bri":100,"hue":6140,"sat":232,"effect":"none","xy":[0.5503,0.4000],"ct":500,"alert":"select","colormode":"xy","mode":"homeautomation","reachable":true},"swupdate":{"state":"noupdates","lastinstall":"2022-05-27T14:23:54"},"type":"Extended color light","name":"litaljós","modelid":"LCA001","manufacturername":"Signify Netherlands B.V.","productname":"Hue color lamp","capabilities":{"certified":true,"control":{"mindimlevel":200,"maxlumen":800,"colorgamuttype":"C","colorgamut":[[0.6915,0.3083],[0.1700,0.7000],[0.1532,0.0475]],"ct":{"min":153,"max":500}},"streaming":{"renderer":true,"proxy":true}},"config":{"archetype":"sultanbulb","function":"mixed","direction":"omnidirectional","startup":{"mode":"safety","configured":true}},"uniqueid":"00:17:88:01:06:79:c3:94-0b","swversion":"1.93.7","swconfigid":"3C05E7B6","productid":"Philips-LCA001-5-A19ECLv6"},"2":{"state":{"on":true,"bri":2,"ct":454,"alert":"select","colormode":"ct","mode":"homeautomation","reachable":false},"swupdate":{"state":"notupdatable","lastinstall":"2020-06-29T12:05:21"},"type":"Color temperature light","name":"Ikea pera Uno","modelid":"TRADFRI bulb E27 WS opal 1000lm","manufacturername":"IKEA of Sweden","productname":"Color temperature light","capabilities":{"certified":false,"control":{"ct":{"min":250,"max":454}},"streaming":{"renderer":false,"proxy":false}},"config":{"archetype":"classicbulb","function":"functional","direction":"omnidirectional"},"uniqueid":"cc:cc:cc:ff:fe:02:92:52-01","swversion":"2.0.023"},"3":{"state":{"on":true,"bri":105,"ct":454,"alert":"select","colormode":"ct","mode":"homeautomation","reachable":false},"swupdate":{"state":"notupdatable","lastinstall":"2020-07-20T13:03:26"},"type":"Color temperature light","name":"lesljós","modelid":"TRADFRI bulb E14 WS opal 400lm","manufacturername":"IKEA of Sweden","productname":"Color temperature light","capabilities":{"certified":false,"control":{"ct":{"min":250,"max":454}},"streaming":{"renderer":false,"proxy":false}},"config":{"archetype":"classicbulb","function":"functional","direction":"omnidirectional"},"uniqueid":"90:fd:9f:ff:fe:93:be:a1-01","swversion":"1.2.217"}}' +// const GROUPS_EX = '{"1":{"name":"eldhús","lights":["1"],"sensors":[],"type":"Room","state":{"all_on":true,"any_on":true},"recycle":false,"class":"Living room","action":{"on":true,"bri":100,"hue":6140,"sat":232,"effect":"none","xy":[0.5503,0.4000],"ct":500,"alert":"select","colormode":"xy"}},"2":{"name":"Hús","lights":["3","2","1"],"sensors":[],"type":"Zone","state":{"all_on":true,"any_on":true},"recycle":false,"class":"Downstairs","action":{"on":true,"bri":100,"hue":6140,"sat":232,"effect":"none","xy":[0.5503,0.4000],"ct":500,"alert":"select","colormode":"xy"}},"3":{"name":"skrifstofa","lights":["2","3"],"sensors":[],"type":"Room","state":{"all_on":true,"any_on":true},"recycle":false,"class":"Living room","action":{"on":true,"bri":105,"ct":454,"alert":"select","colormode":"ct"}}}' +var output = undefined; + +async function getIdObject(query) { + let allLights = getAllLights(); + let allGroups = getAllGroups(); + + for (let light in allLights) { + if (allLights[light].name === query) { + let return_object = {id: light, type: "light", url: `lights/${light}/state`}; + return return_object; + } + } + for (let group in allGroups) { + if (allGroups[group].name === query) { + let return_object = {id: group, type: "group", url: `groups/${group}/action`}; + return return_object; + } + } + //vantar e.k. error + return; +} + + +async function getSceneID(scene_name){ + let allScenes = await getAllScenes(); + for (let scene in allScenes) { + if (allScenes[scene].name === scene_name) { + console.log("matching scene id: " + scene); + return scene; + } + } +} + + +async function setLights(query, state){ + let idObject = await getIdObject(query); + let ID = idObject.id; + let parsed_state = JSON.parse(state); + + // Check if state includes a scene or a brightness change + if (parsed_state.scene) { + parsed_state.scene = await getSceneID(parsed_state.scene); + state = JSON.stringify(parsed_state); + } + else if (parsed_state.bri_inc) { + console.log(parsed_state.bri_inc); + state = JSON.stringify(parsed_state); + } + // Send data to API + let url = idObject.url; + console.log(state); + fetch(`http://${BRIDGE_IP}/api/${USERNAME}/${url}`, { + method: "PUT", + body: state, + }) + .then((resp) => resp.json()) + .then((obj) => { + console.log(obj); + output = "Aðgerð tókst. " + query + state + fetch(`http://192.168.1.243:31337/?q=outputDefinedINSuccess`) + if (!("error" in obj)) { + console.log("philips didn't understand") + } + }) + .catch((err) => { + console.log("an error occurred!"); + output = "Aðgerð mistókst. " + query + state + fetch(`http://192.168.1.243:31337/?q=outputDefinedInError`) + }); +} + + +function setLightsFromHTML() { + let query = document.getElementById("queryInput").value; + let stateObject = new Object(); + stateObject.bri_inc = Number(document.getElementById("brightnessInput").value); + stateObject = JSON.stringify(stateObject); + console.log(stateObject); + setLights(query, stateObject); +} + +function syncSetLights(query, state) { + setLights(query, state); + fetch(`http://192.168.1.243:31337/?q=syncSetLightsAccessed`) + // while (output === undefined) {} + fetch(`http://192.168.1.243:31337/?q=whileLoopExited`) + return query + "\n" + state +} \ No newline at end of file From 63b40eae1d52977b7230c148d6bcce26ab7f9ed8 Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Mon, 13 Jun 2022 10:33:21 +0000 Subject: [PATCH 014/371] promise strucutre --- queries/iot_hue.py | 4 +- .../js/IoT_Embla/Philips_Hue/set_lights.js | 198 +- queries/js/IoT_Embla/fuse.js | 2240 +++++++++++++++++ 3 files changed, 2348 insertions(+), 94 deletions(-) create mode 100644 queries/js/IoT_Embla/fuse.js diff --git a/queries/iot_hue.py b/queries/iot_hue.py index 016da8b9..f3fbac20 100755 --- a/queries/iot_hue.py +++ b/queries/iot_hue.py @@ -619,7 +619,9 @@ def sentence(state: QueryStateDict, result: Result) -> None: ) ) js = ( - f"var BRIDGE_IP = '192.168.1.68';var USERNAME = 'q2jNarhGHO9izO0xZZXcoww5GYANGi6mZyJYgMdL';" + read_jsfile("IoT_Embla/fuse.js") + + f"var BRIDGE_IP = '192.168.1.68';var USERNAME = 'q2jNarhGHO9izO0xZZXcoww5GYANGi6mZyJYgMdL';" + + read_jsfile("IoT_Embla/Philips_Hue/fuse_search.js") + read_jsfile("IoT_Embla/Philips_Hue/lights.js") + read_jsfile("IoT_Embla/Philips_Hue/set_lights.js") ) diff --git a/queries/js/IoT_Embla/Philips_Hue/set_lights.js b/queries/js/IoT_Embla/Philips_Hue/set_lights.js index af1d6250..d6b71a2b 100644 --- a/queries/js/IoT_Embla/Philips_Hue/set_lights.js +++ b/queries/js/IoT_Embla/Philips_Hue/set_lights.js @@ -9,135 +9,147 @@ var USERNAME = "q2jNarhGHO9izO0xZZXcoww5GYANGi6mZyJYgMdL"; // const LIGHTS_EX = '{"1":{"state":{"on":true,"bri":100,"hue":6140,"sat":232,"effect":"none","xy":[0.5503,0.4000],"ct":500,"alert":"select","colormode":"xy","mode":"homeautomation","reachable":true},"swupdate":{"state":"noupdates","lastinstall":"2022-05-27T14:23:54"},"type":"Extended color light","name":"litaljós","modelid":"LCA001","manufacturername":"Signify Netherlands B.V.","productname":"Hue color lamp","capabilities":{"certified":true,"control":{"mindimlevel":200,"maxlumen":800,"colorgamuttype":"C","colorgamut":[[0.6915,0.3083],[0.1700,0.7000],[0.1532,0.0475]],"ct":{"min":153,"max":500}},"streaming":{"renderer":true,"proxy":true}},"config":{"archetype":"sultanbulb","function":"mixed","direction":"omnidirectional","startup":{"mode":"safety","configured":true}},"uniqueid":"00:17:88:01:06:79:c3:94-0b","swversion":"1.93.7","swconfigid":"3C05E7B6","productid":"Philips-LCA001-5-A19ECLv6"},"2":{"state":{"on":true,"bri":2,"ct":454,"alert":"select","colormode":"ct","mode":"homeautomation","reachable":false},"swupdate":{"state":"notupdatable","lastinstall":"2020-06-29T12:05:21"},"type":"Color temperature light","name":"Ikea pera Uno","modelid":"TRADFRI bulb E27 WS opal 1000lm","manufacturername":"IKEA of Sweden","productname":"Color temperature light","capabilities":{"certified":false,"control":{"ct":{"min":250,"max":454}},"streaming":{"renderer":false,"proxy":false}},"config":{"archetype":"classicbulb","function":"functional","direction":"omnidirectional"},"uniqueid":"cc:cc:cc:ff:fe:02:92:52-01","swversion":"2.0.023"},"3":{"state":{"on":true,"bri":105,"ct":454,"alert":"select","colormode":"ct","mode":"homeautomation","reachable":false},"swupdate":{"state":"notupdatable","lastinstall":"2020-07-20T13:03:26"},"type":"Color temperature light","name":"lesljós","modelid":"TRADFRI bulb E14 WS opal 400lm","manufacturername":"IKEA of Sweden","productname":"Color temperature light","capabilities":{"certified":false,"control":{"ct":{"min":250,"max":454}},"streaming":{"renderer":false,"proxy":false}},"config":{"archetype":"classicbulb","function":"functional","direction":"omnidirectional"},"uniqueid":"90:fd:9f:ff:fe:93:be:a1-01","swversion":"1.2.217"}}' // const GROUPS_EX = '{"1":{"name":"eldhús","lights":["1"],"sensors":[],"type":"Room","state":{"all_on":true,"any_on":true},"recycle":false,"class":"Living room","action":{"on":true,"bri":100,"hue":6140,"sat":232,"effect":"none","xy":[0.5503,0.4000],"ct":500,"alert":"select","colormode":"xy"}},"2":{"name":"Hús","lights":["3","2","1"],"sensors":[],"type":"Zone","state":{"all_on":true,"any_on":true},"recycle":false,"class":"Downstairs","action":{"on":true,"bri":100,"hue":6140,"sat":232,"effect":"none","xy":[0.5503,0.4000],"ct":500,"alert":"select","colormode":"xy"}},"3":{"name":"skrifstofa","lights":["2","3"],"sensors":[],"type":"Room","state":{"all_on":true,"any_on":true},"recycle":false,"class":"Living room","action":{"on":true,"bri":105,"ct":454,"alert":"select","colormode":"ct"}}}' +var output; async function getIdObject(query) { - let allLights = await getAllLights(); - let allGroups = await getAllGroups(); - let returnObject - - console.log("all lights: ", allLights); - console.log("query :", query); - - let lightsResult = await philipsFuzzySearch(query, allLights); - let groupsResult = await philipsFuzzySearch(query, allGroups); - - console.log("lightResult :", lightsResult); - console.log("groupsResult :", groupsResult); - if (lightsResult != null) { - returnObject = {id: lightsResult.ID, type: "light", url: `lights/${lightsResult.ID}/state`}; - console.log("returnObject: ", returnObject); - } - else if (groupsResult != null) { - console.log("groupresult test") - returnObject = {id: groupsResult.ID, type: "group", url: `groups/${groupsResult.ID}/action`}; - console.log("returnObject: ",returnObject); - } - console.log("returnObject: ",returnObject) - return returnObject; - //vantar e.k. error -}; - + let returnObject; + Promise + .all([getAllLights(), getAllGroups()]) + .then((lists) => { + fetch(`http://192.168.1.70:9001/`, { + method: "POST", + body: JSON.stringify({ + lights: lists[0], + groups: lists[1], + }), + }); + console.log(lists[0]); + console.log(lists[1]); + }); + let allLights = await getAllLights(); + let allGroups = await getAllGroups(); + + console.log("all lights: ", allLights); + console.log("query :", query); + + let lightsResult = await philipsFuzzySearch(query, allLights); + let groupsResult = await philipsFuzzySearch(query, allGroups); + + console.log("lightResult :", lightsResult); + console.log("groupsResult :", groupsResult); + if (lightsResult != null) { + returnObject = { + id: lightsResult.ID, + type: "light", + url: `lights/${lightsResult.ID}/state`, + }; + console.log("returnObject: ", returnObject); + } else if (groupsResult != null) { + console.log("groupresult test"); + returnObject = { + id: groupsResult.ID, + type: "group", + url: `groups/${groupsResult.ID}/action`, + }; + console.log("returnObject: ", returnObject); + } + console.log("returnObject: ", returnObject); + return returnObject; + //vantar e.k. error +} -async function getSceneID(scene_name){ +async function getSceneID(scene_name) { let allScenes = await getAllScenes(); let scenesResult = await philipsFuzzySearch(scene_name, allScenes); if (scenesResult != null) { return scenesResult.ID; - } - else { + } else { return; } -}; - - -async function setLights(query, state){ - let idObject = await getIdObject(query); - if (idObject === undefined) { - return "Ekki tókst að finna ljós" - }; - let ID = idObject.id; - let parsed_state = JSON.parse(state); - console.log("parsed state :", parsed_state); +} - // Check if state includes a scene or a brightness change - if (parsed_state.scene) { - let sceneID = await getSceneID(parsed_state.scene); - console.log("sceneID :", sceneID) - if (sceneID === undefined) { - return "Ekki tókst að finna senu" +async function setLights(query, state) { + try { + let idObject = await getIdObject(query); + if (idObject === undefined) { + return "Ekki tókst að finna ljós"; } - else { - console.log("tókst") - parsed_state.scene = sceneID; + let parsed_state = JSON.parse(state); + let ID = idObject.id; + console.log("parsed state :", parsed_state); + + // Check if state includes a scene or a brightness change + if (parsed_state.scene) { + let sceneID = await getSceneID(parsed_state.scene); + console.log("sceneID :", sceneID); + if (sceneID === undefined) { + return "Ekki tókst að finna senu"; + } else { + console.log("tókst"); + parsed_state.scene = sceneID; + state = JSON.stringify(parsed_state); + } + } else if (parsed_state.bri_inc) { + console.log(parsed_state.bri_inc); state = JSON.stringify(parsed_state); } - } - else if (parsed_state.bri_inc) { - console.log(parsed_state.bri_inc); - state = JSON.stringify(parsed_state); - } - // Send data to API - let url = idObject.url; - console.log("url", url) - console.log(state); - console.log("idObject", idObject); - fetch(`http://${BRIDGE_IP}/api/${USERNAME}/${url}`, { - method: "PUT", - body: state, - }) - .then((resp) => resp.json()) - .then((obj) => { - console.log(obj); - output = "Aðgerð tókst. " + query + state - fetch(`http://192.168.1.243:31337/?q=outputDefinedINSuccess`) - if (!("error" in obj)) { - console.log("philips didn't understand") - } + // Send data to API + let url = idObject.url; + console.log("url", url); + console.log(state); + console.log("idObject", idObject); + fetch(`http://${BRIDGE_IP}/api/${USERNAME}/${url}`, { + method: "PUT", + body: state, }) - .catch((err) => { - console.log("an error occurred!"); - output = "Aðgerð mistókst. " + query + state - fetch(`http://192.168.1.243:31337/?q=outputDefinedInError`) - }); + .then((resp) => resp.json()) + .then((obj) => { + console.log(obj); + output = "Aðgerð tókst. " + query + state; + if (!("error" in obj)) { + console.log("philips didn't understand"); + } + }) + .catch((err) => { + console.log("an error occurred!"); + output = "Aðgerð mistókst. " + query + state; + }); + } catch (e) { + fetch(`http://192.168.1.70:9001/?q=${e}`); + } } - function setLightsFromHTML() { let query = document.getElementById("queryInput").value; let stateObject = new Object(); - stateObject.bri_inc = Number(document.getElementById("brightnessInput").value); + stateObject.bri_inc = Number( + document.getElementById("brightnessInput").value + ); stateObject = JSON.stringify(stateObject); console.log(stateObject); setLights(query, stateObject); -}; - +} function syncSetLights(query, state) { setLights(query, state); - fetch(`http://192.168.1.243:31337/?q=syncSetLightsAccessed`) // while (output === undefined) {} - fetch(`http://192.168.1.243:31337/?q=whileLoopExited`) - return query + "\n" + state -}; - + return output; +} function epliSceneTest() { - setLights('eldhús', '{"on": true, "scene": "epli"}'); -}; - + setLights("eldhús", '{"on": true, "scene": "epli"}'); +} function queryTest() { - let query = document.getElementById('queryInput').value; + let query = document.getElementById("queryInput").value; let bool = document.getElementById("boolInput").value; let scene = document.getElementById("sceneInput").value; if (scene === "") { syncSetLights(query, `{"on": ${bool}}`); - } - else{ + } else { syncSetLights(query, `{"scene": "${scene}"}`); } -}; - +} // async function getIdObjectOld(query) { // let allLights = await getAllLights(); diff --git a/queries/js/IoT_Embla/fuse.js b/queries/js/IoT_Embla/fuse.js new file mode 100644 index 00000000..42e7d3b7 --- /dev/null +++ b/queries/js/IoT_Embla/fuse.js @@ -0,0 +1,2240 @@ +/** + * Fuse.js v6.6.2 - Lightweight fuzzy-search (http://fusejs.io) + * + * Copyright (c) 2022 Kiro Risk (http://kiro.me) + * All Rights Reserved. Apache Software License 2.0 + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ + +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Fuse = factory()); +})(this, (function () { 'use strict'; + + function ownKeys(object, enumerableOnly) { + var keys = Object.keys(object); + + if (Object.getOwnPropertySymbols) { + var symbols = Object.getOwnPropertySymbols(object); + enumerableOnly && (symbols = symbols.filter(function (sym) { + return Object.getOwnPropertyDescriptor(object, sym).enumerable; + })), keys.push.apply(keys, symbols); + } + + return keys; + } + + function _objectSpread2(target) { + for (var i = 1; i < arguments.length; i++) { + var source = null != arguments[i] ? arguments[i] : {}; + i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { + _defineProperty(target, key, source[key]); + }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { + Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); + }); + } + + return target; + } + + function _typeof(obj) { + "@babel/helpers - typeof"; + + return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { + return typeof obj; + } : function (obj) { + return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; + }, _typeof(obj); + } + + function _classCallCheck(instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } + } + + function _defineProperties(target, props) { + for (var i = 0; i < props.length; i++) { + var descriptor = props[i]; + descriptor.enumerable = descriptor.enumerable || false; + descriptor.configurable = true; + if ("value" in descriptor) descriptor.writable = true; + Object.defineProperty(target, descriptor.key, descriptor); + } + } + + function _createClass(Constructor, protoProps, staticProps) { + if (protoProps) _defineProperties(Constructor.prototype, protoProps); + if (staticProps) _defineProperties(Constructor, staticProps); + Object.defineProperty(Constructor, "prototype", { + writable: false + }); + return Constructor; + } + + function _defineProperty(obj, key, value) { + if (key in obj) { + Object.defineProperty(obj, key, { + value: value, + enumerable: true, + configurable: true, + writable: true + }); + } else { + obj[key] = value; + } + + return obj; + } + + function _inherits(subClass, superClass) { + if (typeof superClass !== "function" && superClass !== null) { + throw new TypeError("Super expression must either be null or a function"); + } + + Object.defineProperty(subClass, "prototype", { + value: Object.create(superClass && superClass.prototype, { + constructor: { + value: subClass, + writable: true, + configurable: true + } + }), + writable: false + }); + if (superClass) _setPrototypeOf(subClass, superClass); + } + + function _getPrototypeOf(o) { + _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { + return o.__proto__ || Object.getPrototypeOf(o); + }; + return _getPrototypeOf(o); + } + + function _setPrototypeOf(o, p) { + _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { + o.__proto__ = p; + return o; + }; + + return _setPrototypeOf(o, p); + } + + function _isNativeReflectConstruct() { + if (typeof Reflect === "undefined" || !Reflect.construct) return false; + if (Reflect.construct.sham) return false; + if (typeof Proxy === "function") return true; + + try { + Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); + return true; + } catch (e) { + return false; + } + } + + function _assertThisInitialized(self) { + if (self === void 0) { + throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); + } + + return self; + } + + function _possibleConstructorReturn(self, call) { + if (call && (typeof call === "object" || typeof call === "function")) { + return call; + } else if (call !== void 0) { + throw new TypeError("Derived constructors may only return object or undefined"); + } + + return _assertThisInitialized(self); + } + + function _createSuper(Derived) { + var hasNativeReflectConstruct = _isNativeReflectConstruct(); + + return function _createSuperInternal() { + var Super = _getPrototypeOf(Derived), + result; + + if (hasNativeReflectConstruct) { + var NewTarget = _getPrototypeOf(this).constructor; + + result = Reflect.construct(Super, arguments, NewTarget); + } else { + result = Super.apply(this, arguments); + } + + return _possibleConstructorReturn(this, result); + }; + } + + function _toConsumableArray(arr) { + return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); + } + + function _arrayWithoutHoles(arr) { + if (Array.isArray(arr)) return _arrayLikeToArray(arr); + } + + function _iterableToArray(iter) { + if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); + } + + function _unsupportedIterableToArray(o, minLen) { + if (!o) return; + if (typeof o === "string") return _arrayLikeToArray(o, minLen); + var n = Object.prototype.toString.call(o).slice(8, -1); + if (n === "Object" && o.constructor) n = o.constructor.name; + if (n === "Map" || n === "Set") return Array.from(o); + if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); + } + + function _arrayLikeToArray(arr, len) { + if (len == null || len > arr.length) len = arr.length; + + for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; + + return arr2; + } + + function _nonIterableSpread() { + throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); + } + + function isArray(value) { + return !Array.isArray ? getTag(value) === '[object Array]' : Array.isArray(value); + } // Adapted from: https://github.com/lodash/lodash/blob/master/.internal/baseToString.js + + var INFINITY = 1 / 0; + function baseToString(value) { + // Exit early for strings to avoid a performance hit in some environments. + if (typeof value == 'string') { + return value; + } + + var result = value + ''; + return result == '0' && 1 / value == -INFINITY ? '-0' : result; + } + function toString(value) { + return value == null ? '' : baseToString(value); + } + function isString(value) { + return typeof value === 'string'; + } + function isNumber(value) { + return typeof value === 'number'; + } // Adapted from: https://github.com/lodash/lodash/blob/master/isBoolean.js + + function isBoolean(value) { + return value === true || value === false || isObjectLike(value) && getTag(value) == '[object Boolean]'; + } + function isObject(value) { + return _typeof(value) === 'object'; + } // Checks if `value` is object-like. + + function isObjectLike(value) { + return isObject(value) && value !== null; + } + function isDefined(value) { + return value !== undefined && value !== null; + } + function isBlank(value) { + return !value.trim().length; + } // Gets the `toStringTag` of `value`. + // Adapted from: https://github.com/lodash/lodash/blob/master/.internal/getTag.js + + function getTag(value) { + return value == null ? value === undefined ? '[object Undefined]' : '[object Null]' : Object.prototype.toString.call(value); + } + + var EXTENDED_SEARCH_UNAVAILABLE = 'Extended search is not available'; + var INCORRECT_INDEX_TYPE = "Incorrect 'index' type"; + var LOGICAL_SEARCH_INVALID_QUERY_FOR_KEY = function LOGICAL_SEARCH_INVALID_QUERY_FOR_KEY(key) { + return "Invalid value for key ".concat(key); + }; + var PATTERN_LENGTH_TOO_LARGE = function PATTERN_LENGTH_TOO_LARGE(max) { + return "Pattern length exceeds max of ".concat(max, "."); + }; + var MISSING_KEY_PROPERTY = function MISSING_KEY_PROPERTY(name) { + return "Missing ".concat(name, " property in key"); + }; + var INVALID_KEY_WEIGHT_VALUE = function INVALID_KEY_WEIGHT_VALUE(key) { + return "Property 'weight' in key '".concat(key, "' must be a positive integer"); + }; + + var hasOwn = Object.prototype.hasOwnProperty; + + var KeyStore = /*#__PURE__*/function () { + function KeyStore(keys) { + var _this = this; + + _classCallCheck(this, KeyStore); + + this._keys = []; + this._keyMap = {}; + var totalWeight = 0; + keys.forEach(function (key) { + var obj = createKey(key); + totalWeight += obj.weight; + + _this._keys.push(obj); + + _this._keyMap[obj.id] = obj; + totalWeight += obj.weight; + }); // Normalize weights so that their sum is equal to 1 + + this._keys.forEach(function (key) { + key.weight /= totalWeight; + }); + } + + _createClass(KeyStore, [{ + key: "get", + value: function get(keyId) { + return this._keyMap[keyId]; + } + }, { + key: "keys", + value: function keys() { + return this._keys; + } + }, { + key: "toJSON", + value: function toJSON() { + return JSON.stringify(this._keys); + } + }]); + + return KeyStore; + }(); + function createKey(key) { + var path = null; + var id = null; + var src = null; + var weight = 1; + var getFn = null; + + if (isString(key) || isArray(key)) { + src = key; + path = createKeyPath(key); + id = createKeyId(key); + } else { + if (!hasOwn.call(key, 'name')) { + throw new Error(MISSING_KEY_PROPERTY('name')); + } + + var name = key.name; + src = name; + + if (hasOwn.call(key, 'weight')) { + weight = key.weight; + + if (weight <= 0) { + throw new Error(INVALID_KEY_WEIGHT_VALUE(name)); + } + } + + path = createKeyPath(name); + id = createKeyId(name); + getFn = key.getFn; + } + + return { + path: path, + id: id, + weight: weight, + src: src, + getFn: getFn + }; + } + function createKeyPath(key) { + return isArray(key) ? key : key.split('.'); + } + function createKeyId(key) { + return isArray(key) ? key.join('.') : key; + } + + function get(obj, path) { + var list = []; + var arr = false; + + var deepGet = function deepGet(obj, path, index) { + if (!isDefined(obj)) { + return; + } + + if (!path[index]) { + // If there's no path left, we've arrived at the object we care about. + list.push(obj); + } else { + var key = path[index]; + var value = obj[key]; + + if (!isDefined(value)) { + return; + } // If we're at the last value in the path, and if it's a string/number/bool, + // add it to the list + + + if (index === path.length - 1 && (isString(value) || isNumber(value) || isBoolean(value))) { + list.push(toString(value)); + } else if (isArray(value)) { + arr = true; // Search each item in the array. + + for (var i = 0, len = value.length; i < len; i += 1) { + deepGet(value[i], path, index + 1); + } + } else if (path.length) { + // An object. Recurse further. + deepGet(value, path, index + 1); + } + } + }; // Backwards compatibility (since path used to be a string) + + + deepGet(obj, isString(path) ? path.split('.') : path, 0); + return arr ? list : list[0]; + } + + var MatchOptions = { + // Whether the matches should be included in the result set. When `true`, each record in the result + // set will include the indices of the matched characters. + // These can consequently be used for highlighting purposes. + includeMatches: false, + // When `true`, the matching function will continue to the end of a search pattern even if + // a perfect match has already been located in the string. + findAllMatches: false, + // Minimum number of characters that must be matched before a result is considered a match + minMatchCharLength: 1 + }; + var BasicOptions = { + // When `true`, the algorithm continues searching to the end of the input even if a perfect + // match is found before the end of the same input. + isCaseSensitive: false, + // When true, the matching function will continue to the end of a search pattern even if + includeScore: false, + // List of properties that will be searched. This also supports nested properties. + keys: [], + // Whether to sort the result list, by score + shouldSort: true, + // Default sort function: sort by ascending score, ascending index + sortFn: function sortFn(a, b) { + return a.score === b.score ? a.idx < b.idx ? -1 : 1 : a.score < b.score ? -1 : 1; + } + }; + var FuzzyOptions = { + // Approximately where in the text is the pattern expected to be found? + location: 0, + // At what point does the match algorithm give up. A threshold of '0.0' requires a perfect match + // (of both letters and location), a threshold of '1.0' would match anything. + threshold: 0.6, + // Determines how close the match must be to the fuzzy location (specified above). + // An exact letter match which is 'distance' characters away from the fuzzy location + // would score as a complete mismatch. A distance of '0' requires the match be at + // the exact location specified, a threshold of '1000' would require a perfect match + // to be within 800 characters of the fuzzy location to be found using a 0.8 threshold. + distance: 100 + }; + var AdvancedOptions = { + // When `true`, it enables the use of unix-like search commands + useExtendedSearch: false, + // The get function to use when fetching an object's properties. + // The default will search nested paths *ie foo.bar.baz* + getFn: get, + // When `true`, search will ignore `location` and `distance`, so it won't matter + // where in the string the pattern appears. + // More info: https://fusejs.io/concepts/scoring-theory.html#fuzziness-score + ignoreLocation: false, + // When `true`, the calculation for the relevance score (used for sorting) will + // ignore the field-length norm. + // More info: https://fusejs.io/concepts/scoring-theory.html#field-length-norm + ignoreFieldNorm: false, + // The weight to determine how much field length norm effects scoring. + fieldNormWeight: 1 + }; + var Config = _objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2({}, BasicOptions), MatchOptions), FuzzyOptions), AdvancedOptions); + + var SPACE = /[^ ]+/g; // Field-length norm: the shorter the field, the higher the weight. + // Set to 3 decimals to reduce index size. + + function norm() { + var weight = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1; + var mantissa = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 3; + var cache = new Map(); + var m = Math.pow(10, mantissa); + return { + get: function get(value) { + var numTokens = value.match(SPACE).length; + + if (cache.has(numTokens)) { + return cache.get(numTokens); + } // Default function is 1/sqrt(x), weight makes that variable + + + var norm = 1 / Math.pow(numTokens, 0.5 * weight); // In place of `toFixed(mantissa)`, for faster computation + + var n = parseFloat(Math.round(norm * m) / m); + cache.set(numTokens, n); + return n; + }, + clear: function clear() { + cache.clear(); + } + }; + } + + var FuseIndex = /*#__PURE__*/function () { + function FuseIndex() { + var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, + _ref$getFn = _ref.getFn, + getFn = _ref$getFn === void 0 ? Config.getFn : _ref$getFn, + _ref$fieldNormWeight = _ref.fieldNormWeight, + fieldNormWeight = _ref$fieldNormWeight === void 0 ? Config.fieldNormWeight : _ref$fieldNormWeight; + + _classCallCheck(this, FuseIndex); + + this.norm = norm(fieldNormWeight, 3); + this.getFn = getFn; + this.isCreated = false; + this.setIndexRecords(); + } + + _createClass(FuseIndex, [{ + key: "setSources", + value: function setSources() { + var docs = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; + this.docs = docs; + } + }, { + key: "setIndexRecords", + value: function setIndexRecords() { + var records = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; + this.records = records; + } + }, { + key: "setKeys", + value: function setKeys() { + var _this = this; + + var keys = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; + this.keys = keys; + this._keysMap = {}; + keys.forEach(function (key, idx) { + _this._keysMap[key.id] = idx; + }); + } + }, { + key: "create", + value: function create() { + var _this2 = this; + + if (this.isCreated || !this.docs.length) { + return; + } + + this.isCreated = true; // List is Array + + if (isString(this.docs[0])) { + this.docs.forEach(function (doc, docIndex) { + _this2._addString(doc, docIndex); + }); + } else { + // List is Array + this.docs.forEach(function (doc, docIndex) { + _this2._addObject(doc, docIndex); + }); + } + + this.norm.clear(); + } // Adds a doc to the end of the index + + }, { + key: "add", + value: function add(doc) { + var idx = this.size(); + + if (isString(doc)) { + this._addString(doc, idx); + } else { + this._addObject(doc, idx); + } + } // Removes the doc at the specified index of the index + + }, { + key: "removeAt", + value: function removeAt(idx) { + this.records.splice(idx, 1); // Change ref index of every subsquent doc + + for (var i = idx, len = this.size(); i < len; i += 1) { + this.records[i].i -= 1; + } + } + }, { + key: "getValueForItemAtKeyId", + value: function getValueForItemAtKeyId(item, keyId) { + return item[this._keysMap[keyId]]; + } + }, { + key: "size", + value: function size() { + return this.records.length; + } + }, { + key: "_addString", + value: function _addString(doc, docIndex) { + if (!isDefined(doc) || isBlank(doc)) { + return; + } + + var record = { + v: doc, + i: docIndex, + n: this.norm.get(doc) + }; + this.records.push(record); + } + }, { + key: "_addObject", + value: function _addObject(doc, docIndex) { + var _this3 = this; + + var record = { + i: docIndex, + $: {} + }; // Iterate over every key (i.e, path), and fetch the value at that key + + this.keys.forEach(function (key, keyIndex) { + var value = key.getFn ? key.getFn(doc) : _this3.getFn(doc, key.path); + + if (!isDefined(value)) { + return; + } + + if (isArray(value)) { + (function () { + var subRecords = []; + var stack = [{ + nestedArrIndex: -1, + value: value + }]; + + while (stack.length) { + var _stack$pop = stack.pop(), + nestedArrIndex = _stack$pop.nestedArrIndex, + _value = _stack$pop.value; + + if (!isDefined(_value)) { + continue; + } + + if (isString(_value) && !isBlank(_value)) { + var subRecord = { + v: _value, + i: nestedArrIndex, + n: _this3.norm.get(_value) + }; + subRecords.push(subRecord); + } else if (isArray(_value)) { + _value.forEach(function (item, k) { + stack.push({ + nestedArrIndex: k, + value: item + }); + }); + } else ; + } + + record.$[keyIndex] = subRecords; + })(); + } else if (isString(value) && !isBlank(value)) { + var subRecord = { + v: value, + n: _this3.norm.get(value) + }; + record.$[keyIndex] = subRecord; + } + }); + this.records.push(record); + } + }, { + key: "toJSON", + value: function toJSON() { + return { + keys: this.keys, + records: this.records + }; + } + }]); + + return FuseIndex; + }(); + function createIndex(keys, docs) { + var _ref2 = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}, + _ref2$getFn = _ref2.getFn, + getFn = _ref2$getFn === void 0 ? Config.getFn : _ref2$getFn, + _ref2$fieldNormWeight = _ref2.fieldNormWeight, + fieldNormWeight = _ref2$fieldNormWeight === void 0 ? Config.fieldNormWeight : _ref2$fieldNormWeight; + + var myIndex = new FuseIndex({ + getFn: getFn, + fieldNormWeight: fieldNormWeight + }); + myIndex.setKeys(keys.map(createKey)); + myIndex.setSources(docs); + myIndex.create(); + return myIndex; + } + function parseIndex(data) { + var _ref3 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, + _ref3$getFn = _ref3.getFn, + getFn = _ref3$getFn === void 0 ? Config.getFn : _ref3$getFn, + _ref3$fieldNormWeight = _ref3.fieldNormWeight, + fieldNormWeight = _ref3$fieldNormWeight === void 0 ? Config.fieldNormWeight : _ref3$fieldNormWeight; + + var keys = data.keys, + records = data.records; + var myIndex = new FuseIndex({ + getFn: getFn, + fieldNormWeight: fieldNormWeight + }); + myIndex.setKeys(keys); + myIndex.setIndexRecords(records); + return myIndex; + } + + function computeScore$1(pattern) { + var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, + _ref$errors = _ref.errors, + errors = _ref$errors === void 0 ? 0 : _ref$errors, + _ref$currentLocation = _ref.currentLocation, + currentLocation = _ref$currentLocation === void 0 ? 0 : _ref$currentLocation, + _ref$expectedLocation = _ref.expectedLocation, + expectedLocation = _ref$expectedLocation === void 0 ? 0 : _ref$expectedLocation, + _ref$distance = _ref.distance, + distance = _ref$distance === void 0 ? Config.distance : _ref$distance, + _ref$ignoreLocation = _ref.ignoreLocation, + ignoreLocation = _ref$ignoreLocation === void 0 ? Config.ignoreLocation : _ref$ignoreLocation; + + var accuracy = errors / pattern.length; + + if (ignoreLocation) { + return accuracy; + } + + var proximity = Math.abs(expectedLocation - currentLocation); + + if (!distance) { + // Dodge divide by zero error. + return proximity ? 1.0 : accuracy; + } + + return accuracy + proximity / distance; + } + + function convertMaskToIndices() { + var matchmask = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; + var minMatchCharLength = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : Config.minMatchCharLength; + var indices = []; + var start = -1; + var end = -1; + var i = 0; + + for (var len = matchmask.length; i < len; i += 1) { + var match = matchmask[i]; + + if (match && start === -1) { + start = i; + } else if (!match && start !== -1) { + end = i - 1; + + if (end - start + 1 >= minMatchCharLength) { + indices.push([start, end]); + } + + start = -1; + } + } // (i-1 - start) + 1 => i - start + + + if (matchmask[i - 1] && i - start >= minMatchCharLength) { + indices.push([start, i - 1]); + } + + return indices; + } + + // Machine word size + var MAX_BITS = 32; + + function search(text, pattern, patternAlphabet) { + var _ref = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}, + _ref$location = _ref.location, + location = _ref$location === void 0 ? Config.location : _ref$location, + _ref$distance = _ref.distance, + distance = _ref$distance === void 0 ? Config.distance : _ref$distance, + _ref$threshold = _ref.threshold, + threshold = _ref$threshold === void 0 ? Config.threshold : _ref$threshold, + _ref$findAllMatches = _ref.findAllMatches, + findAllMatches = _ref$findAllMatches === void 0 ? Config.findAllMatches : _ref$findAllMatches, + _ref$minMatchCharLeng = _ref.minMatchCharLength, + minMatchCharLength = _ref$minMatchCharLeng === void 0 ? Config.minMatchCharLength : _ref$minMatchCharLeng, + _ref$includeMatches = _ref.includeMatches, + includeMatches = _ref$includeMatches === void 0 ? Config.includeMatches : _ref$includeMatches, + _ref$ignoreLocation = _ref.ignoreLocation, + ignoreLocation = _ref$ignoreLocation === void 0 ? Config.ignoreLocation : _ref$ignoreLocation; + + if (pattern.length > MAX_BITS) { + throw new Error(PATTERN_LENGTH_TOO_LARGE(MAX_BITS)); + } + + var patternLen = pattern.length; // Set starting location at beginning text and initialize the alphabet. + + var textLen = text.length; // Handle the case when location > text.length + + var expectedLocation = Math.max(0, Math.min(location, textLen)); // Highest score beyond which we give up. + + var currentThreshold = threshold; // Is there a nearby exact match? (speedup) + + var bestLocation = expectedLocation; // Performance: only computer matches when the minMatchCharLength > 1 + // OR if `includeMatches` is true. + + var computeMatches = minMatchCharLength > 1 || includeMatches; // A mask of the matches, used for building the indices + + var matchMask = computeMatches ? Array(textLen) : []; + var index; // Get all exact matches, here for speed up + + while ((index = text.indexOf(pattern, bestLocation)) > -1) { + var score = computeScore$1(pattern, { + currentLocation: index, + expectedLocation: expectedLocation, + distance: distance, + ignoreLocation: ignoreLocation + }); + currentThreshold = Math.min(score, currentThreshold); + bestLocation = index + patternLen; + + if (computeMatches) { + var i = 0; + + while (i < patternLen) { + matchMask[index + i] = 1; + i += 1; + } + } + } // Reset the best location + + + bestLocation = -1; + var lastBitArr = []; + var finalScore = 1; + var binMax = patternLen + textLen; + var mask = 1 << patternLen - 1; + + for (var _i = 0; _i < patternLen; _i += 1) { + // Scan for the best match; each iteration allows for one more error. + // Run a binary search to determine how far from the match location we can stray + // at this error level. + var binMin = 0; + var binMid = binMax; + + while (binMin < binMid) { + var _score2 = computeScore$1(pattern, { + errors: _i, + currentLocation: expectedLocation + binMid, + expectedLocation: expectedLocation, + distance: distance, + ignoreLocation: ignoreLocation + }); + + if (_score2 <= currentThreshold) { + binMin = binMid; + } else { + binMax = binMid; + } + + binMid = Math.floor((binMax - binMin) / 2 + binMin); + } // Use the result from this iteration as the maximum for the next. + + + binMax = binMid; + var start = Math.max(1, expectedLocation - binMid + 1); + var finish = findAllMatches ? textLen : Math.min(expectedLocation + binMid, textLen) + patternLen; // Initialize the bit array + + var bitArr = Array(finish + 2); + bitArr[finish + 1] = (1 << _i) - 1; + + for (var j = finish; j >= start; j -= 1) { + var currentLocation = j - 1; + var charMatch = patternAlphabet[text.charAt(currentLocation)]; + + if (computeMatches) { + // Speed up: quick bool to int conversion (i.e, `charMatch ? 1 : 0`) + matchMask[currentLocation] = +!!charMatch; + } // First pass: exact match + + + bitArr[j] = (bitArr[j + 1] << 1 | 1) & charMatch; // Subsequent passes: fuzzy match + + if (_i) { + bitArr[j] |= (lastBitArr[j + 1] | lastBitArr[j]) << 1 | 1 | lastBitArr[j + 1]; + } + + if (bitArr[j] & mask) { + finalScore = computeScore$1(pattern, { + errors: _i, + currentLocation: currentLocation, + expectedLocation: expectedLocation, + distance: distance, + ignoreLocation: ignoreLocation + }); // This match will almost certainly be better than any existing match. + // But check anyway. + + if (finalScore <= currentThreshold) { + // Indeed it is + currentThreshold = finalScore; + bestLocation = currentLocation; // Already passed `loc`, downhill from here on in. + + if (bestLocation <= expectedLocation) { + break; + } // When passing `bestLocation`, don't exceed our current distance from `expectedLocation`. + + + start = Math.max(1, 2 * expectedLocation - bestLocation); + } + } + } // No hope for a (better) match at greater error levels. + + + var _score = computeScore$1(pattern, { + errors: _i + 1, + currentLocation: expectedLocation, + expectedLocation: expectedLocation, + distance: distance, + ignoreLocation: ignoreLocation + }); + + if (_score > currentThreshold) { + break; + } + + lastBitArr = bitArr; + } + + var result = { + isMatch: bestLocation >= 0, + // Count exact matches (those with a score of 0) to be "almost" exact + score: Math.max(0.001, finalScore) + }; + + if (computeMatches) { + var indices = convertMaskToIndices(matchMask, minMatchCharLength); + + if (!indices.length) { + result.isMatch = false; + } else if (includeMatches) { + result.indices = indices; + } + } + + return result; + } + + function createPatternAlphabet(pattern) { + var mask = {}; + + for (var i = 0, len = pattern.length; i < len; i += 1) { + var _char = pattern.charAt(i); + + mask[_char] = (mask[_char] || 0) | 1 << len - i - 1; + } + + return mask; + } + + var BitapSearch = /*#__PURE__*/function () { + function BitapSearch(pattern) { + var _this = this; + + var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, + _ref$location = _ref.location, + location = _ref$location === void 0 ? Config.location : _ref$location, + _ref$threshold = _ref.threshold, + threshold = _ref$threshold === void 0 ? Config.threshold : _ref$threshold, + _ref$distance = _ref.distance, + distance = _ref$distance === void 0 ? Config.distance : _ref$distance, + _ref$includeMatches = _ref.includeMatches, + includeMatches = _ref$includeMatches === void 0 ? Config.includeMatches : _ref$includeMatches, + _ref$findAllMatches = _ref.findAllMatches, + findAllMatches = _ref$findAllMatches === void 0 ? Config.findAllMatches : _ref$findAllMatches, + _ref$minMatchCharLeng = _ref.minMatchCharLength, + minMatchCharLength = _ref$minMatchCharLeng === void 0 ? Config.minMatchCharLength : _ref$minMatchCharLeng, + _ref$isCaseSensitive = _ref.isCaseSensitive, + isCaseSensitive = _ref$isCaseSensitive === void 0 ? Config.isCaseSensitive : _ref$isCaseSensitive, + _ref$ignoreLocation = _ref.ignoreLocation, + ignoreLocation = _ref$ignoreLocation === void 0 ? Config.ignoreLocation : _ref$ignoreLocation; + + _classCallCheck(this, BitapSearch); + + this.options = { + location: location, + threshold: threshold, + distance: distance, + includeMatches: includeMatches, + findAllMatches: findAllMatches, + minMatchCharLength: minMatchCharLength, + isCaseSensitive: isCaseSensitive, + ignoreLocation: ignoreLocation + }; + this.pattern = isCaseSensitive ? pattern : pattern.toLowerCase(); + this.chunks = []; + + if (!this.pattern.length) { + return; + } + + var addChunk = function addChunk(pattern, startIndex) { + _this.chunks.push({ + pattern: pattern, + alphabet: createPatternAlphabet(pattern), + startIndex: startIndex + }); + }; + + var len = this.pattern.length; + + if (len > MAX_BITS) { + var i = 0; + var remainder = len % MAX_BITS; + var end = len - remainder; + + while (i < end) { + addChunk(this.pattern.substr(i, MAX_BITS), i); + i += MAX_BITS; + } + + if (remainder) { + var startIndex = len - MAX_BITS; + addChunk(this.pattern.substr(startIndex), startIndex); + } + } else { + addChunk(this.pattern, 0); + } + } + + _createClass(BitapSearch, [{ + key: "searchIn", + value: function searchIn(text) { + var _this$options = this.options, + isCaseSensitive = _this$options.isCaseSensitive, + includeMatches = _this$options.includeMatches; + + if (!isCaseSensitive) { + text = text.toLowerCase(); + } // Exact match + + + if (this.pattern === text) { + var _result = { + isMatch: true, + score: 0 + }; + + if (includeMatches) { + _result.indices = [[0, text.length - 1]]; + } + + return _result; + } // Otherwise, use Bitap algorithm + + + var _this$options2 = this.options, + location = _this$options2.location, + distance = _this$options2.distance, + threshold = _this$options2.threshold, + findAllMatches = _this$options2.findAllMatches, + minMatchCharLength = _this$options2.minMatchCharLength, + ignoreLocation = _this$options2.ignoreLocation; + var allIndices = []; + var totalScore = 0; + var hasMatches = false; + this.chunks.forEach(function (_ref2) { + var pattern = _ref2.pattern, + alphabet = _ref2.alphabet, + startIndex = _ref2.startIndex; + + var _search = search(text, pattern, alphabet, { + location: location + startIndex, + distance: distance, + threshold: threshold, + findAllMatches: findAllMatches, + minMatchCharLength: minMatchCharLength, + includeMatches: includeMatches, + ignoreLocation: ignoreLocation + }), + isMatch = _search.isMatch, + score = _search.score, + indices = _search.indices; + + if (isMatch) { + hasMatches = true; + } + + totalScore += score; + + if (isMatch && indices) { + allIndices = [].concat(_toConsumableArray(allIndices), _toConsumableArray(indices)); + } + }); + var result = { + isMatch: hasMatches, + score: hasMatches ? totalScore / this.chunks.length : 1 + }; + + if (hasMatches && includeMatches) { + result.indices = allIndices; + } + + return result; + } + }]); + + return BitapSearch; + }(); + + var BaseMatch = /*#__PURE__*/function () { + function BaseMatch(pattern) { + _classCallCheck(this, BaseMatch); + + this.pattern = pattern; + } + + _createClass(BaseMatch, [{ + key: "search", + value: function + /*text*/ + search() {} + }], [{ + key: "isMultiMatch", + value: function isMultiMatch(pattern) { + return getMatch(pattern, this.multiRegex); + } + }, { + key: "isSingleMatch", + value: function isSingleMatch(pattern) { + return getMatch(pattern, this.singleRegex); + } + }]); + + return BaseMatch; + }(); + + function getMatch(pattern, exp) { + var matches = pattern.match(exp); + return matches ? matches[1] : null; + } + + var ExactMatch = /*#__PURE__*/function (_BaseMatch) { + _inherits(ExactMatch, _BaseMatch); + + var _super = _createSuper(ExactMatch); + + function ExactMatch(pattern) { + _classCallCheck(this, ExactMatch); + + return _super.call(this, pattern); + } + + _createClass(ExactMatch, [{ + key: "search", + value: function search(text) { + var isMatch = text === this.pattern; + return { + isMatch: isMatch, + score: isMatch ? 0 : 1, + indices: [0, this.pattern.length - 1] + }; + } + }], [{ + key: "type", + get: function get() { + return 'exact'; + } + }, { + key: "multiRegex", + get: function get() { + return /^="(.*)"$/; + } + }, { + key: "singleRegex", + get: function get() { + return /^=(.*)$/; + } + }]); + + return ExactMatch; + }(BaseMatch); + + var InverseExactMatch = /*#__PURE__*/function (_BaseMatch) { + _inherits(InverseExactMatch, _BaseMatch); + + var _super = _createSuper(InverseExactMatch); + + function InverseExactMatch(pattern) { + _classCallCheck(this, InverseExactMatch); + + return _super.call(this, pattern); + } + + _createClass(InverseExactMatch, [{ + key: "search", + value: function search(text) { + var index = text.indexOf(this.pattern); + var isMatch = index === -1; + return { + isMatch: isMatch, + score: isMatch ? 0 : 1, + indices: [0, text.length - 1] + }; + } + }], [{ + key: "type", + get: function get() { + return 'inverse-exact'; + } + }, { + key: "multiRegex", + get: function get() { + return /^!"(.*)"$/; + } + }, { + key: "singleRegex", + get: function get() { + return /^!(.*)$/; + } + }]); + + return InverseExactMatch; + }(BaseMatch); + + var PrefixExactMatch = /*#__PURE__*/function (_BaseMatch) { + _inherits(PrefixExactMatch, _BaseMatch); + + var _super = _createSuper(PrefixExactMatch); + + function PrefixExactMatch(pattern) { + _classCallCheck(this, PrefixExactMatch); + + return _super.call(this, pattern); + } + + _createClass(PrefixExactMatch, [{ + key: "search", + value: function search(text) { + var isMatch = text.startsWith(this.pattern); + return { + isMatch: isMatch, + score: isMatch ? 0 : 1, + indices: [0, this.pattern.length - 1] + }; + } + }], [{ + key: "type", + get: function get() { + return 'prefix-exact'; + } + }, { + key: "multiRegex", + get: function get() { + return /^\^"(.*)"$/; + } + }, { + key: "singleRegex", + get: function get() { + return /^\^(.*)$/; + } + }]); + + return PrefixExactMatch; + }(BaseMatch); + + var InversePrefixExactMatch = /*#__PURE__*/function (_BaseMatch) { + _inherits(InversePrefixExactMatch, _BaseMatch); + + var _super = _createSuper(InversePrefixExactMatch); + + function InversePrefixExactMatch(pattern) { + _classCallCheck(this, InversePrefixExactMatch); + + return _super.call(this, pattern); + } + + _createClass(InversePrefixExactMatch, [{ + key: "search", + value: function search(text) { + var isMatch = !text.startsWith(this.pattern); + return { + isMatch: isMatch, + score: isMatch ? 0 : 1, + indices: [0, text.length - 1] + }; + } + }], [{ + key: "type", + get: function get() { + return 'inverse-prefix-exact'; + } + }, { + key: "multiRegex", + get: function get() { + return /^!\^"(.*)"$/; + } + }, { + key: "singleRegex", + get: function get() { + return /^!\^(.*)$/; + } + }]); + + return InversePrefixExactMatch; + }(BaseMatch); + + var SuffixExactMatch = /*#__PURE__*/function (_BaseMatch) { + _inherits(SuffixExactMatch, _BaseMatch); + + var _super = _createSuper(SuffixExactMatch); + + function SuffixExactMatch(pattern) { + _classCallCheck(this, SuffixExactMatch); + + return _super.call(this, pattern); + } + + _createClass(SuffixExactMatch, [{ + key: "search", + value: function search(text) { + var isMatch = text.endsWith(this.pattern); + return { + isMatch: isMatch, + score: isMatch ? 0 : 1, + indices: [text.length - this.pattern.length, text.length - 1] + }; + } + }], [{ + key: "type", + get: function get() { + return 'suffix-exact'; + } + }, { + key: "multiRegex", + get: function get() { + return /^"(.*)"\$$/; + } + }, { + key: "singleRegex", + get: function get() { + return /^(.*)\$$/; + } + }]); + + return SuffixExactMatch; + }(BaseMatch); + + var InverseSuffixExactMatch = /*#__PURE__*/function (_BaseMatch) { + _inherits(InverseSuffixExactMatch, _BaseMatch); + + var _super = _createSuper(InverseSuffixExactMatch); + + function InverseSuffixExactMatch(pattern) { + _classCallCheck(this, InverseSuffixExactMatch); + + return _super.call(this, pattern); + } + + _createClass(InverseSuffixExactMatch, [{ + key: "search", + value: function search(text) { + var isMatch = !text.endsWith(this.pattern); + return { + isMatch: isMatch, + score: isMatch ? 0 : 1, + indices: [0, text.length - 1] + }; + } + }], [{ + key: "type", + get: function get() { + return 'inverse-suffix-exact'; + } + }, { + key: "multiRegex", + get: function get() { + return /^!"(.*)"\$$/; + } + }, { + key: "singleRegex", + get: function get() { + return /^!(.*)\$$/; + } + }]); + + return InverseSuffixExactMatch; + }(BaseMatch); + + var FuzzyMatch = /*#__PURE__*/function (_BaseMatch) { + _inherits(FuzzyMatch, _BaseMatch); + + var _super = _createSuper(FuzzyMatch); + + function FuzzyMatch(pattern) { + var _this; + + var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, + _ref$location = _ref.location, + location = _ref$location === void 0 ? Config.location : _ref$location, + _ref$threshold = _ref.threshold, + threshold = _ref$threshold === void 0 ? Config.threshold : _ref$threshold, + _ref$distance = _ref.distance, + distance = _ref$distance === void 0 ? Config.distance : _ref$distance, + _ref$includeMatches = _ref.includeMatches, + includeMatches = _ref$includeMatches === void 0 ? Config.includeMatches : _ref$includeMatches, + _ref$findAllMatches = _ref.findAllMatches, + findAllMatches = _ref$findAllMatches === void 0 ? Config.findAllMatches : _ref$findAllMatches, + _ref$minMatchCharLeng = _ref.minMatchCharLength, + minMatchCharLength = _ref$minMatchCharLeng === void 0 ? Config.minMatchCharLength : _ref$minMatchCharLeng, + _ref$isCaseSensitive = _ref.isCaseSensitive, + isCaseSensitive = _ref$isCaseSensitive === void 0 ? Config.isCaseSensitive : _ref$isCaseSensitive, + _ref$ignoreLocation = _ref.ignoreLocation, + ignoreLocation = _ref$ignoreLocation === void 0 ? Config.ignoreLocation : _ref$ignoreLocation; + + _classCallCheck(this, FuzzyMatch); + + _this = _super.call(this, pattern); + _this._bitapSearch = new BitapSearch(pattern, { + location: location, + threshold: threshold, + distance: distance, + includeMatches: includeMatches, + findAllMatches: findAllMatches, + minMatchCharLength: minMatchCharLength, + isCaseSensitive: isCaseSensitive, + ignoreLocation: ignoreLocation + }); + return _this; + } + + _createClass(FuzzyMatch, [{ + key: "search", + value: function search(text) { + return this._bitapSearch.searchIn(text); + } + }], [{ + key: "type", + get: function get() { + return 'fuzzy'; + } + }, { + key: "multiRegex", + get: function get() { + return /^"(.*)"$/; + } + }, { + key: "singleRegex", + get: function get() { + return /^(.*)$/; + } + }]); + + return FuzzyMatch; + }(BaseMatch); + + var IncludeMatch = /*#__PURE__*/function (_BaseMatch) { + _inherits(IncludeMatch, _BaseMatch); + + var _super = _createSuper(IncludeMatch); + + function IncludeMatch(pattern) { + _classCallCheck(this, IncludeMatch); + + return _super.call(this, pattern); + } + + _createClass(IncludeMatch, [{ + key: "search", + value: function search(text) { + var location = 0; + var index; + var indices = []; + var patternLen = this.pattern.length; // Get all exact matches + + while ((index = text.indexOf(this.pattern, location)) > -1) { + location = index + patternLen; + indices.push([index, location - 1]); + } + + var isMatch = !!indices.length; + return { + isMatch: isMatch, + score: isMatch ? 0 : 1, + indices: indices + }; + } + }], [{ + key: "type", + get: function get() { + return 'include'; + } + }, { + key: "multiRegex", + get: function get() { + return /^'"(.*)"$/; + } + }, { + key: "singleRegex", + get: function get() { + return /^'(.*)$/; + } + }]); + + return IncludeMatch; + }(BaseMatch); + + var searchers = [ExactMatch, IncludeMatch, PrefixExactMatch, InversePrefixExactMatch, InverseSuffixExactMatch, SuffixExactMatch, InverseExactMatch, FuzzyMatch]; + var searchersLen = searchers.length; // Regex to split by spaces, but keep anything in quotes together + + var SPACE_RE = / +(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)/; + var OR_TOKEN = '|'; // Return a 2D array representation of the query, for simpler parsing. + // Example: + // "^core go$ | rb$ | py$ xy$" => [["^core", "go$"], ["rb$"], ["py$", "xy$"]] + + function parseQuery(pattern) { + var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + return pattern.split(OR_TOKEN).map(function (item) { + var query = item.trim().split(SPACE_RE).filter(function (item) { + return item && !!item.trim(); + }); + var results = []; + + for (var i = 0, len = query.length; i < len; i += 1) { + var queryItem = query[i]; // 1. Handle multiple query match (i.e, once that are quoted, like `"hello world"`) + + var found = false; + var idx = -1; + + while (!found && ++idx < searchersLen) { + var searcher = searchers[idx]; + var token = searcher.isMultiMatch(queryItem); + + if (token) { + results.push(new searcher(token, options)); + found = true; + } + } + + if (found) { + continue; + } // 2. Handle single query matches (i.e, once that are *not* quoted) + + + idx = -1; + + while (++idx < searchersLen) { + var _searcher = searchers[idx]; + + var _token = _searcher.isSingleMatch(queryItem); + + if (_token) { + results.push(new _searcher(_token, options)); + break; + } + } + } + + return results; + }); + } + + // to a singl match + + var MultiMatchSet = new Set([FuzzyMatch.type, IncludeMatch.type]); + /** + * Command-like searching + * ====================== + * + * Given multiple search terms delimited by spaces.e.g. `^jscript .python$ ruby !java`, + * search in a given text. + * + * Search syntax: + * + * | Token | Match type | Description | + * | ----------- | -------------------------- | -------------------------------------- | + * | `jscript` | fuzzy-match | Items that fuzzy match `jscript` | + * | `=scheme` | exact-match | Items that are `scheme` | + * | `'python` | include-match | Items that include `python` | + * | `!ruby` | inverse-exact-match | Items that do not include `ruby` | + * | `^java` | prefix-exact-match | Items that start with `java` | + * | `!^earlang` | inverse-prefix-exact-match | Items that do not start with `earlang` | + * | `.js$` | suffix-exact-match | Items that end with `.js` | + * | `!.go$` | inverse-suffix-exact-match | Items that do not end with `.go` | + * + * A single pipe character acts as an OR operator. For example, the following + * query matches entries that start with `core` and end with either`go`, `rb`, + * or`py`. + * + * ``` + * ^core go$ | rb$ | py$ + * ``` + */ + + var ExtendedSearch = /*#__PURE__*/function () { + function ExtendedSearch(pattern) { + var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, + _ref$isCaseSensitive = _ref.isCaseSensitive, + isCaseSensitive = _ref$isCaseSensitive === void 0 ? Config.isCaseSensitive : _ref$isCaseSensitive, + _ref$includeMatches = _ref.includeMatches, + includeMatches = _ref$includeMatches === void 0 ? Config.includeMatches : _ref$includeMatches, + _ref$minMatchCharLeng = _ref.minMatchCharLength, + minMatchCharLength = _ref$minMatchCharLeng === void 0 ? Config.minMatchCharLength : _ref$minMatchCharLeng, + _ref$ignoreLocation = _ref.ignoreLocation, + ignoreLocation = _ref$ignoreLocation === void 0 ? Config.ignoreLocation : _ref$ignoreLocation, + _ref$findAllMatches = _ref.findAllMatches, + findAllMatches = _ref$findAllMatches === void 0 ? Config.findAllMatches : _ref$findAllMatches, + _ref$location = _ref.location, + location = _ref$location === void 0 ? Config.location : _ref$location, + _ref$threshold = _ref.threshold, + threshold = _ref$threshold === void 0 ? Config.threshold : _ref$threshold, + _ref$distance = _ref.distance, + distance = _ref$distance === void 0 ? Config.distance : _ref$distance; + + _classCallCheck(this, ExtendedSearch); + + this.query = null; + this.options = { + isCaseSensitive: isCaseSensitive, + includeMatches: includeMatches, + minMatchCharLength: minMatchCharLength, + findAllMatches: findAllMatches, + ignoreLocation: ignoreLocation, + location: location, + threshold: threshold, + distance: distance + }; + this.pattern = isCaseSensitive ? pattern : pattern.toLowerCase(); + this.query = parseQuery(this.pattern, this.options); + } + + _createClass(ExtendedSearch, [{ + key: "searchIn", + value: function searchIn(text) { + var query = this.query; + + if (!query) { + return { + isMatch: false, + score: 1 + }; + } + + var _this$options = this.options, + includeMatches = _this$options.includeMatches, + isCaseSensitive = _this$options.isCaseSensitive; + text = isCaseSensitive ? text : text.toLowerCase(); + var numMatches = 0; + var allIndices = []; + var totalScore = 0; // ORs + + for (var i = 0, qLen = query.length; i < qLen; i += 1) { + var searchers = query[i]; // Reset indices + + allIndices.length = 0; + numMatches = 0; // ANDs + + for (var j = 0, pLen = searchers.length; j < pLen; j += 1) { + var searcher = searchers[j]; + + var _searcher$search = searcher.search(text), + isMatch = _searcher$search.isMatch, + indices = _searcher$search.indices, + score = _searcher$search.score; + + if (isMatch) { + numMatches += 1; + totalScore += score; + + if (includeMatches) { + var type = searcher.constructor.type; + + if (MultiMatchSet.has(type)) { + allIndices = [].concat(_toConsumableArray(allIndices), _toConsumableArray(indices)); + } else { + allIndices.push(indices); + } + } + } else { + totalScore = 0; + numMatches = 0; + allIndices.length = 0; + break; + } + } // OR condition, so if TRUE, return + + + if (numMatches) { + var result = { + isMatch: true, + score: totalScore / numMatches + }; + + if (includeMatches) { + result.indices = allIndices; + } + + return result; + } + } // Nothing was matched + + + return { + isMatch: false, + score: 1 + }; + } + }], [{ + key: "condition", + value: function condition(_, options) { + return options.useExtendedSearch; + } + }]); + + return ExtendedSearch; + }(); + + var registeredSearchers = []; + function register() { + registeredSearchers.push.apply(registeredSearchers, arguments); + } + function createSearcher(pattern, options) { + for (var i = 0, len = registeredSearchers.length; i < len; i += 1) { + var searcherClass = registeredSearchers[i]; + + if (searcherClass.condition(pattern, options)) { + return new searcherClass(pattern, options); + } + } + + return new BitapSearch(pattern, options); + } + + var LogicalOperator = { + AND: '$and', + OR: '$or' + }; + var KeyType = { + PATH: '$path', + PATTERN: '$val' + }; + + var isExpression = function isExpression(query) { + return !!(query[LogicalOperator.AND] || query[LogicalOperator.OR]); + }; + + var isPath = function isPath(query) { + return !!query[KeyType.PATH]; + }; + + var isLeaf = function isLeaf(query) { + return !isArray(query) && isObject(query) && !isExpression(query); + }; + + var convertToExplicit = function convertToExplicit(query) { + return _defineProperty({}, LogicalOperator.AND, Object.keys(query).map(function (key) { + return _defineProperty({}, key, query[key]); + })); + }; // When `auto` is `true`, the parse function will infer and initialize and add + // the appropriate `Searcher` instance + + + function parse(query, options) { + var _ref3 = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}, + _ref3$auto = _ref3.auto, + auto = _ref3$auto === void 0 ? true : _ref3$auto; + + var next = function next(query) { + var keys = Object.keys(query); + var isQueryPath = isPath(query); + + if (!isQueryPath && keys.length > 1 && !isExpression(query)) { + return next(convertToExplicit(query)); + } + + if (isLeaf(query)) { + var key = isQueryPath ? query[KeyType.PATH] : keys[0]; + var pattern = isQueryPath ? query[KeyType.PATTERN] : query[key]; + + if (!isString(pattern)) { + throw new Error(LOGICAL_SEARCH_INVALID_QUERY_FOR_KEY(key)); + } + + var obj = { + keyId: createKeyId(key), + pattern: pattern + }; + + if (auto) { + obj.searcher = createSearcher(pattern, options); + } + + return obj; + } + + var node = { + children: [], + operator: keys[0] + }; + keys.forEach(function (key) { + var value = query[key]; + + if (isArray(value)) { + value.forEach(function (item) { + node.children.push(next(item)); + }); + } + }); + return node; + }; + + if (!isExpression(query)) { + query = convertToExplicit(query); + } + + return next(query); + } + + function computeScore(results, _ref) { + var _ref$ignoreFieldNorm = _ref.ignoreFieldNorm, + ignoreFieldNorm = _ref$ignoreFieldNorm === void 0 ? Config.ignoreFieldNorm : _ref$ignoreFieldNorm; + results.forEach(function (result) { + var totalScore = 1; + result.matches.forEach(function (_ref2) { + var key = _ref2.key, + norm = _ref2.norm, + score = _ref2.score; + var weight = key ? key.weight : null; + totalScore *= Math.pow(score === 0 && weight ? Number.EPSILON : score, (weight || 1) * (ignoreFieldNorm ? 1 : norm)); + }); + result.score = totalScore; + }); + } + + function transformMatches(result, data) { + var matches = result.matches; + data.matches = []; + + if (!isDefined(matches)) { + return; + } + + matches.forEach(function (match) { + if (!isDefined(match.indices) || !match.indices.length) { + return; + } + + var indices = match.indices, + value = match.value; + var obj = { + indices: indices, + value: value + }; + + if (match.key) { + obj.key = match.key.src; + } + + if (match.idx > -1) { + obj.refIndex = match.idx; + } + + data.matches.push(obj); + }); + } + + function transformScore(result, data) { + data.score = result.score; + } + + function format(results, docs) { + var _ref = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}, + _ref$includeMatches = _ref.includeMatches, + includeMatches = _ref$includeMatches === void 0 ? Config.includeMatches : _ref$includeMatches, + _ref$includeScore = _ref.includeScore, + includeScore = _ref$includeScore === void 0 ? Config.includeScore : _ref$includeScore; + + var transformers = []; + if (includeMatches) transformers.push(transformMatches); + if (includeScore) transformers.push(transformScore); + return results.map(function (result) { + var idx = result.idx; + var data = { + item: docs[idx], + refIndex: idx + }; + + if (transformers.length) { + transformers.forEach(function (transformer) { + transformer(result, data); + }); + } + + return data; + }); + } + + var Fuse$1 = /*#__PURE__*/function () { + function Fuse(docs) { + var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + var index = arguments.length > 2 ? arguments[2] : undefined; + + _classCallCheck(this, Fuse); + + this.options = _objectSpread2(_objectSpread2({}, Config), options); + + if (this.options.useExtendedSearch && !true) { + throw new Error(EXTENDED_SEARCH_UNAVAILABLE); + } + + this._keyStore = new KeyStore(this.options.keys); + this.setCollection(docs, index); + } + + _createClass(Fuse, [{ + key: "setCollection", + value: function setCollection(docs, index) { + this._docs = docs; + + if (index && !(index instanceof FuseIndex)) { + throw new Error(INCORRECT_INDEX_TYPE); + } + + this._myIndex = index || createIndex(this.options.keys, this._docs, { + getFn: this.options.getFn, + fieldNormWeight: this.options.fieldNormWeight + }); + } + }, { + key: "add", + value: function add(doc) { + if (!isDefined(doc)) { + return; + } + + this._docs.push(doc); + + this._myIndex.add(doc); + } + }, { + key: "remove", + value: function remove() { + var predicate = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : function + /* doc, idx */ + () { + return false; + }; + var results = []; + + for (var i = 0, len = this._docs.length; i < len; i += 1) { + var doc = this._docs[i]; + + if (predicate(doc, i)) { + this.removeAt(i); + i -= 1; + len -= 1; + results.push(doc); + } + } + + return results; + } + }, { + key: "removeAt", + value: function removeAt(idx) { + this._docs.splice(idx, 1); + + this._myIndex.removeAt(idx); + } + }, { + key: "getIndex", + value: function getIndex() { + return this._myIndex; + } + }, { + key: "search", + value: function search(query) { + var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, + _ref$limit = _ref.limit, + limit = _ref$limit === void 0 ? -1 : _ref$limit; + + var _this$options = this.options, + includeMatches = _this$options.includeMatches, + includeScore = _this$options.includeScore, + shouldSort = _this$options.shouldSort, + sortFn = _this$options.sortFn, + ignoreFieldNorm = _this$options.ignoreFieldNorm; + var results = isString(query) ? isString(this._docs[0]) ? this._searchStringList(query) : this._searchObjectList(query) : this._searchLogical(query); + computeScore(results, { + ignoreFieldNorm: ignoreFieldNorm + }); + + if (shouldSort) { + results.sort(sortFn); + } + + if (isNumber(limit) && limit > -1) { + results = results.slice(0, limit); + } + + return format(results, this._docs, { + includeMatches: includeMatches, + includeScore: includeScore + }); + } + }, { + key: "_searchStringList", + value: function _searchStringList(query) { + var searcher = createSearcher(query, this.options); + var records = this._myIndex.records; + var results = []; // Iterate over every string in the index + + records.forEach(function (_ref2) { + var text = _ref2.v, + idx = _ref2.i, + norm = _ref2.n; + + if (!isDefined(text)) { + return; + } + + var _searcher$searchIn = searcher.searchIn(text), + isMatch = _searcher$searchIn.isMatch, + score = _searcher$searchIn.score, + indices = _searcher$searchIn.indices; + + if (isMatch) { + results.push({ + item: text, + idx: idx, + matches: [{ + score: score, + value: text, + norm: norm, + indices: indices + }] + }); + } + }); + return results; + } + }, { + key: "_searchLogical", + value: function _searchLogical(query) { + var _this = this; + + var expression = parse(query, this.options); + + var evaluate = function evaluate(node, item, idx) { + if (!node.children) { + var keyId = node.keyId, + searcher = node.searcher; + + var matches = _this._findMatches({ + key: _this._keyStore.get(keyId), + value: _this._myIndex.getValueForItemAtKeyId(item, keyId), + searcher: searcher + }); + + if (matches && matches.length) { + return [{ + idx: idx, + item: item, + matches: matches + }]; + } + + return []; + } + + var res = []; + + for (var i = 0, len = node.children.length; i < len; i += 1) { + var child = node.children[i]; + var result = evaluate(child, item, idx); + + if (result.length) { + res.push.apply(res, _toConsumableArray(result)); + } else if (node.operator === LogicalOperator.AND) { + return []; + } + } + + return res; + }; + + var records = this._myIndex.records; + var resultMap = {}; + var results = []; + records.forEach(function (_ref3) { + var item = _ref3.$, + idx = _ref3.i; + + if (isDefined(item)) { + var expResults = evaluate(expression, item, idx); + + if (expResults.length) { + // Dedupe when adding + if (!resultMap[idx]) { + resultMap[idx] = { + idx: idx, + item: item, + matches: [] + }; + results.push(resultMap[idx]); + } + + expResults.forEach(function (_ref4) { + var _resultMap$idx$matche; + + var matches = _ref4.matches; + + (_resultMap$idx$matche = resultMap[idx].matches).push.apply(_resultMap$idx$matche, _toConsumableArray(matches)); + }); + } + } + }); + return results; + } + }, { + key: "_searchObjectList", + value: function _searchObjectList(query) { + var _this2 = this; + + var searcher = createSearcher(query, this.options); + var _this$_myIndex = this._myIndex, + keys = _this$_myIndex.keys, + records = _this$_myIndex.records; + var results = []; // List is Array + + records.forEach(function (_ref5) { + var item = _ref5.$, + idx = _ref5.i; + + if (!isDefined(item)) { + return; + } + + var matches = []; // Iterate over every key (i.e, path), and fetch the value at that key + + keys.forEach(function (key, keyIndex) { + matches.push.apply(matches, _toConsumableArray(_this2._findMatches({ + key: key, + value: item[keyIndex], + searcher: searcher + }))); + }); + + if (matches.length) { + results.push({ + idx: idx, + item: item, + matches: matches + }); + } + }); + return results; + } + }, { + key: "_findMatches", + value: function _findMatches(_ref6) { + var key = _ref6.key, + value = _ref6.value, + searcher = _ref6.searcher; + + if (!isDefined(value)) { + return []; + } + + var matches = []; + + if (isArray(value)) { + value.forEach(function (_ref7) { + var text = _ref7.v, + idx = _ref7.i, + norm = _ref7.n; + + if (!isDefined(text)) { + return; + } + + var _searcher$searchIn2 = searcher.searchIn(text), + isMatch = _searcher$searchIn2.isMatch, + score = _searcher$searchIn2.score, + indices = _searcher$searchIn2.indices; + + if (isMatch) { + matches.push({ + score: score, + key: key, + value: text, + idx: idx, + norm: norm, + indices: indices + }); + } + }); + } else { + var text = value.v, + norm = value.n; + + var _searcher$searchIn3 = searcher.searchIn(text), + isMatch = _searcher$searchIn3.isMatch, + score = _searcher$searchIn3.score, + indices = _searcher$searchIn3.indices; + + if (isMatch) { + matches.push({ + score: score, + key: key, + value: text, + norm: norm, + indices: indices + }); + } + } + + return matches; + } + }]); + + return Fuse; + }(); + + Fuse$1.version = '6.6.2'; + Fuse$1.createIndex = createIndex; + Fuse$1.parseIndex = parseIndex; + Fuse$1.config = Config; + + { + Fuse$1.parseQuery = parse; + } + + { + register(ExtendedSearch); + } + + var Fuse = Fuse$1; + + return Fuse; + +})); From 0be924c28d8aa7d59c3ecb16d62360b0412222b8 Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Mon, 13 Jun 2022 10:43:14 +0000 Subject: [PATCH 015/371] SetLights refactor and console.log deletion SetLights function refactored. Now uses promises to more efficiently get all lights, all groups and all scenes asynchronously . --- .../js/IoT_Embla/Philips_Hue/fuse_search.js | 2 +- queries/js/IoT_Embla/Philips_Hue/hub.js | 2 +- .../js/IoT_Embla/Philips_Hue/set_lights.js | 116 ++++++++---------- 3 files changed, 53 insertions(+), 67 deletions(-) diff --git a/queries/js/IoT_Embla/Philips_Hue/fuse_search.js b/queries/js/IoT_Embla/Philips_Hue/fuse_search.js index bcd70052..36bc3075 100644 --- a/queries/js/IoT_Embla/Philips_Hue/fuse_search.js +++ b/queries/js/IoT_Embla/Philips_Hue/fuse_search.js @@ -1,4 +1,4 @@ -async function philipsFuzzySearch(query, data) { +function philipsFuzzySearch(query, data) { var newData = Object.keys(data).map(function(key) { return { ID: key, info: data[key]}; }); diff --git a/queries/js/IoT_Embla/Philips_Hue/hub.js b/queries/js/IoT_Embla/Philips_Hue/hub.js index 6ca0af14..96e19d5a 100644 --- a/queries/js/IoT_Embla/Philips_Hue/hub.js +++ b/queries/js/IoT_Embla/Philips_Hue/hub.js @@ -94,7 +94,7 @@ async function connectHub(clientID, requestURL) { function syncConnectHub() { let clientID = 'AB8C8D7E-20F5-4772-BD69-313EA9DAFBD8' - let requestURl = '192.168.1.70:5000' + let requestURl = '192.168.1.69:5000' connectHub(clientID, requestURl); return "blabla"; }; diff --git a/queries/js/IoT_Embla/Philips_Hue/set_lights.js b/queries/js/IoT_Embla/Philips_Hue/set_lights.js index 2d050e3e..e21dcd58 100644 --- a/queries/js/IoT_Embla/Philips_Hue/set_lights.js +++ b/queries/js/IoT_Embla/Philips_Hue/set_lights.js @@ -9,38 +9,25 @@ var USERNAME = "q2jNarhGHO9izO0xZZXcoww5GYANGi6mZyJYgMdL"; // const LIGHTS_EX = '{"1":{"state":{"on":true,"bri":100,"hue":6140,"sat":232,"effect":"none","xy":[0.5503,0.4000],"ct":500,"alert":"select","colormode":"xy","mode":"homeautomation","reachable":true},"swupdate":{"state":"noupdates","lastinstall":"2022-05-27T14:23:54"},"type":"Extended color light","name":"litaljós","modelid":"LCA001","manufacturername":"Signify Netherlands B.V.","productname":"Hue color lamp","capabilities":{"certified":true,"control":{"mindimlevel":200,"maxlumen":800,"colorgamuttype":"C","colorgamut":[[0.6915,0.3083],[0.1700,0.7000],[0.1532,0.0475]],"ct":{"min":153,"max":500}},"streaming":{"renderer":true,"proxy":true}},"config":{"archetype":"sultanbulb","function":"mixed","direction":"omnidirectional","startup":{"mode":"safety","configured":true}},"uniqueid":"00:17:88:01:06:79:c3:94-0b","swversion":"1.93.7","swconfigid":"3C05E7B6","productid":"Philips-LCA001-5-A19ECLv6"},"2":{"state":{"on":true,"bri":2,"ct":454,"alert":"select","colormode":"ct","mode":"homeautomation","reachable":false},"swupdate":{"state":"notupdatable","lastinstall":"2020-06-29T12:05:21"},"type":"Color temperature light","name":"Ikea pera Uno","modelid":"TRADFRI bulb E27 WS opal 1000lm","manufacturername":"IKEA of Sweden","productname":"Color temperature light","capabilities":{"certified":false,"control":{"ct":{"min":250,"max":454}},"streaming":{"renderer":false,"proxy":false}},"config":{"archetype":"classicbulb","function":"functional","direction":"omnidirectional"},"uniqueid":"cc:cc:cc:ff:fe:02:92:52-01","swversion":"2.0.023"},"3":{"state":{"on":true,"bri":105,"ct":454,"alert":"select","colormode":"ct","mode":"homeautomation","reachable":false},"swupdate":{"state":"notupdatable","lastinstall":"2020-07-20T13:03:26"},"type":"Color temperature light","name":"lesljós","modelid":"TRADFRI bulb E14 WS opal 400lm","manufacturername":"IKEA of Sweden","productname":"Color temperature light","capabilities":{"certified":false,"control":{"ct":{"min":250,"max":454}},"streaming":{"renderer":false,"proxy":false}},"config":{"archetype":"classicbulb","function":"functional","direction":"omnidirectional"},"uniqueid":"90:fd:9f:ff:fe:93:be:a1-01","swversion":"1.2.217"}}' // const GROUPS_EX = '{"1":{"name":"eldhús","lights":["1"],"sensors":[],"type":"Room","state":{"all_on":true,"any_on":true},"recycle":false,"class":"Living room","action":{"on":true,"bri":100,"hue":6140,"sat":232,"effect":"none","xy":[0.5503,0.4000],"ct":500,"alert":"select","colormode":"xy"}},"2":{"name":"Hús","lights":["3","2","1"],"sensors":[],"type":"Zone","state":{"all_on":true,"any_on":true},"recycle":false,"class":"Downstairs","action":{"on":true,"bri":100,"hue":6140,"sat":232,"effect":"none","xy":[0.5503,0.4000],"ct":500,"alert":"select","colormode":"xy"}},"3":{"name":"skrifstofa","lights":["2","3"],"sensors":[],"type":"Room","state":{"all_on":true,"any_on":true},"recycle":false,"class":"Living room","action":{"on":true,"bri":105,"ct":454,"alert":"select","colormode":"ct"}}}' - -async function getIdObject(query) { - let allLights = await getAllLights(); - let allGroups = await getAllGroups(); - let returnObject - - console.log("all lights: ", allLights); - console.log("query :", query); - - let lightsResult = await philipsFuzzySearch(query, allLights); - let groupsResult = await philipsFuzzySearch(query, allGroups); - - console.log("lightResult :", lightsResult); - console.log("groupsResult :", groupsResult); - if (lightsResult != null) { - returnObject = {id: lightsResult.ID, type: "light", url: `lights/${lightsResult.ID}/state`}; - console.log("returnObject: ", returnObject); - } - else if (groupsResult != null) { - console.log("groupresult test") - returnObject = {id: groupsResult.ID, type: "group", url: `groups/${groupsResult.ID}/action`}; - console.log("returnObject: ",returnObject); - } - console.log("returnObject: ",returnObject) - return returnObject; - //vantar e.k. error +/** Finds a matching light or group and returns an object with the ID, name and url for the target */ +function getTargetObject(query, allLights, allGroups) { + let returnObject + let lightsResult = philipsFuzzySearch(query, allLights); + let groupsResult = philipsFuzzySearch(query, allGroups); + + if (lightsResult != null) { + returnObject = {id: lightsResult.ID, type: "light", url: `lights/${lightsResult.ID}/state`}; + } + else if (groupsResult != null) { + returnObject = {id: groupsResult.ID, type: "group", url: `groups/${groupsResult.ID}/action`}; + } + return returnObject; + //vantar e.k. error }; -async function getSceneID(scene_name){ - let allScenes = await getAllScenes(); - let scenesResult = await philipsFuzzySearch(scene_name, allScenes); +async function getSceneID(scene_name, allScenes){ + let scenesResult = philipsFuzzySearch(scene_name, allScenes); if (scenesResult != null) { return scenesResult.ID; } @@ -49,38 +36,55 @@ async function getSceneID(scene_name){ } }; - +/* Gets a target for the given query and sets the state of the target to the given state using a fetch request +* @param query - the query to find the target +* @param state - the state to set the target to +* example of paramaters: query = "eldhús" state = "{"on": true}" +*/ async function setLights(query, state){ - let idObject = await getIdObject(query); - if (idObject === undefined) { + let parsed_state = JSON.parse(state); + let promiseList = [getAllGroups(), getAllLights()]; + if(parsed_state.scene) { + promiseList.push(getAllScenes()); + } + // Get all lights and all groups from the API + Promise + .allSettled(promiseList) + .then( + (resolvedPromises) => { + let allGroups = resolvedPromises[0].value; + let allLights = resolvedPromises[1].value; + let allScenes; + try{ + allScenes = resolvedPromises[2].value; + } + catch(e) { + console.log("no scenes"); + } + if (allScenes != undefined){ + } + + let targetObject = getTargetObject(query, allLights, allGroups); + if (targetObject === undefined) { return "Ekki tókst að finna ljós" }; - let ID = idObject.id; - let parsed_state = JSON.parse(state); - console.log("parsed state :", parsed_state); // Check if state includes a scene or a brightness change if (parsed_state.scene) { - let sceneID = await getSceneID(parsed_state.scene); - console.log("sceneID :", sceneID) + let sceneID = getSceneID(parsed_state.scene, allScenes); if (sceneID === undefined) { return "Ekki tókst að finna senu" } else { - console.log("tókst") parsed_state.scene = sceneID; state = JSON.stringify(parsed_state); } } else if (parsed_state.bri_inc) { - console.log(parsed_state.bri_inc); state = JSON.stringify(parsed_state); } // Send data to API - let url = idObject.url; - console.log("url", url) - console.log(state); - console.log("idObject", idObject); + let url = targetObject.url; fetch(`http://${BRIDGE_IP}/api/${USERNAME}/${url}`, { method: "PUT", body: state, @@ -92,6 +96,8 @@ async function setLights(query, state){ .catch((err) => { console.log("an error occurred!"); }); + } + ) }; @@ -100,7 +106,6 @@ function setLightsFromHTML() { let stateObject = new Object(); stateObject.bri_inc = Number(document.getElementById("brightnessInput").value); stateObject = JSON.stringify(stateObject); - console.log(stateObject); setLights(query, stateObject); }; @@ -111,6 +116,7 @@ function syncSetLights(query, state) { }; + function epliSceneTest() { setLights('eldhús', '{"on": true, "scene": "epli"}'); }; @@ -126,24 +132,4 @@ function queryTest() { else{ syncSetLights(query, `{"scene": "${scene}"}`); } -}; - - -// async function getIdObjectOld(query) { -// let allLights = await getAllLights(); -// let allGroups = await getAllGroups(); - -// for (let light in allLights) { -// if (allLights[light].name === query) { -// let returnObject = {id: light, type: "light", url: `lights/${light}/state`}; -// return returnObject; -// } -// } -// for (let group in allGroups) { -// if (allGroups[group].name === query) { -// let returnObject = {id: group, type: "group", url: `groups/${group}/action`}; -// return returnObject; -// } -// } -// //vantar e.k. error -// return; \ No newline at end of file +}; \ No newline at end of file From bd1c69c1cfdff2098d972586ab6d9becd3adf875 Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Mon, 13 Jun 2022 10:56:03 +0000 Subject: [PATCH 016/371] merge fix --- .../js/IoT_Embla/Philips_Hue/set_lights.js | 52 ++++++------------- 1 file changed, 16 insertions(+), 36 deletions(-) diff --git a/queries/js/IoT_Embla/Philips_Hue/set_lights.js b/queries/js/IoT_Embla/Philips_Hue/set_lights.js index 37693ae3..e21dcd58 100644 --- a/queries/js/IoT_Embla/Philips_Hue/set_lights.js +++ b/queries/js/IoT_Embla/Philips_Hue/set_lights.js @@ -25,37 +25,16 @@ function getTargetObject(query, allLights, allGroups) { //vantar e.k. error }; - console.log("lightResult :", lightsResult); - console.log("groupsResult :", groupsResult); - if (lightsResult != null) { - returnObject = { - id: lightsResult.ID, - type: "light", - url: `lights/${lightsResult.ID}/state`, - }; - console.log("returnObject: ", returnObject); - } else if (groupsResult != null) { - console.log("groupresult test"); - returnObject = { - id: groupsResult.ID, - type: "group", - url: `groups/${groupsResult.ID}/action`, - }; - console.log("returnObject: ", returnObject); - } - console.log("returnObject: ", returnObject); - return returnObject; - //vantar e.k. error -} async function getSceneID(scene_name, allScenes){ let scenesResult = philipsFuzzySearch(scene_name, allScenes); if (scenesResult != null) { return scenesResult.ID; - } else { + } + else { return; } -} +}; /* Gets a target for the given query and sets the state of the target to the given state using a fetch request * @param query - the query to find the target @@ -125,31 +104,32 @@ async function setLights(query, state){ function setLightsFromHTML() { let query = document.getElementById("queryInput").value; let stateObject = new Object(); - stateObject.bri_inc = Number( - document.getElementById("brightnessInput").value - ); + stateObject.bri_inc = Number(document.getElementById("brightnessInput").value); stateObject = JSON.stringify(stateObject); setLights(query, stateObject); -} +}; + function syncSetLights(query, state) { setLights(query, state); - // while (output === undefined) {} - return output; -} + return query; +}; + function epliSceneTest() { - setLights("eldhús", '{"on": true, "scene": "epli"}'); -} + setLights('eldhús', '{"on": true, "scene": "epli"}'); +}; + function queryTest() { - let query = document.getElementById("queryInput").value; + let query = document.getElementById('queryInput').value; let bool = document.getElementById("boolInput").value; let scene = document.getElementById("sceneInput").value; if (scene === "") { syncSetLights(query, `{"on": ${bool}}`); - } else { + } + else{ syncSetLights(query, `{"scene": "${scene}"}`); } -}; +}; \ No newline at end of file From 22db5c5b26ca26b9d88c42e8ad5be8afaa696593 Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Mon, 13 Jun 2022 11:23:25 +0000 Subject: [PATCH 017/371] javascript fixes --- .../js/IoT_Embla/Philips_Hue/fuse_search.js | 23 ++++--- queries/js/IoT_Embla/Philips_Hue/hub.js | 61 +++++++++---------- queries/js/IoT_Embla/Philips_Hue/lights.js | 60 ++++++++++-------- queries/js/IoT_Embla/Philips_Hue/scenes.js | 7 ++- 4 files changed, 80 insertions(+), 71 deletions(-) diff --git a/queries/js/IoT_Embla/Philips_Hue/fuse_search.js b/queries/js/IoT_Embla/Philips_Hue/fuse_search.js index 36bc3075..50e628df 100644 --- a/queries/js/IoT_Embla/Philips_Hue/fuse_search.js +++ b/queries/js/IoT_Embla/Philips_Hue/fuse_search.js @@ -1,22 +1,21 @@ function philipsFuzzySearch(query, data) { - var newData = Object.keys(data).map(function(key) { - return { ID: key, info: data[key]}; + var newData = Object.keys(data).map(function (key) { + return { ID: key, info: data[key] }; }); var fuse = new Fuse(newData, { - keys: ["info", "info.name"], - shouldSort: true, - threshold: 0.5, + keys: ["info", "info.name"], + shouldSort: true, + threshold: 0.5, }); let searchTerm = query; let result = fuse.search(searchTerm); - + console.log("result: ", result); if (result[0] === undefined) { - console.log("no match found"); - return null; - } - else { - return result[0].item; + console.log("no match found"); + return null; + } else { + return result[0].item; } -}; \ No newline at end of file +} diff --git a/queries/js/IoT_Embla/Philips_Hue/hub.js b/queries/js/IoT_Embla/Philips_Hue/hub.js index 96e19d5a..320de8f1 100644 --- a/queries/js/IoT_Embla/Philips_Hue/hub.js +++ b/queries/js/IoT_Embla/Philips_Hue/hub.js @@ -11,17 +11,17 @@ async function findHub() { .then((resp) => resp.json()) .then((obj) => { console.log(obj); - return(obj[0]); + return obj[0]; }) .catch((err) => { console.log("No smart device found!"); }); -}; +} async function createNewDeveloper(ipAddress) { console.log("create new developer"); const body = JSON.stringify({ - 'devicetype': 'mideind_hue_communication#smartdevice' + devicetype: "mideind_hue_communication#smartdevice", }); return fetch(`http://${ipAddress}/api`, { method: "POST", @@ -29,13 +29,12 @@ async function createNewDeveloper(ipAddress) { }) .then((resp) => resp.json()) .then((obj) => { - return(obj[0]); + return obj[0]; }) .catch((err) => { console.log(err); }); -}; - +} async function storeDevice(data, requestURL) { console.log("store device"); @@ -43,58 +42,58 @@ async function storeDevice(data, requestURL) { method: "POST", body: JSON.stringify(data), headers: { - 'Content-Type': 'application/json' - }, + "Content-Type": "application/json", + }, }) .then((resp) => resp.json()) .then((obj) => { - return(obj); + return obj; }) .catch((err) => { console.log("Error while storing user"); }); -}; +} // clientID = "82AD3C91-7DA2-4502-BB17-075CEC090B14", requestURL = "192.168.1.68") async function connectHub(clientID, requestURL) { console.log("connect hub"); let deviceInfo = await findHub(); console.log("device info: ", deviceInfo); - console.log("device_ip :", deviceInfo.internalipaddress) + console.log("device_ip :", deviceInfo.internalipaddress); try { let username = await createNewDeveloper(deviceInfo.internalipaddress); - console.log("username: ",username); + console.log("username: ", username); if (!username.success) { - return 'Ýttu á \'Philips\' takkann á tengiboxinu og reyndu aftur'; + return "Ýttu á 'Philips' takkann á tengiboxinu og reyndu aftur"; } const data = { - 'client_id': clientID, - 'key': 'smartlights', - 'data': { - 'smartlights': { - 'selected_light': 'philips_hue', - 'philips_hue': { - 'username':username.success.username, - 'ipAddress':deviceInfo.internalipaddress - } - } - } + client_id: clientID, + key: "smartlights", + data: { + smartlights: { + selected_light: "philips_hue", + philips_hue: { + username: username.success.username, + ipAddress: deviceInfo.internalipaddress, + }, + }, + }, }; const result = await storeDevice(data, requestURL); console.log("result: ", result); - return 'Tenging við snjalltæki tókst'; - } catch(error) { + return "Tenging við snjalltæki tókst"; + } catch (error) { console.log(error); - return 'Ekki tókst að tengja snjalltæki'; + return "Ekki tókst að tengja snjalltæki"; } -}; +} function syncConnectHub() { - let clientID = 'AB8C8D7E-20F5-4772-BD69-313EA9DAFBD8' - let requestURl = '192.168.1.69:5000' + let clientID = "AB8C8D7E-20F5-4772-BD69-313EA9DAFBD8"; + let requestURl = "192.168.1.69:5000"; connectHub(clientID, requestURl); return "blabla"; -}; +} diff --git a/queries/js/IoT_Embla/Philips_Hue/lights.js b/queries/js/IoT_Embla/Philips_Hue/lights.js index 2d66b8a5..39371c92 100644 --- a/queries/js/IoT_Embla/Philips_Hue/lights.js +++ b/queries/js/IoT_Embla/Philips_Hue/lights.js @@ -6,7 +6,7 @@ function changeBrightness() { fetch(`http://${BRIDGE_IP}/api/${USERNAME}/lights/1/state`, { method: "PUT", - body: JSON.stringify({ bri: Number(sliderValue)}) + body: JSON.stringify({ bri: Number(sliderValue) }), }) .then((resp) => resp.json()) .then((obj) => { @@ -22,16 +22,23 @@ function changeColor() { let yValue = Number(document.getElementById("color_y").value); console.log(xValue, yValue); - if(xValue === undefined || yValue === undefined || xValue > 1 || xValue < 0 || yValue > 1 || yValue < 0) { - document.getElementById("color_error").innerHTML = "Please enter a value between 0 and 1."; - } - else { + if ( + xValue === undefined || + yValue === undefined || + xValue > 1 || + xValue < 0 || + yValue > 1 || + yValue < 0 + ) { + document.getElementById("color_error").innerHTML = + "Please enter a value between 0 and 1."; + } else { document.getElementById("color_error").innerHTML = ""; - let colorValue = [Number(xValue), Number(yValue)] + let colorValue = [Number(xValue), Number(yValue)]; console.log(colorValue); fetch(`http://${BRIDGE_IP}/api/${USERNAME}/lights/1/state`, { method: "PUT", - body: JSON.stringify({ xy: colorValue }) + body: JSON.stringify({ xy: colorValue }), }) .then((resp) => resp.json()) .then((obj) => { @@ -41,35 +48,38 @@ function changeColor() { console.log("an error occurred!"); }); } - } - function getAllLights(hub_ip = BRIDGE_IP, username = USERNAME) { - return fetch(`http://${hub_ip}/api/${username}/lights`) - .then((resp) => resp.json()); -}; + return fetch(`http://${hub_ip}/api/${username}/lights`).then((resp) => + resp.json() + ); +} function getAllGroups(hub_ip = BRIDGE_IP, username = USERNAME) { - return fetch(`http://${hub_ip}/api/${username}/groups`) - .then((resp) => resp.json()); -}; + return fetch(`http://${hub_ip}/api/${username}/groups`).then((resp) => + resp.json() + ); +} -function getAllScenes(hub_ip = "192.168.1.68", username = "q2jNarhGHO9izO0xZZXcoww5GYANGi6mZyJYgMdL") { - return fetch(`http://${hub_ip}/api/${username}/scenes`) - .then((resp) => resp.json()) +function getAllScenes( + hub_ip = "192.168.1.68", + username = "q2jNarhGHO9izO0xZZXcoww5GYANGi6mZyJYgMdL" +) { + return fetch(`http://${hub_ip}/api/${username}/scenes`).then((resp) => + resp.json() + ); } function getCurrentState(id) { - return fetch(`http://${BRIDGE_IP}/api/${USERNAME}/lights/${id}`) - .then((resp) => resp.json()); -}; + return fetch(`http://${BRIDGE_IP}/api/${USERNAME}/lights/${id}`).then( + (resp) => resp.json() + ); +} -async function getAllLightsAndGroupsFromHTML(){ +async function getAllLightsAndGroupsFromHTML() { var lights = await getAllLights(); var groups = await getAllGroups(); console.log("lights:", lights); console.log("groups:", groups); -}; - - +} diff --git a/queries/js/IoT_Embla/Philips_Hue/scenes.js b/queries/js/IoT_Embla/Philips_Hue/scenes.js index 439fb5ec..51774bf8 100644 --- a/queries/js/IoT_Embla/Philips_Hue/scenes.js +++ b/queries/js/IoT_Embla/Philips_Hue/scenes.js @@ -1,4 +1,5 @@ function getAllScenes(hub_ip = BRIDGE_IP, username = USERNAME) { - return fetch(`http://${hub_ip}/api/${username}/scenes`) - .then((resp) => resp.json()); -}; \ No newline at end of file + return fetch(`http://${hub_ip}/api/${username}/scenes`).then((resp) => + resp.json() + ); +} From e89e0fc5c8a6b46692681a758d92177bb9c49db0 Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Mon, 13 Jun 2022 11:47:20 +0000 Subject: [PATCH 018/371] python style fixes --- queries/iot_hue.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/queries/iot_hue.py b/queries/iot_hue.py index f3fbac20..93406dca 100755 --- a/queries/iot_hue.py +++ b/queries/iot_hue.py @@ -59,11 +59,15 @@ def help_text(lemma: str) -> str: """Help text to return when query.py is unable to parse a query but one of the above lemmas is found in it""" return "Ég skil þig ef þú segir til dæmis: {0}.".format( - random.choice(("Kveiktu á ljósunum inni í eldhúsi.", - "Slökktu á leslampanum.", - "Breyttu lit lýsingarinnar í stofunni í bláan.", - "Gerðu ljósið í borðstofunni bjartara.", - "Stilltu á bjartasta niðri í kjallara.")) + random.choice( + ( + "Kveiktu á ljósunum inni í eldhúsi.", + "Slökktu á leslampanum.", + "Breyttu lit lýsingarinnar í stofunni í bláan.", + "Gerðu ljósið í borðstofunni bjartara.", + "Stilltu á bjartasta niðri í kjallara.", + ) + ) ) @@ -588,7 +592,7 @@ def sentence(state: QueryStateDict, result: Result) -> None: # username = hue_credentials.get("username") # if not device_data or not hue_credentials: - + # js = read_jsfile("IoT_Embla/Philips_Hue/hub.js") # js += f"syncConnectHub('{host}','{client_id}');" # q.set_answer(*gen_answer("blabla")) @@ -601,7 +605,7 @@ def sentence(state: QueryStateDict, result: Result) -> None: try: # kalla í javascripts stuff - light_or_group_name = result.get("light_name", result.get("group_name", "")) + light_or_group_name = result.get("light_name", result.get("group_name", "")) color_name = result.get("color_name", "") print("GROUP NAME:", light_or_group_name) print("COLOR NAME:", color_name) From 232c101aa2385ee65f60289be53bce970f875a9f Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Mon, 13 Jun 2022 14:09:08 +0000 Subject: [PATCH 019/371] setLights further cleanup, docstrings, comments --- queries/js/IoT_Embla/Philips_Hue/lights.js | 64 +++--- .../js/IoT_Embla/Philips_Hue/set_lights.js | 200 ++++++++---------- queries/js/IoT_Embla/main.html | 2 +- 3 files changed, 130 insertions(+), 136 deletions(-) diff --git a/queries/js/IoT_Embla/Philips_Hue/lights.js b/queries/js/IoT_Embla/Philips_Hue/lights.js index 2d66b8a5..b312bb8f 100644 --- a/queries/js/IoT_Embla/Philips_Hue/lights.js +++ b/queries/js/IoT_Embla/Philips_Hue/lights.js @@ -6,7 +6,7 @@ function changeBrightness() { fetch(`http://${BRIDGE_IP}/api/${USERNAME}/lights/1/state`, { method: "PUT", - body: JSON.stringify({ bri: Number(sliderValue)}) + body: JSON.stringify({ bri: Number(sliderValue) }), }) .then((resp) => resp.json()) .then((obj) => { @@ -22,16 +22,23 @@ function changeColor() { let yValue = Number(document.getElementById("color_y").value); console.log(xValue, yValue); - if(xValue === undefined || yValue === undefined || xValue > 1 || xValue < 0 || yValue > 1 || yValue < 0) { - document.getElementById("color_error").innerHTML = "Please enter a value between 0 and 1."; - } - else { + if ( + xValue === undefined || + yValue === undefined || + xValue > 1 || + xValue < 0 || + yValue > 1 || + yValue < 0 + ) { + document.getElementById("color_error").innerHTML = + "Please enter a value between 0 and 1."; + } else { document.getElementById("color_error").innerHTML = ""; - let colorValue = [Number(xValue), Number(yValue)] + let colorValue = [Number(xValue), Number(yValue)]; console.log(colorValue); fetch(`http://${BRIDGE_IP}/api/${USERNAME}/lights/1/state`, { method: "PUT", - body: JSON.stringify({ xy: colorValue }) + body: JSON.stringify({ xy: colorValue }), }) .then((resp) => resp.json()) .then((obj) => { @@ -41,35 +48,38 @@ function changeColor() { console.log("an error occurred!"); }); } - } +async function getAllLights(hub_ip = BRIDGE_IP, username = USERNAME) { + return fetch(`http://${hub_ip}/api/${username}/lights`).then((resp) => + resp.json() + ); +} -function getAllLights(hub_ip = BRIDGE_IP, username = USERNAME) { - return fetch(`http://${hub_ip}/api/${username}/lights`) - .then((resp) => resp.json()); -}; - -function getAllGroups(hub_ip = BRIDGE_IP, username = USERNAME) { - return fetch(`http://${hub_ip}/api/${username}/groups`) - .then((resp) => resp.json()); -}; +async function getAllGroups(hub_ip = BRIDGE_IP, username = USERNAME) { + return fetch(`http://${hub_ip}/api/${username}/groups`).then((resp) => + resp.json() + ); +} -function getAllScenes(hub_ip = "192.168.1.68", username = "q2jNarhGHO9izO0xZZXcoww5GYANGi6mZyJYgMdL") { - return fetch(`http://${hub_ip}/api/${username}/scenes`) - .then((resp) => resp.json()) +async function getAllScenes( + hub_ip = "192.168.1.68", + username = "q2jNarhGHO9izO0xZZXcoww5GYANGi6mZyJYgMdL" +) { + return fetch(`http://${hub_ip}/api/${username}/scenes`).then((resp) => + resp.json() + ); } function getCurrentState(id) { - return fetch(`http://${BRIDGE_IP}/api/${USERNAME}/lights/${id}`) - .then((resp) => resp.json()); -}; + return fetch(`http://${BRIDGE_IP}/api/${USERNAME}/lights/${id}`).then( + (resp) => resp.json() + ); +} -async function getAllLightsAndGroupsFromHTML(){ +async function getAllLightsAndGroupsFromHTML() { var lights = await getAllLights(); var groups = await getAllGroups(); console.log("lights:", lights); console.log("groups:", groups); -}; - - +} diff --git a/queries/js/IoT_Embla/Philips_Hue/set_lights.js b/queries/js/IoT_Embla/Philips_Hue/set_lights.js index e21dcd58..42ee9ead 100644 --- a/queries/js/IoT_Embla/Philips_Hue/set_lights.js +++ b/queries/js/IoT_Embla/Philips_Hue/set_lights.js @@ -1,135 +1,119 @@ "use strict"; -// Constants to be used when setting lights from HTML +// Constants to be used when setting lights from HTML var BRIDGE_IP = "192.168.1.68"; var USERNAME = "q2jNarhGHO9izO0xZZXcoww5GYANGi6mZyJYgMdL"; -// const QUERY_EX = "eldhús"; -// const STATE_EX = JSON.stringify({scene: "rómó"}) -// const LIGHTS_EX = test(); -// const LIGHTS_EX = '{"1":{"state":{"on":true,"bri":100,"hue":6140,"sat":232,"effect":"none","xy":[0.5503,0.4000],"ct":500,"alert":"select","colormode":"xy","mode":"homeautomation","reachable":true},"swupdate":{"state":"noupdates","lastinstall":"2022-05-27T14:23:54"},"type":"Extended color light","name":"litaljós","modelid":"LCA001","manufacturername":"Signify Netherlands B.V.","productname":"Hue color lamp","capabilities":{"certified":true,"control":{"mindimlevel":200,"maxlumen":800,"colorgamuttype":"C","colorgamut":[[0.6915,0.3083],[0.1700,0.7000],[0.1532,0.0475]],"ct":{"min":153,"max":500}},"streaming":{"renderer":true,"proxy":true}},"config":{"archetype":"sultanbulb","function":"mixed","direction":"omnidirectional","startup":{"mode":"safety","configured":true}},"uniqueid":"00:17:88:01:06:79:c3:94-0b","swversion":"1.93.7","swconfigid":"3C05E7B6","productid":"Philips-LCA001-5-A19ECLv6"},"2":{"state":{"on":true,"bri":2,"ct":454,"alert":"select","colormode":"ct","mode":"homeautomation","reachable":false},"swupdate":{"state":"notupdatable","lastinstall":"2020-06-29T12:05:21"},"type":"Color temperature light","name":"Ikea pera Uno","modelid":"TRADFRI bulb E27 WS opal 1000lm","manufacturername":"IKEA of Sweden","productname":"Color temperature light","capabilities":{"certified":false,"control":{"ct":{"min":250,"max":454}},"streaming":{"renderer":false,"proxy":false}},"config":{"archetype":"classicbulb","function":"functional","direction":"omnidirectional"},"uniqueid":"cc:cc:cc:ff:fe:02:92:52-01","swversion":"2.0.023"},"3":{"state":{"on":true,"bri":105,"ct":454,"alert":"select","colormode":"ct","mode":"homeautomation","reachable":false},"swupdate":{"state":"notupdatable","lastinstall":"2020-07-20T13:03:26"},"type":"Color temperature light","name":"lesljós","modelid":"TRADFRI bulb E14 WS opal 400lm","manufacturername":"IKEA of Sweden","productname":"Color temperature light","capabilities":{"certified":false,"control":{"ct":{"min":250,"max":454}},"streaming":{"renderer":false,"proxy":false}},"config":{"archetype":"classicbulb","function":"functional","direction":"omnidirectional"},"uniqueid":"90:fd:9f:ff:fe:93:be:a1-01","swversion":"1.2.217"}}' -// const GROUPS_EX = '{"1":{"name":"eldhús","lights":["1"],"sensors":[],"type":"Room","state":{"all_on":true,"any_on":true},"recycle":false,"class":"Living room","action":{"on":true,"bri":100,"hue":6140,"sat":232,"effect":"none","xy":[0.5503,0.4000],"ct":500,"alert":"select","colormode":"xy"}},"2":{"name":"Hús","lights":["3","2","1"],"sensors":[],"type":"Zone","state":{"all_on":true,"any_on":true},"recycle":false,"class":"Downstairs","action":{"on":true,"bri":100,"hue":6140,"sat":232,"effect":"none","xy":[0.5503,0.4000],"ct":500,"alert":"select","colormode":"xy"}},"3":{"name":"skrifstofa","lights":["2","3"],"sensors":[],"type":"Room","state":{"all_on":true,"any_on":true},"recycle":false,"class":"Living room","action":{"on":true,"bri":105,"ct":454,"alert":"select","colormode":"ct"}}}' -/** Finds a matching light or group and returns an object with the ID, name and url for the target */ -function getTargetObject(query, allLights, allGroups) { - let returnObject - let lightsResult = philipsFuzzySearch(query, allLights); - let groupsResult = philipsFuzzySearch(query, allGroups); - - if (lightsResult != null) { - returnObject = {id: lightsResult.ID, type: "light", url: `lights/${lightsResult.ID}/state`}; - } - else if (groupsResult != null) { - returnObject = {id: groupsResult.ID, type: "group", url: `groups/${groupsResult.ID}/action`}; - } - return returnObject; - //vantar e.k. error -}; - - -async function getSceneID(scene_name, allScenes){ - let scenesResult = philipsFuzzySearch(scene_name, allScenes); - if (scenesResult != null) { - return scenesResult.ID; - } - else { - return; - } -}; - -/* Gets a target for the given query and sets the state of the target to the given state using a fetch request -* @param query - the query to find the target -* @param state - the state to set the target to -* example of paramaters: query = "eldhús" state = "{"on": true}" -*/ -async function setLights(query, state){ +// TODO: Implement a hotfix for Ikea Tradfri bulbs, since it can only take one argument at a time +/* Gets a target for the given query and sets the state of the target to the given state using a fetch request. + * query - the query to find the target e.g. "eldhús" + * state - the state to set the target to e.g. "{"on": true}" or "{"scene": "energize"}" + */ +function setLights(query, state) { let parsed_state = JSON.parse(state); let promiseList = [getAllGroups(), getAllLights()]; - if(parsed_state.scene) { + let sceneName; + if (parsed_state.scene) { + sceneName = parsed_state.scene; promiseList.push(getAllScenes()); } - // Get all lights and all groups from the API - Promise - .allSettled(promiseList) - .then( - (resolvedPromises) => { - let allGroups = resolvedPromises[0].value; - let allLights = resolvedPromises[1].value; - let allScenes; - try{ - allScenes = resolvedPromises[2].value; - } - catch(e) { - console.log("no scenes"); - } - if (allScenes != undefined){ + // Get all lights and all groups from the API (and all scenes if "scene" was a paramater) + Promise.allSettled(promiseList).then((resolvedPromises) => { + let allGroups = resolvedPromises[0].value; + let allLights = resolvedPromises[1].value; + let allScenes; + try { + allScenes = resolvedPromises[2].value; + } catch (e) { + console.log("No scene in state"); + } + // Get the target object for the given query + let targetObject = getTargetObject(query, allLights, allGroups); + if (targetObject === undefined) { + return "Ekki tókst að finna ljós"; + } + if (sceneName) { + let sceneID = getSceneID(parsed_state.scene, allScenes); + if (sceneID === undefined) { + return "Ekki tókst að finna senu"; + } else { + parsed_state.scene = sceneID; // Change the scene parameter to the scene ID + state = JSON.stringify(parsed_state); } - - let targetObject = getTargetObject(query, allLights, allGroups); - if (targetObject === undefined) { - return "Ekki tókst að finna ljós" - }; - - // Check if state includes a scene or a brightness change - if (parsed_state.scene) { - let sceneID = getSceneID(parsed_state.scene, allScenes); - if (sceneID === undefined) { - return "Ekki tókst að finna senu" } - else { - parsed_state.scene = sceneID; + // Check if state includes a scene or a brightness change + else if (parsed_state.bri_inc) { state = JSON.stringify(parsed_state); } - } - else if (parsed_state.bri_inc) { - state = JSON.stringify(parsed_state); - } - // Send data to API - let url = targetObject.url; - fetch(`http://${BRIDGE_IP}/api/${USERNAME}/${url}`, { - method: "PUT", - body: state, - }) - .then((resp) => resp.json()) - .then((obj) => { - console.log(obj); + // Send data to API + let url = targetObject.url; + fetch(`http://${BRIDGE_IP}/api/${USERNAME}/${url}`, { + method: "PUT", + body: state, }) - .catch((err) => { - console.log("an error occurred!"); - }); + .then((resp) => resp.json()) + .then((obj) => { + console.log(obj); + }) + .catch((err) => { + console.log("an error occurred!"); + }); + }); +} + +/** Finds a matching light or group and returns an object with the ID, name and url for the target */ +function getTargetObject(query, allLights, allGroups) { + let targetObject; + let lightsResult = philipsFuzzySearch(query, allLights); + let groupsResult = philipsFuzzySearch(query, allGroups); + + if (lightsResult != null) { + // Found a match for a single light + targetObject = { + id: lightsResult.ID, + type: "light", + url: `lights/${lightsResult.ID}/state`, + }; + } else if (groupsResult != null) { + // Found a match for a light group + targetObject = { + id: groupsResult.ID, + type: "group", + url: `groups/${groupsResult.ID}/action`, + }; } - ) -}; + return targetObject; +} +/** Returns the ID for a given scene name using fuzzy search */ +function getSceneID(scene_name, allScenes) { + let scenesResult = philipsFuzzySearch(scene_name, allScenes); + if (scenesResult != null) { + return scenesResult.ID; + } else { + return; + } +} +/* Tester function for setting lights directly from HTML controls */ function setLightsFromHTML() { let query = document.getElementById("queryInput").value; let stateObject = new Object(); - stateObject.bri_inc = Number(document.getElementById("brightnessInput").value); + stateObject.bri_inc = Number( + document.getElementById("brightnessInput").value + ); stateObject = JSON.stringify(stateObject); setLights(query, stateObject); -}; - - -function syncSetLights(query, state) { - setLights(query, state); - return query; -}; - +} - -function epliSceneTest() { - setLights('eldhús', '{"on": true, "scene": "epli"}'); -}; - - -function queryTest() { - let query = document.getElementById('queryInput').value; +/* Tester function for setting lights directly from HTML input fields */ +function queryTestFromHTML() { + let query = document.getElementById("queryInput").value; let bool = document.getElementById("boolInput").value; let scene = document.getElementById("sceneInput").value; if (scene === "") { - syncSetLights(query, `{"on": ${bool}}`); - } - else{ - syncSetLights(query, `{"scene": "${scene}"}`); + setLights(query, `{"on": ${bool}}`); + } else { + setLights(query, `{"scene": "${scene}"}`); } -}; \ No newline at end of file +} diff --git a/queries/js/IoT_Embla/main.html b/queries/js/IoT_Embla/main.html index 2fed5ef6..b0ced8b7 100644 --- a/queries/js/IoT_Embla/main.html +++ b/queries/js/IoT_Embla/main.html @@ -65,7 +65,7 @@

Testing

- + From 9626caecb6eda7919cd0e3daa39149105d1d5f61 Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Mon, 13 Jun 2022 17:34:11 +0000 Subject: [PATCH 020/371] hub.js updated, iot_hue.py updated --- queries/iot_hue.py | 66 +++++++++++-------- .../js/IoT_Embla/Philips_Hue/fuse_search.js | 1 + queries/js/IoT_Embla/Philips_Hue/hub.js | 29 ++++---- queries/js/IoT_Embla/Philips_Hue/lights.js | 7 +- .../js/IoT_Embla/Philips_Hue/set_lights.js | 6 +- queries/js/IoT_Embla/main.html | 2 +- 6 files changed, 63 insertions(+), 48 deletions(-) diff --git a/queries/iot_hue.py b/queries/iot_hue.py index 93406dca..b61232ac 100755 --- a/queries/iot_hue.py +++ b/queries/iot_hue.py @@ -573,34 +573,42 @@ def sentence(state: QueryStateDict, result: Result) -> None: q.set_error("E_QUERY_NOT_UNDERSTOOD") return - # host = str(flask.request.host) - # smartdevice_type = "smartlights" - # client_id = str(q.client_id) - - # # Fetch relevant data from the device_data table to perform an action on the lights - # device_data = cast(Optional[DeviceData], q.client_data(smartdevice_type)) - # print(device_data) - - # selected_light: Optional[str] = None - # hue_credentials: Optional[Dict[str, str]] = None - # if device_data is not None and smartdevice_type in device_data: - # dev = device_data[smartdevice_type] - # assert dev is not None - # selected_light = dev.get("selected_light") - # hue_credentials = dev.get("philips_hue") - # bridge_ip = hue_credentials.get("ipAddress") - # username = hue_credentials.get("username") - - # if not device_data or not hue_credentials: - - # js = read_jsfile("IoT_Embla/Philips_Hue/hub.js") - # js += f"syncConnectHub('{host}','{client_id}');" - # q.set_answer(*gen_answer("blabla")) - # q.set_command(js) - # return + host = str(flask.request.host) + print("host: ", host) + smartdevice_type = "smartlights" + client_id = str(q.client_id) + print("client_id:", client_id) + + # Fetch relevant data from the device_data table to perform an action on the lights + device_data = cast(Optional[DeviceData], q.client_data(smartdevice_type)) + print("device data :", device_data) + + selected_light: Optional[str] = None + hue_credentials: Optional[Dict[str, str]] = None + + + if device_data is not None and smartdevice_type in device_data: + dev = device_data[smartdevice_type] + assert dev is not None + selected_light = dev.get("selected_light") + hue_credentials = dev.get("philips_hue") + bridge_ip = hue_credentials.get("ipAddress") + username = hue_credentials.get("username") + + + if not device_data or not hue_credentials: + + js = read_jsfile("IoT_Embla/Philips_Hue/hub.js") + js += f"syncConnectHub('{client_id}','{host}');" + q.set_answer(*gen_answer("Reyndi að búa til user")) + q.set_command(js) + return # Successfully matched a query type - + print("bridge_ip: ", bridge_ip) + print("username: ", username) + print("selected light :", selected_light) + print("hue credentials :", hue_credentials) q.set_qtype(result.qtype) try: @@ -624,14 +632,16 @@ def sentence(state: QueryStateDict, result: Result) -> None: ) js = ( read_jsfile("IoT_Embla/fuse.js") - + f"var BRIDGE_IP = '192.168.1.68';var USERNAME = 'q2jNarhGHO9izO0xZZXcoww5GYANGi6mZyJYgMdL';" + + f"var BRIDGE_IP = '{bridge_ip}';var USERNAME = '{username}';" + read_jsfile("IoT_Embla/Philips_Hue/fuse_search.js") + read_jsfile("IoT_Embla/Philips_Hue/lights.js") + read_jsfile("IoT_Embla/Philips_Hue/set_lights.js") ) - js += f"syncSetLights('{light_or_group_name}', '{json.dumps(result.hue_obj)}');" + js += f"setLights('{light_or_group_name}', '{json.dumps(result.hue_obj)}');" q.set_command(js) except Exception as e: logging.warning("Exception while processing random query: {0}".format(e)) q.set_error("E_EXCEPTION: {0}".format(e)) raise + + # f"var BRIDGE_IP = '192.168.1.68';var USERNAME = 'p3obluiXT13IbHMpp4X63ZvZnpNRdbqqMt723gy2';" \ No newline at end of file diff --git a/queries/js/IoT_Embla/Philips_Hue/fuse_search.js b/queries/js/IoT_Embla/Philips_Hue/fuse_search.js index 50e628df..cb921d4c 100644 --- a/queries/js/IoT_Embla/Philips_Hue/fuse_search.js +++ b/queries/js/IoT_Embla/Philips_Hue/fuse_search.js @@ -2,6 +2,7 @@ function philipsFuzzySearch(query, data) { var newData = Object.keys(data).map(function (key) { return { ID: key, info: data[key] }; }); + console.log("new data: ", newData); var fuse = new Fuse(newData, { keys: ["info", "info.name"], shouldSort: true, diff --git a/queries/js/IoT_Embla/Philips_Hue/hub.js b/queries/js/IoT_Embla/Philips_Hue/hub.js index 320de8f1..088052f6 100644 --- a/queries/js/IoT_Embla/Philips_Hue/hub.js +++ b/queries/js/IoT_Embla/Philips_Hue/hub.js @@ -1,12 +1,14 @@ "use strict"; async function findHub() { - // let hubObj = new Object() - // hubObj.id = "ecb5fafffe1be1a4" - // hubObj.internalipaddress = "192.168.1.68" - // hubObj.port = "443" - // console.log(hubObj) - // return hubObj + // let hubArr = []; + // let hubObj = new Object(); + // hubObj.id = "ecb5fafffe1be1a4"; + // hubObj.internalipaddress = "192.168.1.68"; + // hubObj.port = "443"; + // console.log(hubObj); + // hubArr.push(hubObj); + // return hubArr[0]; return fetch(`https://discovery.meethue.com`) .then((resp) => resp.json()) .then((obj) => { @@ -91,9 +93,14 @@ async function connectHub(clientID, requestURL) { } } -function syncConnectHub() { - let clientID = "AB8C8D7E-20F5-4772-BD69-313EA9DAFBD8"; - let requestURl = "192.168.1.69:5000"; - connectHub(clientID, requestURl); - return "blabla"; +function syncConnectHub(clientID, requestURL) { + connectHub(clientID, requestURL); + return clientID; +} + +function syncConnectHubFromHTML() { + let clientID = "82AD3C91-7DA2-4502-BB17-075CEC090B14"; + let requestURL = "192.168.1.69:5000"; + connectHub(clientID, requestURL); + return clientID; } diff --git a/queries/js/IoT_Embla/Philips_Hue/lights.js b/queries/js/IoT_Embla/Philips_Hue/lights.js index 1d475294..b4c5d4da 100644 --- a/queries/js/IoT_Embla/Philips_Hue/lights.js +++ b/queries/js/IoT_Embla/Philips_Hue/lights.js @@ -50,24 +50,19 @@ function changeColor() { } } - async function getAllLights(hub_ip = BRIDGE_IP, username = USERNAME) { return fetch(`http://${hub_ip}/api/${username}/lights`).then((resp) => resp.json() ); } - async function getAllGroups(hub_ip = BRIDGE_IP, username = USERNAME) { return fetch(`http://${hub_ip}/api/${username}/groups`).then((resp) => resp.json() ); } -async function getAllScenes( - hub_ip = "192.168.1.68", - username = "q2jNarhGHO9izO0xZZXcoww5GYANGi6mZyJYgMdL" -) { +async function getAllScenes(hub_ip = BRIDGE_IP, username = USERNAME) { return fetch(`http://${hub_ip}/api/${username}/scenes`).then((resp) => resp.json() ); diff --git a/queries/js/IoT_Embla/Philips_Hue/set_lights.js b/queries/js/IoT_Embla/Philips_Hue/set_lights.js index 42ee9ead..a364eb54 100644 --- a/queries/js/IoT_Embla/Philips_Hue/set_lights.js +++ b/queries/js/IoT_Embla/Philips_Hue/set_lights.js @@ -1,8 +1,8 @@ "use strict"; // Constants to be used when setting lights from HTML -var BRIDGE_IP = "192.168.1.68"; -var USERNAME = "q2jNarhGHO9izO0xZZXcoww5GYANGi6mZyJYgMdL"; +// var BRIDGE_IP = "192.168.1.68"; +// var USERNAME = "p3obluiXT13IbHMpp4X63ZvZnpNRdbqqMt723gy2"; // TODO: Implement a hotfix for Ikea Tradfri bulbs, since it can only take one argument at a time /* Gets a target for the given query and sets the state of the target to the given state using a fetch request. @@ -29,6 +29,7 @@ function setLights(query, state) { } // Get the target object for the given query let targetObject = getTargetObject(query, allLights, allGroups); + console.log("targetObject: ", targetObject); if (targetObject === undefined) { return "Ekki tókst að finna ljós"; } @@ -111,6 +112,7 @@ function queryTestFromHTML() { let query = document.getElementById("queryInput").value; let bool = document.getElementById("boolInput").value; let scene = document.getElementById("sceneInput").value; + console.log(query); if (scene === "") { setLights(query, `{"on": ${bool}}`); } else { diff --git a/queries/js/IoT_Embla/main.html b/queries/js/IoT_Embla/main.html index b0ced8b7..5fe5e294 100644 --- a/queries/js/IoT_Embla/main.html +++ b/queries/js/IoT_Embla/main.html @@ -40,7 +40,7 @@

Testing

- +
From 46f5ad4682cfd56b8b39c0f4e53cdceb1884ecf8 Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Mon, 13 Jun 2022 17:44:27 +0000 Subject: [PATCH 021/371] added TODO and uncommented functioning code --- queries/iot_hue.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/queries/iot_hue.py b/queries/iot_hue.py index b61232ac..00a00adc 100755 --- a/queries/iot_hue.py +++ b/queries/iot_hue.py @@ -27,6 +27,8 @@ # TODO: make the objects of sentences more modular, so that the same structure doesn't need to be written for each action # TODO: ditto the previous comment. make the initial non-terminals general and go into specifics at the terminal level instead. # TODO: substituion klósett, baðherbergi hugmyndÆ senda lista i javascript og profa i röð +# TODO: Embla stores old javascript code cached which has caused errors +# TODO: Cut down javascript sent to Embla from typing import Dict, Mapping, Optional, cast from typing_extensions import TypedDict @@ -152,11 +154,11 @@ def help_text(lemma: str) -> str: | QIoTMoreBrightness QIoTGroupNamePhrase? | QIoTMoreBrightness QIoTLocationPreposition QIoTLightPhrase -# QIoTSetBrightObject -> -# QIoTColorLightPhrase "á" QIoTColorNamePhrase -# | QIoTColorLight "á" QIoTColorNamePhrase QIoTGroupNamePhrase? -# | "á"? QIoTColorNamePhrase QIoTGroupNamePhrase? -# | "á"? QIoTColorNamePhrase QIoTLocationPreposition QIoTLightPhrase +QIoTSetBrightObject -> + QIoTColorLightPhrase "á" QIoTColorNamePhrase + | QIoTColorLight "á" QIoTColorNamePhrase QIoTGroupNamePhrase? + | "á"? QIoTColorNamePhrase QIoTGroupNamePhrase? + | "á"? QIoTColorNamePhrase QIoTLocationPreposition QIoTLightPhrase QIoTDecreaseBrightness -> QIoTDecrease QIoTBrightness QIoTLightPhrase? From 3bec2259d260452680fa18520175359b970d906b Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Tue, 14 Jun 2022 11:42:21 +0000 Subject: [PATCH 022/371] fuse search score with new getTargetObject function --- .../js/IoT_Embla/Philips_Hue/fuse_search.js | 24 +++- .../js/IoT_Embla/Philips_Hue/set_lights.js | 125 +++++++++++++----- 2 files changed, 111 insertions(+), 38 deletions(-) diff --git a/queries/js/IoT_Embla/Philips_Hue/fuse_search.js b/queries/js/IoT_Embla/Philips_Hue/fuse_search.js index cb921d4c..78ed6e7d 100644 --- a/queries/js/IoT_Embla/Philips_Hue/fuse_search.js +++ b/queries/js/IoT_Embla/Philips_Hue/fuse_search.js @@ -1,22 +1,32 @@ +/* +Fuzzy search function that returns an object in the form of {result: (Object), score: (Number)} +* @param {String} query - The search term +* @param {Object} data - The data to search +*/ function philipsFuzzySearch(query, data) { + // Restructure data to be searchable by name var newData = Object.keys(data).map(function (key) { return { ID: key, info: data[key] }; }); - console.log("new data: ", newData); + // Fuzzy search for the query term (returns an array of objects) var fuse = new Fuse(newData, { keys: ["info", "info.name"], + includeScore: true, shouldSort: true, threshold: 0.5, }); + let searchResult = fuse.search(query); - let searchTerm = query; - let result = fuse.search(searchTerm); - - console.log("result: ", result); - if (result[0] === undefined) { + let resultObject = new Object(); + console.log("result: ", searchResult); + if (searchResult[0] === undefined) { console.log("no match found"); return null; } else { - return result[0].item; + // Structure the return object to be in the form of {result: (Object), score: (Number)} + resultObject.result = searchResult[0].item; + resultObject.score = searchResult[0].score; + console.log("resultObject :", resultObject); + return resultObject; } } diff --git a/queries/js/IoT_Embla/Philips_Hue/set_lights.js b/queries/js/IoT_Embla/Philips_Hue/set_lights.js index a364eb54..49bc4e03 100644 --- a/queries/js/IoT_Embla/Philips_Hue/set_lights.js +++ b/queries/js/IoT_Embla/Philips_Hue/set_lights.js @@ -2,19 +2,20 @@ // Constants to be used when setting lights from HTML // var BRIDGE_IP = "192.168.1.68"; -// var USERNAME = "p3obluiXT13IbHMpp4X63ZvZnpNRdbqqMt723gy2"; +// var USERNAME = "MQH9DVv1lhgaOKN67uVox4fWNc9iu3j5g7MmdDUr"; // TODO: Implement a hotfix for Ikea Tradfri bulbs, since it can only take one argument at a time -/* Gets a target for the given query and sets the state of the target to the given state using a fetch request. - * query - the query to find the target e.g. "eldhús" - * state - the state to set the target to e.g. "{"on": true}" or "{"scene": "energize"}" + +/** Gets a target for the given query and sets the state of the target to the given state using a fetch request. + * @param {String} query - the query to find the target e.g. "eldhús" or "lampi" + * @param {String} state - the state to set the target to e.g. "{"on": true}" or "{"scene": "energize"}" */ function setLights(query, state) { - let parsed_state = JSON.parse(state); + let parsedState = JSON.parse(state); let promiseList = [getAllGroups(), getAllLights()]; let sceneName; - if (parsed_state.scene) { - sceneName = parsed_state.scene; + if (parsedState.scene) { + sceneName = parsedState.scene; promiseList.push(getAllScenes()); } // Get all lights and all groups from the API (and all scenes if "scene" was a paramater) @@ -27,25 +28,26 @@ function setLights(query, state) { } catch (e) { console.log("No scene in state"); } + // Get the target object for the given query let targetObject = getTargetObject(query, allLights, allGroups); - console.log("targetObject: ", targetObject); if (targetObject === undefined) { return "Ekki tókst að finna ljós"; } + + // Check if state includes a scene or a brightness change if (sceneName) { - let sceneID = getSceneID(parsed_state.scene, allScenes); + let sceneID = getSceneID(parsedState.scene, allScenes); if (sceneID === undefined) { return "Ekki tókst að finna senu"; } else { - parsed_state.scene = sceneID; // Change the scene parameter to the scene ID - state = JSON.stringify(parsed_state); + parsedState.scene = sceneID; // Change the scene parameter to the scene ID + state = JSON.stringify(parsedState); } + } else if (parsedState.bri_inc) { + state = JSON.stringify(parsedState); } - // Check if state includes a scene or a brightness change - else if (parsed_state.bri_inc) { - state = JSON.stringify(parsed_state); - } + // Send data to API let url = targetObject.url; fetch(`http://${BRIDGE_IP}/api/${USERNAME}/${url}`, { @@ -62,35 +64,54 @@ function setLights(query, state) { }); } -/** Finds a matching light or group and returns an object with the ID, name and url for the target */ +/** Finds a matching light or group and returns an object with the ID, name and url for the target + * @param {String} query - the query to find the target e.g. "eldhús" + * @param {Array} allLights - an object of all lights from the API + * @param {Array} allGroups - an object of all groups from the API + */ function getTargetObject(query, allLights, allGroups) { - let targetObject; + let targetObject, selection, url; let lightsResult = philipsFuzzySearch(query, allLights); let groupsResult = philipsFuzzySearch(query, allGroups); - if (lightsResult != null) { + if (lightsResult != null && groupsResult != null) { + // Found a match for a light group and a light + selection = + lightsResult.score < groupsResult.score // Select the light with the highest score + ? lightsResult + : groupsResult; + url = + selection === lightsResult // Set url based on selection + ? `lights/${lightsResult.result.ID}/state` + : `groups/${groupsResult.result.ID}/action`; + } else if (lightsResult != null && groupsResult == null) { // Found a match for a single light - targetObject = { - id: lightsResult.ID, - type: "light", - url: `lights/${lightsResult.ID}/state`, - }; - } else if (groupsResult != null) { + selection = lightsResult; + url = `lights/${selection.result.ID}/state`; + } else if (groupsResult != null && lightsResult == null) { // Found a match for a light group - targetObject = { - id: groupsResult.ID, - type: "group", - url: `groups/${groupsResult.ID}/action`, - }; + selection = groupsResult; + url = `groups/${selection.result.ID}/action`; + } else { + return; } + + targetObject = { + id: selection.result.ID, + url: url, + }; return targetObject; } -/** Returns the ID for a given scene name using fuzzy search */ +/** Returns the ID for a given scene name using fuzzy search + * @param {String} sceneName - the name of the scene to find + * @param {Object} allScenes - an array of all scenes from the API + */ function getSceneID(scene_name, allScenes) { let scenesResult = philipsFuzzySearch(scene_name, allScenes); + console.log("sceneResult :", scenesResult); if (scenesResult != null) { - return scenesResult.ID; + return scenesResult.result.ID; } else { return; } @@ -119,3 +140,45 @@ function queryTestFromHTML() { setLights(query, `{"scene": "${scene}"}`); } } + +// /** Finds a matching light or group and returns an object with the ID, name and url for the target +// * @param {String} query - the query to find the target e.g. "eldhús" +// * @param {Object} allLights - an array of all lights from the API +// * @param {Object} allGroups - an array of all groups from the API +// */ +// function getTargetObjectOLD(query, allLights, allGroups) { +// let targetObject; +// let lightsResult = philipsFuzzySearch(query, allLights); +// let groupsResult = philipsFuzzySearch(query, allGroups); +// console.log("lightsResult: ", lightsResult); +// console.log("groupsResult: ", groupsResult); + +// if (lightsResult != null && groupsResult == null) { +// // Found a match for a single light +// targetObject = { +// id: lightsResult.result.ID, +// type: "light", +// url: `lights/${lightsResult.result.ID}/state`, +// }; +// } else if (groupsResult != null && lightsResult == null) { +// // Found a match for a light group +// targetObject = { +// id: groupsResult.result.ID, +// type: "group", +// url: `groups/${groupsResult.result.ID}/action`, +// }; +// } else if (groupsResult != null && lightsResult != null) { +// let lightsScore = lightsResult.score; +// let groupsScore = groupsResult.score; +// let selection = lightsScore > groupsScore ? lightsResult : groupsResult; +// console.log("selection :", selection); +// // Found a match for a light group and a light +// targetObject = { +// id: lightsResult.result.ID, +// type: "light", +// url: `lights/${lightsResult.result.ID}/state`, +// }; +// } +// console.log("targetObject: ", targetObject); +// return targetObject; +// } From 0c5ce1589dd79258754b02274dc17f3aa3d52678 Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Tue, 14 Jun 2022 12:26:29 +0000 Subject: [PATCH 023/371] getTargetObject refactor getTargetObject refactor --- .../js/IoT_Embla/Philips_Hue/set_lights.js | 43 ++++++++++--------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/queries/js/IoT_Embla/Philips_Hue/set_lights.js b/queries/js/IoT_Embla/Philips_Hue/set_lights.js index 49bc4e03..77e99b3c 100644 --- a/queries/js/IoT_Embla/Philips_Hue/set_lights.js +++ b/queries/js/IoT_Embla/Philips_Hue/set_lights.js @@ -1,8 +1,8 @@ "use strict"; // Constants to be used when setting lights from HTML -// var BRIDGE_IP = "192.168.1.68"; -// var USERNAME = "MQH9DVv1lhgaOKN67uVox4fWNc9iu3j5g7MmdDUr"; +var BRIDGE_IP = "192.168.1.68"; +var USERNAME = "MQH9DVv1lhgaOKN67uVox4fWNc9iu3j5g7MmdDUr"; // TODO: Implement a hotfix for Ikea Tradfri bulbs, since it can only take one argument at a time @@ -66,8 +66,8 @@ function setLights(query, state) { /** Finds a matching light or group and returns an object with the ID, name and url for the target * @param {String} query - the query to find the target e.g. "eldhús" - * @param {Array} allLights - an object of all lights from the API - * @param {Array} allGroups - an object of all groups from the API + * @param {Object} allLights - an object of all lights from the API + * @param {Object} allGroups - an object of all groups from the API */ function getTargetObject(query, allLights, allGroups) { let targetObject, selection, url; @@ -75,31 +75,32 @@ function getTargetObject(query, allLights, allGroups) { let groupsResult = philipsFuzzySearch(query, allGroups); if (lightsResult != null && groupsResult != null) { - // Found a match for a light group and a light - selection = + // Found a match for a light group and a light+ + targetObject = lightsResult.score < groupsResult.score // Select the light with the highest score - ? lightsResult - : groupsResult; - url = - selection === lightsResult // Set url based on selection - ? `lights/${lightsResult.result.ID}/state` - : `groups/${groupsResult.result.ID}/action`; + ? { + id: lightsResult.result.ID, + url: `lights/${lightsResult.result.ID}/state`, + } + : { + id: groupsResult.result.ID, + url: `groups/${groupsResult.result.ID}/action`, + }; } else if (lightsResult != null && groupsResult == null) { // Found a match for a single light - selection = lightsResult; - url = `lights/${selection.result.ID}/state`; + targetObject = { + id: lightsResult.result.ID, + url: `lights/${lightsResult.result.ID}/state`, + }; } else if (groupsResult != null && lightsResult == null) { // Found a match for a light group - selection = groupsResult; - url = `groups/${selection.result.ID}/action`; + targetObject = { + id: groupsResult.result.ID, + url: `groups/${groupsResult.result.ID}/action`, + }; } else { return; } - - targetObject = { - id: selection.result.ID, - url: url, - }; return targetObject; } From c06d9e937f46cf0e1ce729e23e8ff3fa63da70b3 Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Tue, 14 Jun 2022 16:36:11 +0000 Subject: [PATCH 024/371] started fixing grammar --- queries/__init__.py | 32 ++++++++++++++++++++++++++++++++ queries/iot_hue.py | 4 +++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/queries/__init__.py b/queries/__init__.py index 3884a7a9..4e2ece21 100755 --- a/queries/__init__.py +++ b/queries/__init__.py @@ -672,3 +672,35 @@ def read_jsfile(filename: str) -> str: fpath = os.path.join(basepath, "js", filename) with open(fpath, mode="r") as file: return cast(str, jsmin(file.read())) + + +def read_grammar_file(filename: str, **format_kwargs: str) -> str: + """ + Read and return a grammar file from the 'grammars' folder. + Optionally specify keyword arguments for str.format() call + """ + basepath, _ = os.path.split(os.path.realpath(__file__)) + fpath = os.path.join(basepath, "grammars", filename + ".grammar") + + grammar = "" + with open(fpath, mode="r") as file: + grammar = file.read() + if len(format_kwargs) > 0: + grammar = grammar.format(**format_kwargs) + return grammar + + +def join_grammar_files(folder: str) -> str: + """ + Given a folder name, return a string containing + the contents of all '.grammar' files within the folder + """ + basepath, _ = os.path.split(os.path.realpath(__file__)) + fpath = os.path.join(basepath, folder) + + grammar: List[str] = [] + for fname in os.listdir(fpath): + if fname.endswith(".grammar"): + with open(os.path.join(fpath, fname), mode="r") as file: + grammar.append(file.read()) + return "\n".join(grammar) diff --git a/queries/iot_hue.py b/queries/iot_hue.py index 00a00adc..7f78e37b 100755 --- a/queries/iot_hue.py +++ b/queries/iot_hue.py @@ -39,7 +39,7 @@ import flask from query import Query, QueryStateDict, AnswerTuple -from queries import gen_answer, read_jsfile +from queries import gen_answer, read_jsfile, read_grammar_file from tree import Result, Node @@ -94,6 +94,8 @@ def help_text(lemma: str) -> str: QUERY_NONTERMINALS = {"QIoT"} # The context-free grammar for the queries recognized by this plug-in module +# GRAMMAR = read_grammar_file("iot_hue") + GRAMMAR = f""" Query → From 9fe39337ae35ac579541c3ef1c63fe0094075f7f Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Wed, 15 Jun 2022 13:56:46 +0000 Subject: [PATCH 025/371] started working on new grammar --- queries/grammars/iot_hue.grammar | 185 +++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 queries/grammars/iot_hue.grammar diff --git a/queries/grammars/iot_hue.grammar b/queries/grammars/iot_hue.grammar new file mode 100644 index 00000000..c095e231 --- /dev/null +++ b/queries/grammars/iot_hue.grammar @@ -0,0 +1,185 @@ +Query → + QIoT + +QIoT → QIoTQuery '?'? + +QIoTQuery -> + QIoTMakeVerb QIoTMakeRest + | QIoTSetVerb QIoTSetRest + | QIoTChangeVerb QIoTChangeRest + | QIoTLetVerb QIoTLetRest + | QIoTTurnVerb QIoTTurnRest + +QIoTMakeVerb -> + 'gera:so'_bh + +QIoTSetVerb -> + 'setja:so'_bh + | 'stilla:so'_bh + +QIoTChangeVerb -> + 'breyta:so'_bh + +QIoTLetVerb -> + 'láta:so'_bh + +QIoTMakeRest -> + QIoTHvad QIoTHvar QIoTHvernigMake + | QIoTHvad QIoTHvernigMake QIoTHvar + | QIoTHvar QIoTHvad QIoTHvernigMake + | QIoTHvar QIoTHvernigMake QIoTHvad + | QIoTHvernigMake QIoTHvad QIoTHvar + | QIoTHvernigMake QIoTHvar QIoTHvad + +# TODO: Add support for "stilltu rauðan lit á ljósið í eldhúsinu" +QIoTSetRest -> + QIoTHvad QIoTHvar QIoTHvernigSet + | QIoTHvad QIoTHvernigSet QIoTHvar + | QIoTHvar QIoTHvad QIoTHvernigSet + | QIoTHvar QIoTHvernigSet QIoTHvad + | QIoTHvernigSet QIoTHvad QIoTHvar + | QIoTHvernigSet QIoTHvar QIoTHvad + +QIoTChangeRest -> + QIoTHverju QIoTHvar QIoTHvernigChange + | QIoTHverju QIoTHvernigChange QIoTHvar + | QIoTHvar QIoTHverju QIoTHvernigChange + | QIoTHvar QIoTHvernigChange QIoTHverju + | QIoTHvernigChange QIoTHverju QIoTHvar + | QIoTHvernigChange QIoTHvar QIoTHverju + +QIoTLetRest -> + QIoTHvad QIoTHvar QIoTHvernigLet + | QIoTHvad QIoTHvernigLet QIoTHvar + | QIoTHvar QIoTHvad QIoTHvernigLet + | QIoTHvar QIoTHvernigLet QIoTHvad + | QIoTHvernigLet QIoTHvad QIoTHvar + | QIoTHvernigLet QIoTHvar QIoTHvad + +QIoTTurnRest -> + QIoTLightObject QIoTHvar + | QIoTHvar QIoTLightObject + | QIoTAHverju QIoTHvar + | QIoTHvar QIoTAHverju + +QIoTHvad -> + QIoTLightSubjectNf + | QIoTColorSubjectNf + | QIoTBrightnessSubjectNf + | QIoTSceneSubjectNf + | QIoTGroupNameSubjectNf #styður bara "gerðu eldhúsið rautt" og "gerðu eldhúsið bjartara", t.d. + +# TODO: Decide whether LightSubjectÞgf should be accepted +QIoTHverju -> + QIoTLightSubjectÞgf + | QIoTColorSubjectÞgf + | QIoTBrightnessSubjectÞgf + | QIoTSceneSubjectÞgf + +QIoTHvar -> + QIoTLocationPreposition QIoTGroupNameÞgf + +QIoTHvernigMake -> + QIoTAnnadAndlag # gerðu litinn rauðan í eldhúsinu EÐA gerðu birtuna meiri í eldhúsinu + | QIoTAdHverju # gerðu litinn að rauðum í eldhúsinu + +QIoTHvernigSet -> + QIoTAHvad + +QIoTHvernigChange -> + QIoTIHvad + +QIoTHvernigLet -> + QIoTBecome QIoTSomethingOrSomehow + | QIoTBe QIoTSomehow + +# I think these verbs only appear in these forms. +# In which case these terminals should be deleted and a direct reference should be made in the relevant non-terminals. +QIoTBe -> + "vera" + +QIoTBecome -> + "verða" + +QIoTLightSubjectNf -> + QIoTLightNf + +QIoTLightSubjectÞgf -> + QIoTLightÞgf + +QIoTColorSubjectNf -> + QIoTColorWordNf QIoTLightEf? + | QIoTColorWordNf "á" QIoTLightÞgf + +QIoTColorSubjectÞgf -> + QIoTColorWordÞgf QIoTLightEf? + | QIoTColorWordÞgf "á" QIoTLightÞgf + +QIoTBrightnessSubjectNf -> + QIoTBrightnessWordNf QIoTLightEf? + | QIoTBrightnessWordNf "á" QIoTLightÞgf + +QIoTBrightnessSubjectÞgf -> + QIoTBrightnessWordÞgf QIoTLightEf? + | QIoTBrightnessWordÞgf "á" QIoTLightÞgf + +QIoTSceneSubjectNf -> + QIoTSceneWordNf + +QIoTGroupNameSubjectNf -> + QIoTGroupNameNf + +QIoTLocationPreposition -> + QIoTLocationPrepositionFirstPart? QIoTLocationPrepositionSecondPart + +# The latter proverbs are grammatically incorrect, but common errors, both in speech and transcription. +# The list provided is taken from StefnuAtv in Greynir.grammar. That includes "aftur:ao", which is not applicable here. +QIoTLocationPrepositionFirstPart -> + StaðarAtv + | "fram:ao" + | "inn:ao" + | "niður:ao" + | "upp:ao" + | "út:ao" + +QIoTLocationPrepositionSecondPart -> + "á" | "í" + +QIoTGroupNameNf -> + no_nf + +QIoTGroupNameÞgf -> + no_þgf + +QIoTAnnadAndlag -> + QIoTNewSettingNf + +QIoTAdHverju -> + "að" QIoTNewSettingÞgf + +QIoTAHvad -> + "á" QIoTNewSettingÞf + +QIoTSomethingOrSomehow -> + QIoTAnnadAndlag + | QIoTAdHverju + +QIotSomehow -> + QIoTAnnadAndlag + +QIoTLight/fall -> + QIoTLightName/fall + | QIoTLightWord/fall + +QIoTLightÞgf -> + QIoTLightNameÞgf + | QIoTLightWordÞgf + +QIoTLightEf -> + QIoTLightNameEf + | QIoTLightWordEf + +QIoTColorWord/fall -> + 'litur'/fall + | 'litblær'_nf + | 'blær'_nf From 8370b151f80dee9ea6363657a703edaca714f71a Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Wed, 15 Jun 2022 15:20:30 +0000 Subject: [PATCH 026/371] added to grammar --- queries/grammars/iot_hue.grammar | 118 ++++++++++++++++++------------- 1 file changed, 67 insertions(+), 51 deletions(-) diff --git a/queries/grammars/iot_hue.grammar b/queries/grammars/iot_hue.grammar index c095e231..afcca145 100644 --- a/queries/grammars/iot_hue.grammar +++ b/queries/grammars/iot_hue.grammar @@ -63,21 +63,21 @@ QIoTTurnRest -> | QIoTHvar QIoTAHverju QIoTHvad -> - QIoTLightSubjectNf - | QIoTColorSubjectNf - | QIoTBrightnessSubjectNf - | QIoTSceneSubjectNf - | QIoTGroupNameSubjectNf #styður bara "gerðu eldhúsið rautt" og "gerðu eldhúsið bjartara", t.d. + QIoTLightSubject/nf + | QIoTColorSubject/nf + | QIoTBrightnessSubject/nf + | QIoTSceneSubject/nf + | QIoTGroupNameSubject/nf #á bara að styðja "gerðu eldhúsið rautt", "gerðu eldhúsið rómó" "gerðu eldhúsið bjartara", t.d. -# TODO: Decide whether LightSubjectÞgf should be accepted +# TODO: Decide whether LightSubject/þgf should be accepted QIoTHverju -> - QIoTLightSubjectÞgf - | QIoTColorSubjectÞgf - | QIoTBrightnessSubjectÞgf - | QIoTSceneSubjectÞgf + QIoTLightSubject/þgf + | QIoTColorSubject/þgf + | QIoTBrightnessSubject/þgf + | QIoTSceneSubject/þgf QIoTHvar -> - QIoTLocationPreposition QIoTGroupNameÞgf + QIoTLocationPreposition QIoTGroupName/þgf QIoTHvernigMake -> QIoTAnnadAndlag # gerðu litinn rauðan í eldhúsinu EÐA gerðu birtuna meiri í eldhúsinu @@ -101,33 +101,22 @@ QIoTBe -> QIoTBecome -> "verða" -QIoTLightSubjectNf -> - QIoTLightNf +QIoTLightSubject/fall -> + QIoTLight/fall -QIoTLightSubjectÞgf -> - QIoTLightÞgf +QIoTColorSubject/fall -> + QIoTColorWord/fall QIoTLight/ef? + | QIoTColorWord/fall "á" QIoTLight/þgf -QIoTColorSubjectNf -> - QIoTColorWordNf QIoTLightEf? - | QIoTColorWordNf "á" QIoTLightÞgf +QIoTBrightnessSubject/fall -> + QIoTBrightnessWord/fall QIoTLight/ef? + | QIoTBrightnessWord/fall "á" QIoTLight/þgf -QIoTColorSubjectÞgf -> - QIoTColorWordÞgf QIoTLightEf? - | QIoTColorWordÞgf "á" QIoTLightÞgf +QIoTSceneSubject/fall -> + QIoTSceneWord/fall -QIoTBrightnessSubjectNf -> - QIoTBrightnessWordNf QIoTLightEf? - | QIoTBrightnessWordNf "á" QIoTLightÞgf - -QIoTBrightnessSubjectÞgf -> - QIoTBrightnessWordÞgf QIoTLightEf? - | QIoTBrightnessWordÞgf "á" QIoTLightÞgf - -QIoTSceneSubjectNf -> - QIoTSceneWordNf - -QIoTGroupNameSubjectNf -> - QIoTGroupNameNf +QIoTGroupNameSubject/fall -> + QIoTGroupName/fall QIoTLocationPreposition -> QIoTLocationPrepositionFirstPart? QIoTLocationPrepositionSecondPart @@ -145,20 +134,28 @@ QIoTLocationPrepositionFirstPart -> QIoTLocationPrepositionSecondPart -> "á" | "í" -QIoTGroupNameNf -> - no_nf +QIoTGroupName/fall -> + no/fall + +QIoTLightName/fall -> + no/fall -QIoTGroupNameÞgf -> - no_þgf +QIoTColorName/fall -> + {" | ".join(f"'{color}:lo'/fall" for color in _COLORS.keys())} QIoTAnnadAndlag -> - QIoTNewSettingNf + QIoTNewSetting/nf + | QIoTSpyrjaHuldu/nf QIoTAdHverju -> - "að" QIoTNewSettingÞgf + "að" QIoTNewSetting/þgf QIoTAHvad -> - "á" QIoTNewSettingÞf + "á" QIoTNewSetting/þf + +QIoTAHverju -> + "á" QIoTLight/þgf + | "á" QIoTNewSetting/þgf QIoTSomethingOrSomehow -> QIoTAnnadAndlag @@ -171,15 +168,34 @@ QIoTLight/fall -> QIoTLightName/fall | QIoTLightWord/fall -QIoTLightÞgf -> - QIoTLightNameÞgf - | QIoTLightWordÞgf - -QIoTLightEf -> - QIoTLightNameEf - | QIoTLightWordEf - QIoTColorWord/fall -> 'litur'/fall - | 'litblær'_nf - | 'blær'_nf + | 'litblær'/fall + | 'blær'/fall + +QIoTBrightnessWord/fall -> + 'birta'/fall + | 'birtustig'/fall + +QIoTSceneWord/fall -> + 'sena'/fall + | 'stemning'/fall + | 'stemming'/fall + | 'stemmning'/fall + +# Need to ask Hulda how this works. +QIoTSpyrjaHuldu/fall -> + # QIoTHuldaColor/fall + QIoTHuldaBrightness/fall + # | QIoTHuldaScene/fall + +# Do I need a "new light state" non-terminal? +QIoTNewSetting/fall -> + QIoTNewColor/fall + | QIoTNewBrightness/fall + | QIoTNewScene/fall + +QIoTHuldaColor/fall -> + QIoTColorName/fall + +QIoT \ No newline at end of file From 162ddbc79f7a7f29d410c960633cc4ffe4e15e72 Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Wed, 15 Jun 2022 15:34:20 +0000 Subject: [PATCH 027/371] fixed hard-coding --- queries/js/IoT_Embla/Philips_Hue/set_lights.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/queries/js/IoT_Embla/Philips_Hue/set_lights.js b/queries/js/IoT_Embla/Philips_Hue/set_lights.js index 77e99b3c..46c790c7 100644 --- a/queries/js/IoT_Embla/Philips_Hue/set_lights.js +++ b/queries/js/IoT_Embla/Philips_Hue/set_lights.js @@ -1,8 +1,8 @@ "use strict"; // Constants to be used when setting lights from HTML -var BRIDGE_IP = "192.168.1.68"; -var USERNAME = "MQH9DVv1lhgaOKN67uVox4fWNc9iu3j5g7MmdDUr"; +// var BRIDGE_IP = "192.168.1.68"; +// var USERNAME = "MQH9DVv1lhgaOKN67uVox4fWNc9iu3j5g7MmdDUr"; // TODO: Implement a hotfix for Ikea Tradfri bulbs, since it can only take one argument at a time From 028323866d4116b2135e538fa7b752c147116ed3 Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Wed, 15 Jun 2022 16:01:52 +0000 Subject: [PATCH 028/371] grammar ass --- queries/grammars/iot_hue.grammar | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/queries/grammars/iot_hue.grammar b/queries/grammars/iot_hue.grammar index afcca145..1f12c4e4 100644 --- a/queries/grammars/iot_hue.grammar +++ b/queries/grammars/iot_hue.grammar @@ -195,7 +195,8 @@ QIoTNewSetting/fall -> | QIoTNewBrightness/fall | QIoTNewScene/fall -QIoTHuldaColor/fall -> - QIoTColorName/fall - -QIoT \ No newline at end of file +QIoTHuldaBrightness/fall -> + QIoTMore/fall + | QIoTLess/fall + | QIoTBrighter/fall + | QIoTDarker/fall From ac347dbbc5d236fd6f9b7f50970347368e51a02d Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Wed, 15 Jun 2022 16:24:59 +0000 Subject: [PATCH 029/371] =?UTF-8?q?iotHue=20grammar=20for=20"tengdu=20lj?= =?UTF-8?q?=C3=B3sin"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit iotHue grammar for "tengdu ljósin" --- queries/iot_hue.py | 39 ++++++++++++++----- queries/js/IoT_Embla/Philips_Hue/hub.js | 18 ++++----- .../js/IoT_Embla/Philips_Hue/set_lights.js | 4 +- 3 files changed, 41 insertions(+), 20 deletions(-) diff --git a/queries/iot_hue.py b/queries/iot_hue.py index 00a00adc..edaec282 100755 --- a/queries/iot_hue.py +++ b/queries/iot_hue.py @@ -99,7 +99,12 @@ def help_text(lemma: str) -> str: Query → QIoT -QIoT → QIoTQuery '?'? +QIoT → + QIoTQuery '?'? + | QIoTConnectLights '?'? + +QIoTConnectLights → + "tengdu" "ljósin" QIoTQuery → QIoTTurnOn @@ -554,6 +559,10 @@ def QIoTGroupName(node: Node, params: QueryStateDict, result: Result) -> None: def QIoTLightName(node: Node, params: QueryStateDict, result: Result) -> None: result["light_name"] = result._indefinite +def QIoTConnectLights(node: Node, params: QueryStateDict, result: Result) -> None: + result.qtype = "connect_lights" + result.action = "connect_lights" + # Convert color name into hue # Taken from home.py @@ -575,8 +584,23 @@ def sentence(state: QueryStateDict, result: Result) -> None: q.set_error("E_QUERY_NOT_UNDERSTOOD") return - host = str(flask.request.host) - print("host: ", host) + if result.qtype == "connect_lights": + host = str(flask.request.host) + print("host: ", host) + smartdevice_type = "smartlights" + client_id = str(q.client_id) + print("client_id:", client_id) + js = read_jsfile("IoT_Embla/Philips_Hue/hub.js") + js += f"syncConnectHub('{client_id}','{host}');" + answer = "Philips Hue miðstöðin hefur verið tengd" + voice_answer = answer + response = dict(answer=answer) + q.set_answer(response, answer, voice_answer) + q.set_command(js) + + return + + smartdevice_type = "smartlights" client_id = str(q.client_id) print("client_id:", client_id) @@ -588,7 +612,7 @@ def sentence(state: QueryStateDict, result: Result) -> None: selected_light: Optional[str] = None hue_credentials: Optional[Dict[str, str]] = None - + if device_data is not None and smartdevice_type in device_data: dev = device_data[smartdevice_type] assert dev is not None @@ -599,11 +623,8 @@ def sentence(state: QueryStateDict, result: Result) -> None: if not device_data or not hue_credentials: - - js = read_jsfile("IoT_Embla/Philips_Hue/hub.js") - js += f"syncConnectHub('{client_id}','{host}');" - q.set_answer(*gen_answer("Reyndi að búa til user")) - q.set_command(js) + answer = "Philips Hue hefur ekki verið tengt" + q.set_answer(*gen_answer(answer)) return # Successfully matched a query type diff --git a/queries/js/IoT_Embla/Philips_Hue/hub.js b/queries/js/IoT_Embla/Philips_Hue/hub.js index 088052f6..8248b4ad 100644 --- a/queries/js/IoT_Embla/Philips_Hue/hub.js +++ b/queries/js/IoT_Embla/Philips_Hue/hub.js @@ -1,14 +1,14 @@ "use strict"; async function findHub() { - // let hubArr = []; - // let hubObj = new Object(); - // hubObj.id = "ecb5fafffe1be1a4"; - // hubObj.internalipaddress = "192.168.1.68"; - // hubObj.port = "443"; - // console.log(hubObj); - // hubArr.push(hubObj); - // return hubArr[0]; + let hubArr = []; + let hubObj = new Object(); + hubObj.id = "ecb5fafffe1be1a4"; + hubObj.internalipaddress = "192.168.1.68"; + hubObj.port = "443"; + console.log(hubObj); + hubArr.push(hubObj); + return hubArr[0]; return fetch(`https://discovery.meethue.com`) .then((resp) => resp.json()) .then((obj) => { @@ -95,7 +95,7 @@ async function connectHub(clientID, requestURL) { function syncConnectHub(clientID, requestURL) { connectHub(clientID, requestURL); - return clientID; + return "Philips Hue miðstöðin hefur verið tengd"; } function syncConnectHubFromHTML() { diff --git a/queries/js/IoT_Embla/Philips_Hue/set_lights.js b/queries/js/IoT_Embla/Philips_Hue/set_lights.js index 77e99b3c..46c790c7 100644 --- a/queries/js/IoT_Embla/Philips_Hue/set_lights.js +++ b/queries/js/IoT_Embla/Philips_Hue/set_lights.js @@ -1,8 +1,8 @@ "use strict"; // Constants to be used when setting lights from HTML -var BRIDGE_IP = "192.168.1.68"; -var USERNAME = "MQH9DVv1lhgaOKN67uVox4fWNc9iu3j5g7MmdDUr"; +// var BRIDGE_IP = "192.168.1.68"; +// var USERNAME = "MQH9DVv1lhgaOKN67uVox4fWNc9iu3j5g7MmdDUr"; // TODO: Implement a hotfix for Ikea Tradfri bulbs, since it can only take one argument at a time From 27002d4df2b4b9638191499ce1e726747532cc56 Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Wed, 15 Jun 2022 16:27:00 +0000 Subject: [PATCH 030/371] meetHue hard-cording commented out --- queries/js/IoT_Embla/Philips_Hue/hub.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/queries/js/IoT_Embla/Philips_Hue/hub.js b/queries/js/IoT_Embla/Philips_Hue/hub.js index 8248b4ad..c9e5ec53 100644 --- a/queries/js/IoT_Embla/Philips_Hue/hub.js +++ b/queries/js/IoT_Embla/Philips_Hue/hub.js @@ -1,14 +1,14 @@ "use strict"; async function findHub() { - let hubArr = []; - let hubObj = new Object(); - hubObj.id = "ecb5fafffe1be1a4"; - hubObj.internalipaddress = "192.168.1.68"; - hubObj.port = "443"; - console.log(hubObj); - hubArr.push(hubObj); - return hubArr[0]; + // let hubArr = []; + // let hubObj = new Object(); + // hubObj.id = "ecb5fafffe1be1a4"; + // hubObj.internalipaddress = "192.168.1.68"; + // hubObj.port = "443"; + // console.log(hubObj); + // hubArr.push(hubObj); + // return hubArr[0]; return fetch(`https://discovery.meethue.com`) .then((resp) => resp.json()) .then((obj) => { From 0ab40e70b417efac8208e7d851db7cdcf7bfc60c Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Wed, 15 Jun 2022 16:46:33 +0000 Subject: [PATCH 031/371] some grammar changes --- queries/grammars/iot_hue.grammar | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/queries/grammars/iot_hue.grammar b/queries/grammars/iot_hue.grammar index 1f12c4e4..a3c65bdb 100644 --- a/queries/grammars/iot_hue.grammar +++ b/queries/grammars/iot_hue.grammar @@ -153,6 +153,9 @@ QIoTAdHverju -> QIoTAHvad -> "á" QIoTNewSetting/þf +QIoTIHvad -> + "í" QIoTNewSetting/þf + QIoTAHverju -> "á" QIoTLight/þgf | "á" QIoTNewSetting/þgf @@ -168,6 +171,12 @@ QIoTLight/fall -> QIoTLightName/fall | QIoTLightWord/fall +# Should 'birta' be included +QIoTLightWord/fall -> + 'ljós'/fall + | 'lýsing'/fall + | 'birta'/fall + QIoTColorWord/fall -> 'litur'/fall | 'litblær'/fall @@ -200,3 +209,8 @@ QIoTHuldaBrightness/fall -> | QIoTLess/fall | QIoTBrighter/fall | QIoTDarker/fall + +#Unsure about /fall after QIoTColorName +QIoTNewColor/fall -> + QIoTColorWord/fall QIoTColorName + | From aff45c34f6b6c056c69447a31d2dbd38355cc8cb Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Thu, 16 Jun 2022 09:45:00 +0000 Subject: [PATCH 032/371] grammar additions --- queries/grammars/iot_hue.grammar | 119 ++++++++++++++++++++----------- 1 file changed, 77 insertions(+), 42 deletions(-) diff --git a/queries/grammars/iot_hue.grammar b/queries/grammars/iot_hue.grammar index a3c65bdb..334973a2 100644 --- a/queries/grammars/iot_hue.grammar +++ b/queries/grammars/iot_hue.grammar @@ -1,7 +1,15 @@ +/þgf = þgf +/ef = ef + Query → QIoT -QIoT → QIoTQuery '?'? +QIoT → + QIoTQuery '?'? + | QIoTConnectLights '?'? + +QIoTConnectLights → + "tengdu" "ljósin" QIoTQuery -> QIoTMakeVerb QIoTMakeRest @@ -9,6 +17,7 @@ QIoTQuery -> | QIoTChangeVerb QIoTChangeRest | QIoTLetVerb QIoTLetRest | QIoTTurnVerb QIoTTurnRest + | QIoTIncreaseOrDecreaseVerb QIoTIncreaseOrDecreaseRest QIoTMakeVerb -> 'gera:so'_bh @@ -23,38 +32,53 @@ QIoTChangeVerb -> QIoTLetVerb -> 'láta:so'_bh +QIoTTurnVerb -> + 'kveikja:so'_bh + +QIoTIncreaseOrDecreaseVerb -> + QIoTIncreaseVerb + | QIoTDecreaseVerb + +QIoTIncreaseVerb -> + 'hækka:so'_bh + | 'auka:so'_bh + +QIoTDecreaseVerb -> + 'lækka:so'_bh + | 'minnka:so'_bh + QIoTMakeRest -> - QIoTHvad QIoTHvar QIoTHvernigMake - | QIoTHvad QIoTHvernigMake QIoTHvar - | QIoTHvar QIoTHvad QIoTHvernigMake - | QIoTHvar QIoTHvernigMake QIoTHvad - | QIoTHvernigMake QIoTHvad QIoTHvar - | QIoTHvernigMake QIoTHvar QIoTHvad + QIoTSubject/þf QIoTHvar QIoTHvernigMake + | QIoTSubject/þf QIoTHvernigMake QIoTHvar + | QIoTHvar QIoTSubject/þf QIoTHvernigMake + | QIoTHvar QIoTHvernigMake QIoTSubject/þf + | QIoTHvernigMake QIoTSubject/þf QIoTHvar + | QIoTHvernigMake QIoTHvar QIoTSubject/þf # TODO: Add support for "stilltu rauðan lit á ljósið í eldhúsinu" QIoTSetRest -> - QIoTHvad QIoTHvar QIoTHvernigSet - | QIoTHvad QIoTHvernigSet QIoTHvar - | QIoTHvar QIoTHvad QIoTHvernigSet - | QIoTHvar QIoTHvernigSet QIoTHvad - | QIoTHvernigSet QIoTHvad QIoTHvar - | QIoTHvernigSet QIoTHvar QIoTHvad + QIoTSubject/þf QIoTHvar QIoTHvernigSet + | QIoTSubject/þf QIoTHvernigSet QIoTHvar + | QIoTHvar QIoTSubject/þf QIoTHvernigSet + | QIoTHvar QIoTHvernigSet QIoTSubject/þf + | QIoTHvernigSet QIoTSubject/þf QIoTHvar + | QIoTHvernigSet QIoTHvar QIoTSubject/þf QIoTChangeRest -> - QIoTHverju QIoTHvar QIoTHvernigChange - | QIoTHverju QIoTHvernigChange QIoTHvar - | QIoTHvar QIoTHverju QIoTHvernigChange - | QIoTHvar QIoTHvernigChange QIoTHverju - | QIoTHvernigChange QIoTHverju QIoTHvar - | QIoTHvernigChange QIoTHvar QIoTHverju + QIoTSubjectOne/þgf QIoTHvar QIoTHvernigChange + | QIoTSubjectOne/þgf QIoTHvernigChange QIoTHvar + | QIoTHvar QIoTSubjectOne/þgf QIoTHvernigChange + | QIoTHvar QIoTHvernigChange QIoTSubjectOne/þgf + | QIoTHvernigChange QIoTSubjectOne/þgf QIoTHvar + | QIoTHvernigChange QIoTHvar QIoTSubjectOne/þgf QIoTLetRest -> - QIoTHvad QIoTHvar QIoTHvernigLet - | QIoTHvad QIoTHvernigLet QIoTHvar - | QIoTHvar QIoTHvad QIoTHvernigLet - | QIoTHvar QIoTHvernigLet QIoTHvad - | QIoTHvernigLet QIoTHvad QIoTHvar - | QIoTHvernigLet QIoTHvar QIoTHvad + QIoTSubject/þf QIoTHvar QIoTHvernigLet + | QIoTSubject/þf QIoTHvernigLet QIoTHvar + | QIoTHvar QIoTSubject/þf QIoTHvernigLet + | QIoTHvar QIoTHvernigLet QIoTSubject/þf + | QIoTHvernigLet QIoTSubject/þf QIoTHvar + | QIoTHvernigLet QIoTHvar QIoTSubject/þf QIoTTurnRest -> QIoTLightObject QIoTHvar @@ -62,19 +86,24 @@ QIoTTurnRest -> | QIoTAHverju QIoTHvar | QIoTHvar QIoTAHverju -QIoTHvad -> - QIoTLightSubject/nf - | QIoTColorSubject/nf - | QIoTBrightnessSubject/nf - | QIoTSceneSubject/nf - | QIoTGroupNameSubject/nf #á bara að styðja "gerðu eldhúsið rautt", "gerðu eldhúsið rómó" "gerðu eldhúsið bjartara", t.d. +# TODO: Make the subject categorization cleaner +QIoTIncreaseOrDecreaseRest -> + QIoTLightSubject/þgf QIoTHvar + | QIoTBrightnessSubject/þgf QIoTHvar + +QIoTSubject/fall -> + QIoTSubjectOne/fall + | QIoTSubjectTwo/fall # TODO: Decide whether LightSubject/þgf should be accepted -QIoTHverju -> - QIoTLightSubject/þgf - | QIoTColorSubject/þgf - | QIoTBrightnessSubject/þgf - | QIoTSceneSubject/þgf +QIoTSubjectOne/fall -> + QIoTLightSubject/fall + | QIoTColorSubject/fall + | QIoTBrightnessSubject/fall + | QIoTSceneSubject/fall + +QIoTSubjectTwo/fall -> + QIoTGroupNameSubject/fall # á bara að styðja "gerðu eldhúsið rautt", "gerðu eldhúsið rómó" "gerðu eldhúsið bjartara", t.d. QIoTHvar -> QIoTLocationPreposition QIoTGroupName/þgf @@ -205,12 +234,18 @@ QIoTNewSetting/fall -> | QIoTNewScene/fall QIoTHuldaBrightness/fall -> - QIoTMore/fall - | QIoTLess/fall - | QIoTBrighter/fall - | QIoTDarker/fall + QIoTMoreBrighterOrHigher/fall + | QIoTLessDarkerOrLower/fall -#Unsure about /fall after QIoTColorName +#Unsure about whether to include /fall after QIoTColorName QIoTNewColor/fall -> QIoTColorWord/fall QIoTColorName - | + | QIoTColorName/fall QIoTColorWord/fall? + +QIoTNewBrightness/fall -> + 'sá'/fall? QIoTBrightestOrDarkest/fall + | QIoTBrightestOrDarkest/fall QIoTBrightnessOrSettingWord/fall + +QIoTNewScene/fall -> + QIoTSceneWord/fall QIoTSceneName + | QIoTSceneName QIoTSceneWord/fall \ No newline at end of file From d666f6693575a066d9b5b892b4a6b67eeb14dd02 Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Thu, 16 Jun 2022 10:48:25 +0000 Subject: [PATCH 033/371] grammar changes --- queries/grammars/iot_hue.grammar | 51 ++- queries/iot_hue.py | 601 ++++++++++++++----------------- 2 files changed, 318 insertions(+), 334 deletions(-) diff --git a/queries/grammars/iot_hue.grammar b/queries/grammars/iot_hue.grammar index 334973a2..4fe254b5 100644 --- a/queries/grammars/iot_hue.grammar +++ b/queries/grammars/iot_hue.grammar @@ -81,8 +81,8 @@ QIoTLetRest -> | QIoTHvernigLet QIoTHvar QIoTSubject/þf QIoTTurnRest -> - QIoTLightObject QIoTHvar - | QIoTHvar QIoTLightObject + QIoTLightSubject/þf QIoTHvar + | QIoTHvar QIoTLightSubject/þf | QIoTAHverju QIoTHvar | QIoTHvar QIoTAHverju @@ -169,8 +169,8 @@ QIoTGroupName/fall -> QIoTLightName/fall -> no/fall -QIoTColorName/fall -> - {" | ".join(f"'{color}:lo'/fall" for color in _COLORS.keys())} +QIoTColorName -> + {" | ".join(f"'{color}:lo'" for color in _COLORS.keys())} QIoTAnnadAndlag -> QIoTNewSetting/nf @@ -193,7 +193,7 @@ QIoTSomethingOrSomehow -> QIoTAnnadAndlag | QIoTAdHverju -QIotSomehow -> +QIoTSomehow -> QIoTAnnadAndlag QIoTLight/fall -> @@ -211,6 +211,10 @@ QIoTColorWord/fall -> | 'litblær'/fall | 'blær'/fall +QIoTBrightnessWords/fall -> + 'bjartur'/fall + | QIoTBrightnessWord/fall + QIoTBrightnessWord/fall -> 'birta'/fall | 'birtustig'/fall @@ -233,14 +237,15 @@ QIoTNewSetting/fall -> | QIoTNewBrightness/fall | QIoTNewScene/fall +# Missing "meira dimmt" QIoTHuldaBrightness/fall -> - QIoTMoreBrighterOrHigher/fall - | QIoTLessDarkerOrLower/fall + QIoTMoreBrighterOrHigher/fall QIoTBrightnessWords/fall? + | QIoTLessDarkerOrLower/fall QIoTBrightnessWords/fall? #Unsure about whether to include /fall after QIoTColorName QIoTNewColor/fall -> QIoTColorWord/fall QIoTColorName - | QIoTColorName/fall QIoTColorWord/fall? + | QIoTColorName QIoTColorWord/fall? QIoTNewBrightness/fall -> 'sá'/fall? QIoTBrightestOrDarkest/fall @@ -248,4 +253,32 @@ QIoTNewBrightness/fall -> QIoTNewScene/fall -> QIoTSceneWord/fall QIoTSceneName - | QIoTSceneName QIoTSceneWord/fall \ No newline at end of file + | QIoTSceneName QIoTSceneWord/fall + +QIoTMoreBrighterOrHigher/fall -> + 'mikill:lo'_mst/fall + | 'bjartur:lo'_mst/fall + | 'ljós:lo'_mst/fall + | 'hár:lo'_mst/fall + +QIoTLessDarkerOrLower/fall -> + 'lítill:lo'_mst/fall + | 'dökkur:lo'_mst/fall + | 'dimmur:lo'_mst/fall + | 'lágur:lo'_mst/fall + +QIoTBrightestOrDarkest/fall -> + QIoTBrightest/fall + | QIoTDarkest/fall + +QIoTBrightest/fall -> + 'bjartur:lo'_evb + | 'bjartur:lo'_esb + | 'ljós:lo'_evb + | 'ljós:lo'_esb + +QIoTDarkest/fall -> + 'dimmur:lo'_evb + | 'dimmur:lo'_esb + | 'dökkur:lo'_evb + | 'dökkur:lo'_esb \ No newline at end of file diff --git a/queries/iot_hue.py b/queries/iot_hue.py index 0432b6a2..d847234d 100755 --- a/queries/iot_hue.py +++ b/queries/iot_hue.py @@ -28,7 +28,7 @@ # TODO: ditto the previous comment. make the initial non-terminals general and go into specifics at the terminal level instead. # TODO: substituion klósett, baðherbergi hugmyndÆ senda lista i javascript og profa i röð # TODO: Embla stores old javascript code cached which has caused errors -# TODO: Cut down javascript sent to Embla +# TODO: Cut down javascript sent to Embla from typing import Dict, Mapping, Optional, cast from typing_extensions import TypedDict @@ -98,255 +98,168 @@ def help_text(lemma: str) -> str: GRAMMAR = f""" +/þgf = þgf +/ef = ef + Query → QIoT QIoT → - QIoTQuery '?'? + QIoTQuery '?'? | QIoTConnectLights '?'? QIoTConnectLights → "tengdu" "ljósin" -QIoTQuery → - QIoTTurnOn - | QIoTTurnOff - | QIoTSetColor - | QIoTIncreaseBrightness - | QIoTDecreaseBrightness - | QIoTMaxBrightness - | QIoTMinBrightness - | QIoTSetScene - -QIoTTurnOn -> - "kveiktu" QIoTLightPhrase - | "kveiktu" "á" QIoTLightPhrase - -QIoTTurnOff -> - "slökktu" QIoTLightPhrase - | "slökktu" "á" QIoTLightPhrase - -QIoTSetColor -> - QIoTMakeVerb? QIoTMakeColorObject - | QIoTSetVerb QIoTSetColorObject - | QIoTChangeVerb QIoTChangeColorObject - -QIoTMakeColorObject -> - QIoTColorLightPhrase "að"? QIoTColorNamePhrase - | QIoTColorLight "að"? QIoTColorNamePhrase QIoTGroupNamePhrase? - | QIoTColorNamePhrase QIoTGroupNamePhrase? - | QIoTColorNamePhrase QIoTLocationPreposition QIoTLightPhrase - -QIoTSetColorObject -> - QIoTColorLightPhrase "á" QIoTColorNamePhrase - | QIoTColorLight "á" QIoTColorNamePhrase QIoTGroupNamePhrase? - | "á"? QIoTColorNamePhrase QIoTGroupNamePhrase? - | "á"? QIoTColorNamePhrase QIoTLocationPreposition QIoTLightPhrase - -QIoTChangeColorObject -> - QIoTColorLightPhrase "í" QIoTColorNamePhrase - | QIoTColorLight "í" QIoTColorNamePhrase QIoTGroupNamePhrase? - | "í" QIoTColorNamePhrase QIoTGroupNamePhrase? - | "í" QIoTColorNamePhrase QIoTLocationPreposition QIoTLightPhrase - -# Need to add "gerðu minni birtu" functionality. -QIoTIncreaseBrightness -> - QIoTIncrease QIoTBrightness QIoTLightPhrase? - | QIoTMakeVerb? QIoTMakeBrighterObject - # | QIoTSetVerb QIoTSetBrightObject - -QIoTMakeBrighterObject -> - QIoTBrightnessLightPhrase QIoTBrighterPhrase - | QIoTBrightnessLight QIoTMoreOrBrighter QIoTGroupNamePhrase? - | QIoTMoreBrightness QIoTGroupNamePhrase? - | QIoTMoreBrightness QIoTLocationPreposition QIoTLightPhrase - -QIoTSetBrightObject -> - QIoTColorLightPhrase "á" QIoTColorNamePhrase - | QIoTColorLight "á" QIoTColorNamePhrase QIoTGroupNamePhrase? - | "á"? QIoTColorNamePhrase QIoTGroupNamePhrase? - | "á"? QIoTColorNamePhrase QIoTLocationPreposition QIoTLightPhrase - -QIoTDecreaseBrightness -> - QIoTDecrease QIoTBrightness QIoTLightPhrase? - | QIoTMakeVerb? QIoTMakeDarkerObject - -QIoTMakeDarkerObject -> - QIoTBrightnessLightPhrase QIoTDarkerPhrase - | QIoTBrightnessLight QIoTLessOrDarker QIoTGroupNamePhrase? - | QIoTLessOrDarker QIoTGroupNamePhrase? - | QIoTLessOrDarker QIoTLocationPreposition QIoTLightPhrase - -QIoTMaxBrightness -> - # ?QIoTMakeVerb QIoTMakeBrightestObject - QIoTSetVerb QIoTSetBrightestObject - -# QIoTMakeBrightestObject -> -# QIoTBrightnessLightPhrase QIoTMoreOrBrighter -# | QIoTBrightnessLight QIoTMoreOrBrighter QIoTGroupNamePhrase? -# | QIoTMoreBrightness QIoTGroupNamePhrase? -# | QIoTMoreBrightness QIoTLocationPreposition QIoTLightPhrase - -QIoTSetBrightestObject -> - QIoTBrightnessLightPhrase "á" QIoTMostOrBrightest - | QIoTBrightnessLight "á" QIoTMostOrHighest QIoTGroupNamePhrase? - | "á"? QIoTBrightestPhrase QIoTGroupNamePhrase? - | "á"? QIoTBrightestPhrase QIoTLocationPreposition QIoTLightPhrase - -QIoTMinBrightness -> - # ?QIoTMakeVerb QIoTMakeBrightestObject - QIoTSetVerb QIoTSetDarkestObject - -# QIoTMakeDarkestObject -> -# QIoTBrightnessLightPhrase QIoTMoreOrBrighter -# | QIoTBrightnessLight QIoTMoreOrBrighter QIoTGroupNamePhrase? -# | QIoTMoreBrightness QIoTGroupNamePhrase? -# | QIoTMoreBrightness QIoTLocationPreposition QIoTLightPhrase - -QIoTSetDarkestObject -> - QIoTBrightnessLightPhrase "á" QIoTLeastOrDarkest - | QIoTBrightnessLight "á" QIoTLeastOrLowest QIoTGroupNamePhrase? - | "á"? QIoTDarkestPhrase QIoTGroupNamePhrase? - | "á"? QIoTDarkestPhrase QIoTLocationPreposition QIoTLightPhrase - -QIoTSetScene -> - QIoTMakeVerb QIoTMakeSceneObject - | QIoTSetVerb QIoTSetSceneObject - | QIoTChangeVerb QIoTChangeSceneObject - -QIoTMakeSceneObject -> - QIoTSceneLightPhrase "að"? QIoTSceneNamePhrase - | QIoTScene "að"? QIoTSceneNamePhrase QIoTGroupNamePhrase? - | QIoTSceneNamePhrase QIoTGroupNamePhrase - | QIoTSceneNamePhrase QIoTLocationPreposition QIoTLightPhrase - -QIoTSetSceneObject -> - QIoTSceneLightPhrase "á" QIoTSceneNamePhrase - | QIoTScene "á" QIoTSceneNamePhrase QIoTGroupNamePhrase? - | "á"? QIoTSceneNamePhrase QIoTGroupNamePhrase? - | "á"? QIoTSceneNamePhrase QIoTLocationPreposition QIoTLightPhrase - -QIoTChangeSceneObject -> - QIoTSceneLightPhrase "í" QIoTSceneNamePhrase - | QIoTScene "í" QIoTSceneNamePhrase QIoTGroupNamePhrase? - | "í" QIoTSceneNamePhrase QIoTGroupNamePhrase? - | "í" QIoTSceneNamePhrase QIoTLocationPreposition QIoTLightPhrase - -QIoTLeastOrDarkest -> - QIoTLeastOrLowest - | QIoTDarkestPhrase - -QIoTLeastOrLowest -> - QIoTLeast - | QIoTLowest - -QIoTDarkestPhrase -> - QIoTDarkest - | QIoTLeastOrLowest QIoTBrightness - | QIoTMostOrHighest QIoTDarkness - -QIoTMostOrBrightest -> - QIoTMostOrHighest - | QIoTBrightestPhrase - -QIoTMostOrHighest -> - QIoTMost - | QIoTHighest - -QIoTBrightestPhrase -> - QIoTBrightest - | QIoTMostOrHighest QIoTBrightness - | QIoTLeastOrLowest QIoTDarkness - -QIoTMoreBrightness -> - QIoTMoreOrHigher QIoTBrightness - | QIoTBrighterPhrase - -QIoTMoreOrHigher -> - 'mikill:lo'_mst - | 'hár:lo'_mst - -QIoTMoreOrBrighter -> - QIoTMore - | QIoTBrighterPhrase - -QIoTBrighterPhrase -> - QIoTBrighter - | QIoTMore QIoTBright - | QIoTLess QIoTDark - -QIoTLessOrDarker -> - QIoTDarker - | QIoTLess - | QIoTLess QIoTBright - | QIoTMore QIoTDark - -QIoTLessOrDarker -> - QIoTLess - | QIoTDarkerPhrase - -QIoTDarkerPhrase -> - QIoTDarker - | QIoTMore QIoTDark - | QIoTLess QIoTBright - -QIoTBrightnessLightPhrase -> - QIoTBrightnessLight QIoTGroupNamePhrase? - | QIoTGroupName - -QIoTBrightnessLight -> - QIoTBrightness? QIoTLight - | QIoTBrightness "á" QIoTLight - | QIoTBrightness - -QIoTColorLightPhrase -> - QIoTColorLight QIoTGroupNamePhrase? - | QIoTGroupName - -# Separate cases for "lit ljóssins" and "litinn á ljósinu", to be precise. But it is not ideal as is -QIoTColorLight -> - QIoTColor? QIoTLight - | QIoTColor "á" QIoTLight - | QIoTColor - -QIoTSceneLightPhrase -> - QIoTScene QIoTGroupNamePhrase - -QIoTLightPhrase -> - QIoTLight QIoTGroupNamePhrase? - | QIoTGroupNamePhrase - -# tried making this 'ljós:no' to avoid ambiguity, but all queries failed as a result -QIoTLight -> - QIoTLightWord - | QIoTLightName +QIoTQuery -> + QIoTMakeVerb QIoTMakeRest + | QIoTSetVerb QIoTSetRest + | QIoTChangeVerb QIoTChangeRest + | QIoTLetVerb QIoTLetRest + | QIoTTurnOnVerb QIoTTurnOnRest + | QIoTTurnOffVerb QIoTTurnOffRest + | QIoTIncreaseOrDecreaseVerb QIoTIncreaseOrDecreaseRest -QIoTColorName -> - {" | ".join(f"'{color}:lo'" for color in _COLORS.keys())} +QIoTMakeVerb -> + 'gera:so'_bh + +QIoTSetVerb -> + 'setja:so'_bh + | 'stilla:so'_bh -QIoTColorNamePhrase -> - QIoTColor? QIoTColorName - | QIoTColorName QIoTColor? - | QIoTColorName QIoTLight? +QIoTChangeVerb -> + 'breyta:so'_bh -QIoTSceneName -> - no +QIoTLetVerb -> + 'láta:so'_bh -QIoTSceneNamePhrase -> - QIoTScene? QIoTSceneName - | QIoTSceneName QIoTScene? - | QIoTSceneName QIoTLight? +QIoTTurnOnVerb -> + 'kveikja:so'_bh -QIoTGroupNamePhrase -> - QIoTLocationPreposition QIoTGroupName +QIoTTurnOffVerb -> + 'slökkva:so'_bh + +QIoTIncreaseOrDecreaseVerb -> + QIoTIncreaseVerb + | QIoTDecreaseVerb + +QIoTIncreaseVerb -> + 'hækka:so'_bh + | 'auka:so'_bh -# The Nl, noun phrase, is too greedy, e.g. parsing "ljósin í eldhúsinu" as the group name. -# But no, noun, is too strict, e.g. "herbergið hans Loga" could be a user-made group name. -QIoTGroupName -> - no +QIoTDecreaseVerb -> + 'lækka:so'_bh + | 'minnka:so'_bh -QIoTLightName -> - no +QIoTMakeRest -> + QIoTSubject/þf QIoTHvar QIoTHvernigMake + | QIoTSubject/þf QIoTHvernigMake QIoTHvar + | QIoTHvar QIoTSubject/þf QIoTHvernigMake + | QIoTHvar QIoTHvernigMake QIoTSubject/þf + | QIoTHvernigMake QIoTSubject/þf QIoTHvar + | QIoTHvernigMake QIoTHvar QIoTSubject/þf + +# TODO: Add support for "stilltu rauðan lit á ljósið í eldhúsinu" +QIoTSetRest -> + QIoTSubject/þf QIoTHvar QIoTHvernigSet + | QIoTSubject/þf QIoTHvernigSet QIoTHvar + | QIoTHvar QIoTSubject/þf QIoTHvernigSet + | QIoTHvar QIoTHvernigSet QIoTSubject/þf + | QIoTHvernigSet QIoTSubject/þf QIoTHvar + | QIoTHvernigSet QIoTHvar QIoTSubject/þf + +QIoTChangeRest -> + QIoTSubjectOne/þgf QIoTHvar QIoTHvernigChange + | QIoTSubjectOne/þgf QIoTHvernigChange QIoTHvar + | QIoTHvar QIoTSubjectOne/þgf QIoTHvernigChange + | QIoTHvar QIoTHvernigChange QIoTSubjectOne/þgf + | QIoTHvernigChange QIoTSubjectOne/þgf QIoTHvar + | QIoTHvernigChange QIoTHvar QIoTSubjectOne/þgf + +QIoTLetRest -> + QIoTSubject/þf QIoTHvar QIoTHvernigLet + | QIoTSubject/þf QIoTHvernigLet QIoTHvar + | QIoTHvar QIoTSubject/þf QIoTHvernigLet + | QIoTHvar QIoTHvernigLet QIoTSubject/þf + | QIoTHvernigLet QIoTSubject/þf QIoTHvar + | QIoTHvernigLet QIoTHvar QIoTSubject/þf + +QIoTTurnOnRest -> + QIoTTurnOnRest + | QIoTAHverju QIoTHvar + | QIoTHvar QIoTAHverju + +QIoTTurnOnLightsRest -> + QIoTLightSubject/þf QIoTHvar + | QIoTHvar QIoTLightSubject/þf + +QIoTTurnOffRest -> + QIoTTurnOffLightsRest + +QIoTTurnOnLightsRest -> + QIoTLightSubject/þf QIoTHvar + | QIoTHvar QIoTLightSubject/þf + +# TODO: Make the subject categorization cleaner +QIoTIncreaseOrDecreaseRest -> + QIoTLightSubject/þgf QIoTHvar + | QIoTBrightnessSubject/þgf QIoTHvar + +QIoTSubject/fall -> + QIoTSubjectOne/fall + | QIoTSubjectTwo/fall + +# TODO: Decide whether LightSubject/þgf should be accepted +QIoTSubjectOne/fall -> + QIoTLightSubject/fall + | QIoTColorSubject/fall + | QIoTBrightnessSubject/fall + | QIoTSceneSubject/fall + +QIoTSubjectTwo/fall -> + QIoTGroupNameSubject/fall # á bara að styðja "gerðu eldhúsið rautt", "gerðu eldhúsið rómó" "gerðu eldhúsið bjartara", t.d. + +QIoTHvar -> + QIoTLocationPreposition QIoTGroupName/þgf + +QIoTHvernigMake -> + QIoTAnnadAndlag # gerðu litinn rauðan í eldhúsinu EÐA gerðu birtuna meiri í eldhúsinu + | QIoTAdHverju # gerðu litinn að rauðum í eldhúsinu + +QIoTHvernigSet -> + QIoTAHvad + +QIoTHvernigChange -> + QIoTIHvad + +QIoTHvernigLet -> + QIoTBecome QIoTSomethingOrSomehow + | QIoTBe QIoTSomehow + +# I think these verbs only appear in these forms. +# In which case these terminals should be deleted and a direct reference should be made in the relevant non-terminals. +QIoTBe -> + "vera" + +QIoTBecome -> + "verða" + +QIoTLightSubject/fall -> + QIoTLight/fall + +QIoTColorSubject/fall -> + QIoTColorWord/fall QIoTLight/ef? + | QIoTColorWord/fall "á" QIoTLight/þgf + +QIoTBrightnessSubject/fall -> + QIoTBrightnessWord/fall QIoTLight/ef? + | QIoTBrightnessWord/fall "á" QIoTLight/þgf + +QIoTSceneSubject/fall -> + QIoTSceneWord/fall + +QIoTGroupNameSubject/fall -> + QIoTGroupName/fall QIoTLocationPreposition -> QIoTLocationPrepositionFirstPart? QIoTLocationPrepositionSecondPart @@ -364,107 +277,145 @@ def help_text(lemma: str) -> str: QIoTLocationPrepositionSecondPart -> "á" | "í" -QIoTBright -> - 'bjartur:lo'_fst - | 'ljós:lo'_fst - | "Bjart" - | "bjart" - -QIoTDarkest -> - 'dimmur:lo'_evb - | 'dimmur:lo'_esb - | 'dökkur:lo'_evb - | 'dökkur:lo'_esb +QIoTGroupName/fall -> + no/fall -QIoTLeast -> - 'lítill:lo'_evb - | 'lítill:lo'_esb - | 'lítið:ao'_est +QIoTLightName/fall -> + no/fall -QIoTLowest -> - 'lágur:lo'_evb - | 'lágur:lo'_esb +QIoTColorName -> + {" | ".join(f"'{color}:lo'" for color in _COLORS.keys())} -QIoTBrightest -> +QIoTAnnadAndlag -> + QIoTNewSetting/nf + | QIoTSpyrjaHuldu/nf + +QIoTAdHverju -> + "að" QIoTNewSetting/þgf + +QIoTAHvad -> + "á" QIoTNewSetting/þf + +QIoTIHvad -> + "í" QIoTNewSetting/þf + +QIoTAHverju -> + "á" QIoTLight/þgf + | "á" QIoTNewSetting/þgf + +QIoTSomethingOrSomehow -> + QIoTAnnadAndlag + | QIoTAdHverju + +QIoTSomehow -> + QIoTAnnadAndlag + +QIoTLight/fall -> + QIoTLightName/fall + | QIoTLightWord/fall + +# Should 'birta' be included +QIoTLightWord/fall -> + 'ljós'/fall + | 'lýsing'/fall + | 'birta'/fall + +QIoTColorWord/fall -> + 'litur'/fall + | 'litblær'/fall + | 'blær'/fall + +QIoTBrightnessWords/fall -> + 'bjartur'/fall + | QIoTBrightnessWord/fall + +QIoTBrightnessWord/fall -> + 'birta'/fall + | 'birtustig'/fall + +QIoTSceneWord/fall -> + 'sena'/fall + | 'stemning'/fall + | 'stemming'/fall + | 'stemmning'/fall + +# Need to ask Hulda how this works. +QIoTSpyrjaHuldu/fall -> + # QIoTHuldaColor/fall + QIoTHuldaBrightness/fall + # | QIoTHuldaScene/fall + +# Do I need a "new light state" non-terminal? +QIoTNewSetting/fall -> + QIoTNewColor/fall + | QIoTNewBrightness/fall + | QIoTNewScene/fall + +# Missing "meira dimmt" +QIoTHuldaBrightness/fall -> + QIoTMoreBrighterOrHigher/fall QIoTBrightnessWords/fall? + | QIoTLessDarkerOrLower/fall QIoTBrightnessWords/fall? + +#Unsure about whether to include /fall after QIoTColorName +QIoTNewColor/fall -> + QIoTColorWord/fall QIoTColorName + | QIoTColorName QIoTColorWord/fall? + +QIoTNewBrightness/fall -> + 'sá'/fall? QIoTBrightestOrDarkest/fall + | QIoTBrightestOrDarkest/fall QIoTBrightnessOrSettingWord/fall + +QIoTNewScene/fall -> + QIoTSceneWord/fall QIoTSceneName + | QIoTSceneName QIoTSceneWord/fall + +QIoTMoreBrighterOrHigher/fall -> + 'mikill:lo'_mst/fall + | 'bjartur:lo'_mst/fall + | 'ljós:lo'_mst/fall + | 'hár:lo'_mst/fall + +QIoTLessDarkerOrLower/fall -> + 'lítill:lo'_mst/fall + | 'dökkur:lo'_mst/fall + | 'dimmur:lo'_mst/fall + | 'lágur:lo'_mst/fall + +QIoTBrightestOrDarkest/fall -> + QIoTBrightest/fall + | QIoTDarkest/fall + +QIoTBrightest/fall -> 'bjartur:lo'_evb | 'bjartur:lo'_esb | 'ljós:lo'_evb | 'ljós:lo'_esb -QIoTMost -> - 'mikill:lo'_evb - | 'mikill:lo'_esb - | 'mikið:ao'_est - -QIoTHighest -> - 'hár:lo'_evb - | 'hár:lo'_esb - -QIoTBrighter -> - 'bjartur:lo'_mst - | 'ljós:lo'_mst - -QIoTDark -> - 'dimmur:lo'_fst - | 'dökkur:lo'_fst - -QIoTDarker -> - 'dimmur:lo'_mst - | 'dökkur:lo'_mst - -QIoTLessOrLower -> - 'lítill:lo'_mst - | 'lágur:lo'_mst - -QIoTIncrease -> - 'hækka:so'_bh - | 'auka:so'_bh - -QIoTDecrease -> - 'lækka:so'_bh - | 'minnka:so'_bh - -QIoTMore -> - "meiri" - | "meira" - -QIoTLess -> - "minni" - | "minna" - -QIoTSetVerb -> - 'setja:so'_bh - | 'stilla:so'_bh - -QIoTMakeVerb -> - 'gera:so'_bh +QIoTDarkest/fall -> + 'dimmur:lo'_evb + | 'dimmur:lo'_esb + | 'dökkur:lo'_evb + | 'dökkur:lo'_esb -QIoTChangeVerb -> - 'breyta:so'_bh +""" -QIoTLightWord -> - 'ljós' - | 'lýsing' +def QIoTColorWord(node: Node, params: QueryStateDict, result: Result) -> None: + if "keyword" in result: + pass -QIoTColor -> - 'litur' - | 'litblær' - | 'blær' + result.keyword = "color" -QIoTScene -> - 'sena' - | 'stemning' - | 'stemming' - | 'stemmning' +def QIoTSceneWord(node: Node, params: QueryStateDict, result: Result) -> None: + if "keyword" in result: + pass -QIoTDarkness -> - 'myrkur' + result.keyword = "scene" -QIoTBrightness -> - 'birta' - | 'birtustig' -""" +def QIoTBrightnessWord(node: Node, params: QueryStateDict, result: Result) -> None: + if "keyword" in result: + pass + result.keyword = "brightness" def QIoTQuery(node: Node, params: QueryStateDict, result: Result) -> None: result.qtype = _IoT_QTYPE @@ -561,10 +512,13 @@ def QIoTGroupName(node: Node, params: QueryStateDict, result: Result) -> None: def QIoTLightName(node: Node, params: QueryStateDict, result: Result) -> None: result["light_name"] = result._indefinite + def QIoTConnectLights(node: Node, params: QueryStateDict, result: Result) -> None: result.qtype = "connect_lights" result.action = "connect_lights" +def QIoTColorWord(node: Node, params: QueryStateDict, result: Result) -> None: + result.keyword # Convert color name into hue # Taken from home.py @@ -602,7 +556,6 @@ def sentence(state: QueryStateDict, result: Result) -> None: return - smartdevice_type = "smartlights" client_id = str(q.client_id) print("client_id:", client_id) @@ -613,8 +566,7 @@ def sentence(state: QueryStateDict, result: Result) -> None: selected_light: Optional[str] = None hue_credentials: Optional[Dict[str, str]] = None - - + if device_data is not None and smartdevice_type in device_data: dev = device_data[smartdevice_type] assert dev is not None @@ -622,7 +574,6 @@ def sentence(state: QueryStateDict, result: Result) -> None: hue_credentials = dev.get("philips_hue") bridge_ip = hue_credentials.get("ipAddress") username = hue_credentials.get("username") - if not device_data or not hue_credentials: answer = "Philips Hue hefur ekki verið tengt" @@ -669,4 +620,4 @@ def sentence(state: QueryStateDict, result: Result) -> None: q.set_error("E_EXCEPTION: {0}".format(e)) raise - # f"var BRIDGE_IP = '192.168.1.68';var USERNAME = 'p3obluiXT13IbHMpp4X63ZvZnpNRdbqqMt723gy2';" \ No newline at end of file + # f"var BRIDGE_IP = '192.168.1.68';var USERNAME = 'p3obluiXT13IbHMpp4X63ZvZnpNRdbqqMt723gy2';" From 95641232f1ddef679c7ef8d66cc76ca3953d3358 Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Thu, 16 Jun 2022 11:53:22 +0000 Subject: [PATCH 034/371] grammar completion, for now completed iot_hue grammar, fixed iot_hue.py issues resulting from new terminology. changed test_queries to work for current grammar options --- queries/grammars/iot_hue.grammar | 97 ++++++++++++------- queries/iot_hue.py | 158 ++++++++++++++++++++----------- tests/test_queries.py | 15 ++- 3 files changed, 169 insertions(+), 101 deletions(-) diff --git a/queries/grammars/iot_hue.grammar b/queries/grammars/iot_hue.grammar index 4fe254b5..8b1c4457 100644 --- a/queries/grammars/iot_hue.grammar +++ b/queries/grammars/iot_hue.grammar @@ -16,7 +16,8 @@ QIoTQuery -> | QIoTSetVerb QIoTSetRest | QIoTChangeVerb QIoTChangeRest | QIoTLetVerb QIoTLetRest - | QIoTTurnVerb QIoTTurnRest + | QIoTTurnOnVerb QIoTTurnOnRest + | QIoTTurnOffVerb QIoTTurnOffRest | QIoTIncreaseOrDecreaseVerb QIoTIncreaseOrDecreaseRest QIoTMakeVerb -> @@ -32,9 +33,12 @@ QIoTChangeVerb -> QIoTLetVerb -> 'láta:so'_bh -QIoTTurnVerb -> +QIoTTurnOnVerb -> 'kveikja:so'_bh +QIoTTurnOffVerb -> + 'slökkva:so'_bh + QIoTIncreaseOrDecreaseVerb -> QIoTIncreaseVerb | QIoTDecreaseVerb @@ -48,48 +52,59 @@ QIoTDecreaseVerb -> | 'minnka:so'_bh QIoTMakeRest -> - QIoTSubject/þf QIoTHvar QIoTHvernigMake - | QIoTSubject/þf QIoTHvernigMake QIoTHvar - | QIoTHvar QIoTSubject/þf QIoTHvernigMake - | QIoTHvar QIoTHvernigMake QIoTSubject/þf - | QIoTHvernigMake QIoTSubject/þf QIoTHvar - | QIoTHvernigMake QIoTHvar QIoTSubject/þf + QIoTSubject/þf QIoTHvar? QIoTHvernigMake + | QIoTSubject/þf QIoTHvernigMake QIoTHvar? + | QIoTHvar? QIoTSubject/þf QIoTHvernigMake + | QIoTHvar? QIoTHvernigMake QIoTSubject/þf + | QIoTHvernigMake QIoTSubject/þf QIoTHvar? + | QIoTHvernigMake QIoTHvar? QIoTSubject/þf # TODO: Add support for "stilltu rauðan lit á ljósið í eldhúsinu" QIoTSetRest -> - QIoTSubject/þf QIoTHvar QIoTHvernigSet - | QIoTSubject/þf QIoTHvernigSet QIoTHvar - | QIoTHvar QIoTSubject/þf QIoTHvernigSet - | QIoTHvar QIoTHvernigSet QIoTSubject/þf - | QIoTHvernigSet QIoTSubject/þf QIoTHvar - | QIoTHvernigSet QIoTHvar QIoTSubject/þf + QIoTSubject/þf QIoTHvar? QIoTHvernigSet + | QIoTSubject/þf QIoTHvernigSet QIoTHvar? + | QIoTHvar? QIoTSubject/þf QIoTHvernigSet + | QIoTHvar? QIoTHvernigSet QIoTSubject/þf + | QIoTHvernigSet QIoTSubject/þf QIoTHvar? + | QIoTHvernigSet QIoTHvar? QIoTSubject/þf QIoTChangeRest -> - QIoTSubjectOne/þgf QIoTHvar QIoTHvernigChange - | QIoTSubjectOne/þgf QIoTHvernigChange QIoTHvar - | QIoTHvar QIoTSubjectOne/þgf QIoTHvernigChange - | QIoTHvar QIoTHvernigChange QIoTSubjectOne/þgf - | QIoTHvernigChange QIoTSubjectOne/þgf QIoTHvar - | QIoTHvernigChange QIoTHvar QIoTSubjectOne/þgf + QIoTSubjectOne/þgf QIoTHvar? QIoTHvernigChange + | QIoTSubjectOne/þgf QIoTHvernigChange QIoTHvar? + | QIoTHvar? QIoTSubjectOne/þgf QIoTHvernigChange + | QIoTHvar? QIoTHvernigChange QIoTSubjectOne/þgf + | QIoTHvernigChange QIoTSubjectOne/þgf QIoTHvar? + | QIoTHvernigChange QIoTHvar? QIoTSubjectOne/þgf QIoTLetRest -> - QIoTSubject/þf QIoTHvar QIoTHvernigLet - | QIoTSubject/þf QIoTHvernigLet QIoTHvar - | QIoTHvar QIoTSubject/þf QIoTHvernigLet - | QIoTHvar QIoTHvernigLet QIoTSubject/þf - | QIoTHvernigLet QIoTSubject/þf QIoTHvar - | QIoTHvernigLet QIoTHvar QIoTSubject/þf - -QIoTTurnRest -> - QIoTLightSubject/þf QIoTHvar - | QIoTHvar QIoTLightSubject/þf - | QIoTAHverju QIoTHvar - | QIoTHvar QIoTAHverju + QIoTSubject/þf QIoTHvar? QIoTHvernigLet + | QIoTSubject/þf QIoTHvernigLet QIoTHvar? + | QIoTHvar? QIoTSubject/þf QIoTHvernigLet + | QIoTHvar? QIoTHvernigLet QIoTSubject/þf + | QIoTHvernigLet QIoTSubject/þf QIoTHvar? + | QIoTHvernigLet QIoTHvar? QIoTSubject/þf + +QIoTTurnOnRest -> + QIoTTurnOnLightsRest + | QIoTAHverju QIoTHvar? + | QIoTHvar? QIoTAHverju + +QIoTTurnOnLightsRest -> + QIoTLightSubject/þf QIoTHvar? + | QIoTHvar? QIoTLightSubject/þf + +# Would be good to add "slökktu á rauða litnum" functionality +QIoTTurnOffRest -> + QIoTTurnOffLightsRest + +QIoTTurnOffLightsRest -> + QIoTLightSubject/þf QIoTHvar? + | QIoTHvar? QIoTLightSubject/þf # TODO: Make the subject categorization cleaner QIoTIncreaseOrDecreaseRest -> - QIoTLightSubject/þgf QIoTHvar - | QIoTBrightnessSubject/þgf QIoTHvar + QIoTLightSubject/þf QIoTHvar? + | QIoTBrightnessSubject/þf QIoTHvar? QIoTSubject/fall -> QIoTSubjectOne/fall @@ -172,6 +187,9 @@ QIoTLightName/fall -> QIoTColorName -> {" | ".join(f"'{color}:lo'" for color in _COLORS.keys())} +QIoTSceneName -> + no + QIoTAnnadAndlag -> QIoTNewSetting/nf | QIoTSpyrjaHuldu/nf @@ -205,6 +223,7 @@ QIoTLightWord/fall -> 'ljós'/fall | 'lýsing'/fall | 'birta'/fall + | 'Birta'/fall QIoTColorWord/fall -> 'litur'/fall @@ -217,6 +236,7 @@ QIoTBrightnessWords/fall -> QIoTBrightnessWord/fall -> 'birta'/fall + | 'Birta'/fall | 'birtustig'/fall QIoTSceneWord/fall -> @@ -281,4 +301,11 @@ QIoTDarkest/fall -> 'dimmur:lo'_evb | 'dimmur:lo'_esb | 'dökkur:lo'_evb - | 'dökkur:lo'_esb \ No newline at end of file + | 'dökkur:lo'_esb + +QIoTBrightnessOrSettingWord/fall -> + QIoTBrightnessWord/fall + | QIoTSettingWord/fall + +QIoTSettingWord/fall -> + 'stilling'/fall \ No newline at end of file diff --git a/queries/iot_hue.py b/queries/iot_hue.py index d847234d..3f7792c5 100755 --- a/queries/iot_hue.py +++ b/queries/iot_hue.py @@ -29,6 +29,8 @@ # TODO: substituion klósett, baðherbergi hugmyndÆ senda lista i javascript og profa i röð # TODO: Embla stores old javascript code cached which has caused errors # TODO: Cut down javascript sent to Embla +# TODO: Two specified groups or lights. +# TODO: No specified location from typing import Dict, Mapping, Optional, cast from typing_extensions import TypedDict @@ -54,7 +56,17 @@ class DeviceData(TypedDict): _IoT_QTYPE = "IoT" -TOPIC_LEMMAS = ["ljós", "kveikja", "litur", "birta"] +TOPIC_LEMMAS = [ + "ljós", + "kveikja", + "litur", + "birta", + "hækka", + "stemmning", + "sena", + "stemming", + "stemning", +] def help_text(lemma: str) -> str: @@ -152,58 +164,59 @@ def help_text(lemma: str) -> str: | 'minnka:so'_bh QIoTMakeRest -> - QIoTSubject/þf QIoTHvar QIoTHvernigMake - | QIoTSubject/þf QIoTHvernigMake QIoTHvar - | QIoTHvar QIoTSubject/þf QIoTHvernigMake - | QIoTHvar QIoTHvernigMake QIoTSubject/þf - | QIoTHvernigMake QIoTSubject/þf QIoTHvar - | QIoTHvernigMake QIoTHvar QIoTSubject/þf + QIoTSubject/þf QIoTHvar? QIoTHvernigMake + | QIoTSubject/þf QIoTHvernigMake QIoTHvar? + | QIoTHvar? QIoTSubject/þf QIoTHvernigMake + | QIoTHvar? QIoTHvernigMake QIoTSubject/þf + | QIoTHvernigMake QIoTSubject/þf QIoTHvar? + | QIoTHvernigMake QIoTHvar? QIoTSubject/þf # TODO: Add support for "stilltu rauðan lit á ljósið í eldhúsinu" QIoTSetRest -> - QIoTSubject/þf QIoTHvar QIoTHvernigSet - | QIoTSubject/þf QIoTHvernigSet QIoTHvar - | QIoTHvar QIoTSubject/þf QIoTHvernigSet - | QIoTHvar QIoTHvernigSet QIoTSubject/þf - | QIoTHvernigSet QIoTSubject/þf QIoTHvar - | QIoTHvernigSet QIoTHvar QIoTSubject/þf + QIoTSubject/þf QIoTHvar? QIoTHvernigSet + | QIoTSubject/þf QIoTHvernigSet QIoTHvar? + | QIoTHvar? QIoTSubject/þf QIoTHvernigSet + | QIoTHvar? QIoTHvernigSet QIoTSubject/þf + | QIoTHvernigSet QIoTSubject/þf QIoTHvar? + | QIoTHvernigSet QIoTHvar? QIoTSubject/þf QIoTChangeRest -> - QIoTSubjectOne/þgf QIoTHvar QIoTHvernigChange - | QIoTSubjectOne/þgf QIoTHvernigChange QIoTHvar - | QIoTHvar QIoTSubjectOne/þgf QIoTHvernigChange - | QIoTHvar QIoTHvernigChange QIoTSubjectOne/þgf - | QIoTHvernigChange QIoTSubjectOne/þgf QIoTHvar - | QIoTHvernigChange QIoTHvar QIoTSubjectOne/þgf + QIoTSubjectOne/þgf QIoTHvar? QIoTHvernigChange + | QIoTSubjectOne/þgf QIoTHvernigChange QIoTHvar? + | QIoTHvar? QIoTSubjectOne/þgf QIoTHvernigChange + | QIoTHvar? QIoTHvernigChange QIoTSubjectOne/þgf + | QIoTHvernigChange QIoTSubjectOne/þgf QIoTHvar? + | QIoTHvernigChange QIoTHvar? QIoTSubjectOne/þgf QIoTLetRest -> - QIoTSubject/þf QIoTHvar QIoTHvernigLet - | QIoTSubject/þf QIoTHvernigLet QIoTHvar - | QIoTHvar QIoTSubject/þf QIoTHvernigLet - | QIoTHvar QIoTHvernigLet QIoTSubject/þf - | QIoTHvernigLet QIoTSubject/þf QIoTHvar - | QIoTHvernigLet QIoTHvar QIoTSubject/þf + QIoTSubject/þf QIoTHvar? QIoTHvernigLet + | QIoTSubject/þf QIoTHvernigLet QIoTHvar? + | QIoTHvar? QIoTSubject/þf QIoTHvernigLet + | QIoTHvar? QIoTHvernigLet QIoTSubject/þf + | QIoTHvernigLet QIoTSubject/þf QIoTHvar? + | QIoTHvernigLet QIoTHvar? QIoTSubject/þf QIoTTurnOnRest -> - QIoTTurnOnRest - | QIoTAHverju QIoTHvar - | QIoTHvar QIoTAHverju + QIoTTurnOnLightsRest + | QIoTAHverju QIoTHvar? + | QIoTHvar? QIoTAHverju QIoTTurnOnLightsRest -> - QIoTLightSubject/þf QIoTHvar - | QIoTHvar QIoTLightSubject/þf + QIoTLightSubject/þf QIoTHvar? + | QIoTHvar? QIoTLightSubject/þf +# Would be good to add "slökktu á rauða litnum" functionality QIoTTurnOffRest -> QIoTTurnOffLightsRest -QIoTTurnOnLightsRest -> - QIoTLightSubject/þf QIoTHvar - | QIoTHvar QIoTLightSubject/þf +QIoTTurnOffLightsRest -> + QIoTLightSubject/þf QIoTHvar? + | QIoTHvar? QIoTLightSubject/þf # TODO: Make the subject categorization cleaner QIoTIncreaseOrDecreaseRest -> - QIoTLightSubject/þgf QIoTHvar - | QIoTBrightnessSubject/þgf QIoTHvar + QIoTLightSubject/þf QIoTHvar? + | QIoTBrightnessSubject/þf QIoTHvar? QIoTSubject/fall -> QIoTSubjectOne/fall @@ -286,6 +299,9 @@ def help_text(lemma: str) -> str: QIoTColorName -> {" | ".join(f"'{color}:lo'" for color in _COLORS.keys())} +QIoTSceneName -> + no + QIoTAnnadAndlag -> QIoTNewSetting/nf | QIoTSpyrjaHuldu/nf @@ -319,6 +335,7 @@ def help_text(lemma: str) -> str: 'ljós'/fall | 'lýsing'/fall | 'birta'/fall + | 'Birta'/fall QIoTColorWord/fall -> 'litur'/fall @@ -331,6 +348,7 @@ def help_text(lemma: str) -> str: QIoTBrightnessWord/fall -> 'birta'/fall + | 'Birta'/fall | 'birtustig'/fall QIoTSceneWord/fall -> @@ -397,31 +415,33 @@ def help_text(lemma: str) -> str: | 'dökkur:lo'_evb | 'dökkur:lo'_esb +QIoTBrightnessOrSettingWord/fall -> + QIoTBrightnessWord/fall + | QIoTSettingWord/fall + +QIoTSettingWord/fall -> + 'stilling'/fall + """ + def QIoTColorWord(node: Node, params: QueryStateDict, result: Result) -> None: - if "keyword" in result: - pass + result.changing_color = True - result.keyword = "color" def QIoTSceneWord(node: Node, params: QueryStateDict, result: Result) -> None: - if "keyword" in result: - pass + result.changing_scene = True - result.keyword = "scene" def QIoTBrightnessWord(node: Node, params: QueryStateDict, result: Result) -> None: - if "keyword" in result: - pass + result.changing_brightness = True - result.keyword = "brightness" def QIoTQuery(node: Node, params: QueryStateDict, result: Result) -> None: result.qtype = _IoT_QTYPE -def QIoTTurnOn(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTTurnOnLightsRest(node: Node, params: QueryStateDict, result: Result) -> None: result.action = "turn_on" if "hue_obj" not in result: result["hue_obj"] = {"on": True} @@ -429,7 +449,7 @@ def QIoTTurnOn(node: Node, params: QueryStateDict, result: Result) -> None: result["hue_obj"]["on"] = True -def QIoTTurnOff(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTTurnOffLightsRest(node: Node, params: QueryStateDict, result: Result) -> None: result.action = "turn_off" if "hue_obj" not in result: result["hue_obj"] = {"on": False} @@ -437,7 +457,7 @@ def QIoTTurnOff(node: Node, params: QueryStateDict, result: Result) -> None: result["hue_obj"]["on"] = False -def QIoTSetColor(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTNewColor(node: Node, params: QueryStateDict, result: Result) -> None: result.action = "set_color" print(result.color_name) color_hue = _COLORS.get(result.color_name, None) @@ -450,7 +470,9 @@ def QIoTSetColor(node: Node, params: QueryStateDict, result: Result) -> None: result["hue_obj"]["on"] = True -def QIoTIncreaseBrightness(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTMoreBrighterOrHigher( + node: Node, params: QueryStateDict, result: Result +) -> None: result.action = "increase_brightness" if "hue_obj" not in result: result["hue_obj"] = {"on": True, "bri_inc": 64} @@ -459,7 +481,7 @@ def QIoTIncreaseBrightness(node: Node, params: QueryStateDict, result: Result) - result["hue_obj"]["on"] = True -def QIoTDecreaseBrightness(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTLessDarkerOrLower(node: Node, params: QueryStateDict, result: Result) -> None: result.action = "decrease_brightness" if "hue_obj" not in result: result["hue_obj"] = {"bri_inc": -64} @@ -467,7 +489,24 @@ def QIoTDecreaseBrightness(node: Node, params: QueryStateDict, result: Result) - result["hue_obj"]["bri_inc"] = -64 -def QIoTMaxBrightness(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTIncreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: + result.action = "increase_brightness" + if "hue_obj" not in result: + result["hue_obj"] = {"on": True, "bri_inc": 64} + else: + result["hue_obj"]["bri_inc"] = 64 + result["hue_obj"]["on"] = True + + +def QIoTDecreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: + result.action = "decrease_brightness" + if "hue_obj" not in result: + result["hue_obj"] = {"bri_inc": -64} + else: + result["hue_obj"]["bri_inc"] = -64 + + +def QIoTBrightest(node: Node, params: QueryStateDict, result: Result) -> None: result.action = "decrease_brightness" if "hue_obj" not in result: result["hue_obj"] = {"bri": 255} @@ -475,7 +514,7 @@ def QIoTMaxBrightness(node: Node, params: QueryStateDict, result: Result) -> Non result["hue_obj"]["bri"] = 255 -def QIoTMinBrightness(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTDarkest(node: Node, params: QueryStateDict, result: Result) -> None: result.action = "decrease_brightness" if "hue_obj" not in result: result["hue_obj"] = {"bri": 0} @@ -483,7 +522,7 @@ def QIoTMinBrightness(node: Node, params: QueryStateDict, result: Result) -> Non result["hue_obj"]["bri"] = 0 -def QIoTSetScene(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTNewScene(node: Node, params: QueryStateDict, result: Result) -> None: result.action = "set_scene" scene_name = result.get("scene_name", None) print(scene_name) @@ -517,8 +556,6 @@ def QIoTConnectLights(node: Node, params: QueryStateDict, result: Result) -> Non result.qtype = "connect_lights" result.action = "connect_lights" -def QIoTColorWord(node: Node, params: QueryStateDict, result: Result) -> None: - result.keyword # Convert color name into hue # Taken from home.py @@ -536,10 +573,18 @@ def QIoTColorWord(node: Node, params: QueryStateDict, result: Result) -> None: def sentence(state: QueryStateDict, result: Result) -> None: """Called when sentence processing is complete""" q: Query = state["query"] - if "qtype" not in result: + changing_color = result.get("changing_color", False) + changing_scene = result.get("changing_scene", False) + changing_brightness = result.get("changing_brightness", False) + print("error?", sum((changing_color, changing_scene, changing_brightness)) > 1) + if ( + sum((changing_color, changing_scene, changing_brightness)) > 1 + or "qtype" not in result + ): q.set_error("E_QUERY_NOT_UNDERSTOOD") return + q.set_qtype(result.qtype) if result.qtype == "connect_lights": host = str(flask.request.host) print("host: ", host) @@ -576,7 +621,7 @@ def sentence(state: QueryStateDict, result: Result) -> None: username = hue_credentials.get("username") if not device_data or not hue_credentials: - answer = "Philips Hue hefur ekki verið tengt" + answer = "ég var að kveikja ljósin! " q.set_answer(*gen_answer(answer)) return @@ -585,7 +630,6 @@ def sentence(state: QueryStateDict, result: Result) -> None: print("username: ", username) print("selected light :", selected_light) print("hue credentials :", hue_credentials) - q.set_qtype(result.qtype) try: # kalla í javascripts stuff diff --git a/tests/test_queries.py b/tests/test_queries.py index 6f9b35d5..dfe35aaf 100755 --- a/tests/test_queries.py +++ b/tests/test_queries.py @@ -648,9 +648,6 @@ def test_iot(client: FlaskClient) -> None: json = qmcall(client, {"q": "stilltu lit ljóssins í eldhúsinu á grænan"}, "IoT") assert "ég var að kveikja ljósin! " in json["answer"] - json = qmcall(client, {"q": "gerðu grænt í eldhúsinu"}, "IoT") - assert "ég var að kveikja ljósin! " in json["answer"] - json = qmcall(client, {"q": "kveiktu á ljósunum í eldhúsinu"}, "IoT") assert "ég var að kveikja ljósin! " in json["answer"] @@ -669,14 +666,14 @@ def test_iot(client: FlaskClient) -> None: json = qmcall(client, {"q": "slökktu ljósið inni í eldhúsi"}, "IoT") assert "ég var að kveikja ljósin! " in json["answer"] - json = qmcall(client, {"q": "gerðu birtuna inni í eldhúsi meira bjarta"}, "IoT") - assert "ég var að kveikja ljósin! " in json["answer"] + # json = qmcall(client, {"q": "gerðu meiri birtu inni í eldhúsi"}, "IoT") + # assert "ég var að kveikja ljósin! " in json["answer"] - json = qmcall(client, {"q": "gerðu ljósið inni í eldhúsi minna bjart"}, "IoT") - assert "ég var að kveikja ljósin! " in json["answer"] + # json = qmcall(client, {"q": "gerðu ljósið inni í eldhúsi minna bjart"}, "IoT") + # assert "ég var að kveikja ljósin! " in json["answer"] - json = qmcall(client, {"q": "gerðu meiri birtu inni í eldhúsi"}, "IoT") - assert "ég var að kveikja ljósin! " in json["answer"] + # json = qmcall(client, {"q": "gerðu grænt í eldhúsinu"}, "IoT") + # assert "ég var að kveikja ljósin! " in json["answer"] def test_ja(client: FlaskClient) -> None: From da9aed5ccdc77b1523605832512ee076dc2fc581 Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Thu, 16 Jun 2022 13:56:36 +0000 Subject: [PATCH 035/371] hello --- queries/grammars/iot_hue.grammar | 2 +- queries/iot_hue.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/queries/grammars/iot_hue.grammar b/queries/grammars/iot_hue.grammar index 8b1c4457..8e03d66d 100644 --- a/queries/grammars/iot_hue.grammar +++ b/queries/grammars/iot_hue.grammar @@ -273,7 +273,7 @@ QIoTNewBrightness/fall -> QIoTNewScene/fall -> QIoTSceneWord/fall QIoTSceneName - | QIoTSceneName QIoTSceneWord/fall + | QIoTSceneName QIoTSceneWord/fall? QIoTMoreBrighterOrHigher/fall -> 'mikill:lo'_mst/fall diff --git a/queries/iot_hue.py b/queries/iot_hue.py index 3f7792c5..65772305 100755 --- a/queries/iot_hue.py +++ b/queries/iot_hue.py @@ -31,6 +31,7 @@ # TODO: Cut down javascript sent to Embla # TODO: Two specified groups or lights. # TODO: No specified location +# TODO: Fix scene issues from typing import Dict, Mapping, Optional, cast from typing_extensions import TypedDict @@ -385,7 +386,7 @@ def help_text(lemma: str) -> str: QIoTNewScene/fall -> QIoTSceneWord/fall QIoTSceneName - | QIoTSceneName QIoTSceneWord/fall + | QIoTSceneName QIoTSceneWord/fall? QIoTMoreBrighterOrHigher/fall -> 'mikill:lo'_mst/fall @@ -542,6 +543,7 @@ def QIoTColorName(node: Node, params: QueryStateDict, result: Result) -> None: def QIoTSceneName(node: Node, params: QueryStateDict, result: Result) -> None: result["scene_name"] = result._indefinite + print(result.get("scene_name", None)) def QIoTGroupName(node: Node, params: QueryStateDict, result: Result) -> None: From 48f273b321af49a5cab170b3f63c50fe7e04aae2 Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Thu, 16 Jun 2022 14:51:11 +0000 Subject: [PATCH 036/371] minor grammar fixes --- queries/grammars/iot_hue.grammar | 5 +++-- queries/iot_hue.py | 7 ++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/queries/grammars/iot_hue.grammar b/queries/grammars/iot_hue.grammar index 8e03d66d..2c7c4e3d 100644 --- a/queries/grammars/iot_hue.grammar +++ b/queries/grammars/iot_hue.grammar @@ -91,7 +91,7 @@ QIoTTurnOnRest -> QIoTTurnOnLightsRest -> QIoTLightSubject/þf QIoTHvar? - | QIoTHvar? QIoTLightSubject/þf + | QIoTHvar QIoTLightSubject/þf? # Would be good to add "slökktu á rauða litnum" functionality QIoTTurnOffRest -> @@ -99,7 +99,7 @@ QIoTTurnOffRest -> QIoTTurnOffLightsRest -> QIoTLightSubject/þf QIoTHvar? - | QIoTHvar? QIoTLightSubject/þf + | QIoTHvar QIoTLightSubject/þf? # TODO: Make the subject categorization cleaner QIoTIncreaseOrDecreaseRest -> @@ -189,6 +189,7 @@ QIoTColorName -> QIoTSceneName -> no + | lo QIoTAnnadAndlag -> QIoTNewSetting/nf diff --git a/queries/iot_hue.py b/queries/iot_hue.py index 65772305..24fecaca 100755 --- a/queries/iot_hue.py +++ b/queries/iot_hue.py @@ -204,7 +204,7 @@ def help_text(lemma: str) -> str: QIoTTurnOnLightsRest -> QIoTLightSubject/þf QIoTHvar? - | QIoTHvar? QIoTLightSubject/þf + | QIoTHvar QIoTLightSubject/þf? # Would be good to add "slökktu á rauða litnum" functionality QIoTTurnOffRest -> @@ -212,7 +212,7 @@ def help_text(lemma: str) -> str: QIoTTurnOffLightsRest -> QIoTLightSubject/þf QIoTHvar? - | QIoTHvar? QIoTLightSubject/þf + | QIoTHvar QIoTLightSubject/þf? # TODO: Make the subject categorization cleaner QIoTIncreaseOrDecreaseRest -> @@ -302,6 +302,7 @@ def help_text(lemma: str) -> str: QIoTSceneName -> no + | lo QIoTAnnadAndlag -> QIoTNewSetting/nf @@ -623,7 +624,7 @@ def sentence(state: QueryStateDict, result: Result) -> None: username = hue_credentials.get("username") if not device_data or not hue_credentials: - answer = "ég var að kveikja ljósin! " + answer = "Það vantar að tengja Philips Hub-inn." q.set_answer(*gen_answer(answer)) return From 245c820cd93f63030be2d4b52296d2fa5d2ffd94 Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Thu, 16 Jun 2022 15:34:34 +0000 Subject: [PATCH 037/371] =?UTF-8?q?added=20"=C3=BEannig=20a=C3=B0=20=C3=BE?= =?UTF-8?q?a=C3=B0=20s=C3=A9=20X"=20functionality?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- queries/grammars/iot_hue.grammar | 11 +++++++++++ queries/iot_hue.py | 21 ++++++++++++++++----- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/queries/grammars/iot_hue.grammar b/queries/grammars/iot_hue.grammar index 2c7c4e3d..02b6fd2a 100644 --- a/queries/grammars/iot_hue.grammar +++ b/queries/grammars/iot_hue.grammar @@ -126,17 +126,23 @@ QIoTHvar -> QIoTHvernigMake -> QIoTAnnadAndlag # gerðu litinn rauðan í eldhúsinu EÐA gerðu birtuna meiri í eldhúsinu | QIoTAdHverju # gerðu litinn að rauðum í eldhúsinu + | QIoTThannigAd QIoTHvernigSet -> QIoTAHvad + | QIoTThannigAd QIoTHvernigChange -> QIoTIHvad + | QIoTThannigAd QIoTHvernigLet -> QIoTBecome QIoTSomethingOrSomehow | QIoTBe QIoTSomehow +QIoTThannigAd -> + "þannig" "að"? pfn_nf QIoTBeOrBecomeSubjunctive QIoTAnnadAndlag + # I think these verbs only appear in these forms. # In which case these terminals should be deleted and a direct reference should be made in the relevant non-terminals. QIoTBe -> @@ -145,6 +151,10 @@ QIoTBe -> QIoTBecome -> "verða" +QIoTBeOrBecomeSubjunctive -> + "verði" + | "sé" + QIoTLightSubject/fall -> QIoTLight/fall @@ -214,6 +224,7 @@ QIoTSomethingOrSomehow -> QIoTSomehow -> QIoTAnnadAndlag + | QIoTThannigAd QIoTLight/fall -> QIoTLightName/fall diff --git a/queries/iot_hue.py b/queries/iot_hue.py index 24fecaca..0918f82f 100755 --- a/queries/iot_hue.py +++ b/queries/iot_hue.py @@ -76,11 +76,11 @@ def help_text(lemma: str) -> str: return "Ég skil þig ef þú segir til dæmis: {0}.".format( random.choice( ( - "Kveiktu á ljósunum inni í eldhúsi.", - "Slökktu á leslampanum.", - "Breyttu lit lýsingarinnar í stofunni í bláan.", - "Gerðu ljósið í borðstofunni bjartara.", - "Stilltu á bjartasta niðri í kjallara.", + "Kveiktu á ljósunum inni í eldhúsi", + "Slökktu á leslampanum", + "Breyttu lit lýsingarinnar í stofunni í bláan", + "Gerðu ljósið í borðstofunni bjartara", + "Stilltu á bjartasta niðri í kjallara", ) ) ) @@ -239,17 +239,23 @@ def help_text(lemma: str) -> str: QIoTHvernigMake -> QIoTAnnadAndlag # gerðu litinn rauðan í eldhúsinu EÐA gerðu birtuna meiri í eldhúsinu | QIoTAdHverju # gerðu litinn að rauðum í eldhúsinu + | QIoTThannigAd QIoTHvernigSet -> QIoTAHvad + | QIoTThannigAd QIoTHvernigChange -> QIoTIHvad + | QIoTThannigAd QIoTHvernigLet -> QIoTBecome QIoTSomethingOrSomehow | QIoTBe QIoTSomehow +QIoTThannigAd -> + "þannig" "að"? pfn_nf QIoTBeOrBecomeSubjunctive QIoTAnnadAndlag + # I think these verbs only appear in these forms. # In which case these terminals should be deleted and a direct reference should be made in the relevant non-terminals. QIoTBe -> @@ -258,6 +264,10 @@ def help_text(lemma: str) -> str: QIoTBecome -> "verða" +QIoTBeOrBecomeSubjunctive -> + "verði" + | "sé" + QIoTLightSubject/fall -> QIoTLight/fall @@ -327,6 +337,7 @@ def help_text(lemma: str) -> str: QIoTSomehow -> QIoTAnnadAndlag + | QIoTThannigAd QIoTLight/fall -> QIoTLightName/fall From 89bbb9af0e34cbdcffff8ccf707a63c415cbeeeb Mon Sep 17 00:00:00 2001 From: Logi E Date: Tue, 21 Jun 2022 10:18:32 +0000 Subject: [PATCH 038/371] added fruit_seller folder --- queries/fruit_seller/fruitseller.py | 190 ++++++++++++++++++++++ queries/fruit_seller/fruitseller.yaml | 23 +++ queries/fruit_seller/fruitstate.py | 222 ++++++++++++++++++++++++++ queries/fruit_seller/resource.py | 37 +++++ 4 files changed, 472 insertions(+) create mode 100644 queries/fruit_seller/fruitseller.py create mode 100644 queries/fruit_seller/fruitseller.yaml create mode 100644 queries/fruit_seller/fruitstate.py create mode 100644 queries/fruit_seller/resource.py diff --git a/queries/fruit_seller/fruitseller.py b/queries/fruit_seller/fruitseller.py new file mode 100644 index 00000000..fc5f3f7b --- /dev/null +++ b/queries/fruit_seller/fruitseller.py @@ -0,0 +1,190 @@ +import logging +from query import Query, QueryStateDict +from tree import Result, Node +from queries import gen_answer, parse_num +from fruitstate import FruitStateManager +import pickle + +# Indicate that this module wants to handle parse trees for queries, +# as opposed to simple literal text strings +HANDLE_TREE = True + +# The grammar nonterminals this module wants to handle +QUERY_NONTERMINALS = {"QFruitStartQuery", "QFruitQuery"} + +# The context-free grammar for the queries recognized by this plug-in module +GRAMMAR = """ + +Query → QFruitStartQuery | QFruitQuery + +QFruitStartQuery → + "ég" "vill" "kaupa" "ávexti" + | "ég" "vil" "kaupa" "ávexti" + | "mig" "langar" "að" "kaupa" "ávexti" "hjá"? "þér"? + | "mig" "langar" "að" "panta" "ávexti" "hjá"? "þér"? + | "get" "ég" "keypt" "ávexti" "hjá" "þér" '?'? + +QFruitQuery → + QAddFruitQuery + | QRemoveFruitQuery + | QChangeFruitQuery + | QFruitOptionsQuery + | QYes + | QNo + | QCancelOrder + +QAddFruitQuery → + "já"? "má" "ég" "fá" QFruitList + | "já"? "get" "ég" "fengið" QFruitList + | "já"? "gæti" "ég" "fengið" QFruitList + | "já"? "ég" "vil" "fá" QFruitList + | "já"? "ég" "vill" "fá" QFruitList + | "já"? "ég" "vil" "panta" QFruitList + | "já"? "ég" "vill" "panta" QFruitList + | "já"? "ég" "vil" "kaupa" QFruitList + | "já"? "ég" "vill" "kaupa" QFruitList + | "já"? "mig" "langar" "að" "fá" QFruitList + | "já"? "mig" "langar" "að" "kaupa" QFruitList + | "já"? "mig" "langar" "að" "panta" QFruitList + +QRemoveFruitQuery → + "taktu" "út" QFruitList + | "slepptu" QFruitList + | "ég" "vil" "sleppa" QFruitList + | "ég" "vill" "sleppa" QFruitList + | "ég" "hætti" "við" QFruitList + | "ég" "vil" "ekki" QFruitList + | "ég" "vill" "ekki" QFruitList + +QChangeFruitQuery → + QChangeStart QFruitList QChangeConnector QFruitList + +QChangeStart → + "breyttu" + | "ég" "vil" "frekar" + | "ég" "vill" "frekar" + | "ég" "vil" "skipta" "út" + | "ég" "vill" "skipta" "út" + | "ég" "vil" "breyta" + | "ég" "vill" "breyta" + +QChangeConnector → + "en" | "í" "staðinn" "fyrir" + +QFruitOptionsQuery → + "hvað" "er" "í" "boði" '?'? + | "hverjir" "eru" "valmöguleikarnir" '?'? + | "hvaða" "valmöguleikar" "eru" "í" "boði" '?'? + | "hvaða" "valmöguleikar" "eru" "til" '?'? + | "hvaða" "ávexti" "ertu" "með" '?'? + | "hvaða" "ávextir" "eru" "í" "boði" '?'? + +QFruitList → QNumOfFruit QNumOfFruit* + +QNumOfFruit → QNum? QFruit "og"? + +QNum → 'einn' | 'tveir' | 'þrír' | 'fjórir' | "fimm" | "sex" | "sjö" + | "átta" | "níu" | "tíu" | "ellefu" | "tólf" | "þrettán" | "fjórtán" + | "fimmtán" | "sextán" | "sautján" | "átján" | "nítján" + +QFruit → 'banani' | 'epli' | 'pera' | 'appelsína' + +QYes → "já" | "já" "takk" | "já" "þakka" "þér" "fyrir" | "já" "takk" "kærlega" "fyrir"? + +QNo → "nei" | "nei" "takk" + +QCancelOrder → "ég" "hætti" "við" + | "ég" "vil" "hætta" "við" "pöntunina" + | "ég" "vill" "hætta" "við" "pöntunina" + +""" + +# fruitStateManager = FruitStateManager() + + +def QFruitStartQuery(node: Node, params: QueryStateDict, result: Result): + params[0]["_state"]["query"].set_client_data("conversation", "fruit_seller") + result.qtype = "QFruitStartQuery" + + +def QAddFruitQuery(node: Node, params: QueryStateDict, result: Result): + result.qtype = "QAddFruitQuery" + + +def QRemoveFruitQuery(node: Node, params: QueryStateDict, result: Result): + result.qtype = "QRemoveFruitQuery" + + +def QCancelOrder(node: Node, params: QueryStateDict, result: Result): + result.qtype = "QCancelOrder" + + +def QFruitOptionsQuery(node: Node, params: QueryStateDict, result: Result): + result.qtype = "QFruitOptionsQuery" + + +def QYes(node: Node, params: QueryStateDict, result: Result): + result.qtype = "QYes" + + +def QNo(node: Node, params: QueryStateDict, result: Result): + result.qtype = "QNo" + + +def QNumOfFruit(node: Node, params: QueryStateDict, result: Result): + if "queryfruits" not in result: + result.queryfruits = {} + if "fruitnumber" not in result: + result.queryfruits[result.fruit] = 1 + else: + result.queryfruits[result.fruit] = result.fruitnumber + + +def QNum(node: Node, params: QueryStateDict, result: Result): + fruitnumber = int(parse_num(node, result._nominative)) + if fruitnumber is not None: + result.fruitnumber = fruitnumber + else: + result.fruitnumber = 1 + + +def QFruit(node: Node, params: QueryStateDict, result: Result): + fruit = result._root + if fruit is not None: + result.fruit = fruit + + +def updateClientData(query, fruitStateManager): + d = pickle.dumps(fruitStateManager) + query.set_client_data("conversation_data", d) + + +def sentence(state: QueryStateDict, result: Result) -> None: + """Called when sentence processing is complete""" + q: Query = state["query"] + # checka hvort user se i samtali med q.client_data + if q.client_data("conversation") != "fruit_seller" or "qtype" not in result: + q.set_error("E_QUERY_NOT_UNDERSTOOD") + return + + # Successfully matched a query type + try: + if result.qtype == "QFruitStartQuery": + fruitStateManager = FruitStateManager() + fruitStateManager.startFruitOrder() + else: + fruitStateManager = pickle.loads(q.client_data("conversation_data")) + fruitStateManager.stateMethod(result.qtype, result) + updateClientData(q, fruitStateManager) + ans = fruitStateManager.ans + if result.qtype == "OrderComplete" or result.qtype == "CancelOrder": + q.set_client_data("conversation", None) + q.set_client_data("conversation_data", None) + + q.set_answer(*gen_answer(ans)) + return + except Exception as e: + logging.warning( + "Exception {0} while processing date query '{1}'".format(e, q.query) + ) + q.set_error("E_EXCEPTION: {0}".format(e)) diff --git a/queries/fruit_seller/fruitseller.yaml b/queries/fruit_seller/fruitseller.yaml new file mode 100644 index 00000000..d5ff5879 --- /dev/null +++ b/queries/fruit_seller/fruitseller.yaml @@ -0,0 +1,23 @@ +# Example YAML file for fruitseller dialogue +variables: + - &Success 1 + - &Cancel -1 + +states: + - Start: + Dialogue Entry: true + NextState: + OnYes: TakeOrder + - TakeOrder: + Prompt: "Hvernig ávexti má bjóða þér?" + RepeatPrompt: "Pöntunin þín samanstendur af {curr_order}. Verður það eitthvað fleira?" + NextState: + OnYes: TakeOrder + OnNo: "OrderReceived" + Cancel: *Cancel + - ConfirmOrder: + Prompt: "Pöntunin þín er {curr_order}. Viltu staðfesta pöntunina?" + NextState: + OnYes: *Success + OnNo: "TakeOrder" + Cancel: *Cancel diff --git a/queries/fruit_seller/fruitstate.py b/queries/fruit_seller/fruitstate.py new file mode 100644 index 00000000..21902d86 --- /dev/null +++ b/queries/fruit_seller/fruitstate.py @@ -0,0 +1,222 @@ +from tree import Result +from .resource import Resource +from reynir import NounPhrase +from typing import Optional, List + + +class FruitStateManager: + def __init__(self): + self.resources: List[Resource] = [] + self.fruitState = None + self.ans: Optional[str] = None + + def startFruitOrder(self): + # Order here is the priority of each resource + self.resources.append(FruitState(required=True)) + self.resources.append(OrderReceivedState(required=True)) + self.updateState("FruitStart") + + def generateAnswer(self, type): + if type == "FruitStart": + self.ans = "Hvaða ávexti má bjóða þér?" + elif type == "ListFruit": + if len(self.fruitState.data) != 0: + self.ans = "Komið! Pöntunin samanstendur af " + for fruitname in self.fruitState.data.keys(): + fruitNumber = self.fruitState.data[fruitname] + if fruitname == "banani": + self.ans += ( + "banana " + if (fruitNumber == 1) + else f"{fruitNumber} bönunum " + ) + elif fruitname == "appelsína": + self.ans += ( + "appelsínu " + if (fruitNumber == 1) + else f"{fruitNumber} appelsínum " + ) + elif fruitname == "pera": + self.ans += ( + "peru " if (fruitNumber == 1) else f"{fruitNumber} perum " + ) + elif fruitname == "epli": + self.ans += ( + "epli " if (fruitNumber == 1) else f"{fruitNumber} eplum " + ) + else: + self.ans += f"{'' if (fruitNumber == 1) else f'{fruitNumber} '}{NounPhrase(fruitname).dative} " + self.ans = self.ans.rstrip() + ". Var það eitthvað fleira?" + else: + self.ans = "Komið! Karfan er núna tóm. Hvaða ávexti má bjóða þér?" + elif type == "FruitOrderNotFinished": + self.ans = "Hverju viltu að bæta við pöntunina?" + elif type == "FruitsFulfilled": + self.ans = "Frábært! Á ég að staðfesta pöntunina?" + elif type == "FruitMethodNotFound": + self.ans = "Ég get ekki tekið við þessari beiðni strax." + elif type == "OrderComplete": + self.ans = "Frábært, pöntunin er staðfest!" + elif type == "OrderWrong": + self.ans = "Leitt að heyra, viltu hætta við pöntunina eða breyta henni?" + elif type == "CancelOrder": + self.ans = "Móttekið. Hætti við pöntunina." + elif type == "FruitOptions": + self.ans = "Hægt er að panta appelsínur, banana, epli og perur." + elif type == "FruitRemoved": + self.ans = "Karfan hefur verið uppfærð. Var það eitthvað fleira?" + elif type == "NoFruitMatched": + self.ans = "Enginn ávöxtur í körfunni passaði við beiðnina á undan." + elif type == "NoFruitToRemove": + self.ans = "Engir ávextir eru í körfunni til að fjarlægja." + + def updateState(self, type): + for resource in self.resources: + if resource.required and not resource.fulfilled: + if resource.data is None: + if self.fruitState is not resource: + self.fruitState = resource + resource.state = resource.DataState( + resource.data, resource.partiallyFulfilled, resource.fulfilled + ) + break + elif not resource.partiallyFulfilled: + resource.state = resource.PartiallyFulfilledState( + resource.data, resource.partiallyFulfilled, resource.fulfilled + ) + break + elif resource.partiallyFulfilled: + resource.state = resource.FulfilledState( + resource.data, resource.partiallyFulfilled, resource.fulfilled + ) + break + self.generateAnswer(type) + print("Current state: ", self.fruitState.state) + + def stateMethod(self, methodName, result): + try: + method = getattr(self.fruitState.state, methodName) + if method is not None: + ( + self.fruitState.data, + self.fruitState.partiallyFulfilled, + self.fruitState.fulfilled, + ) = method(result) + self.updateState(result.qtype) + except Exception as e: + print("Error: ", e) + result.qtype = "FruitMethodNotFound" + + +class FruitState(Resource): + def __init__(self, required=True): + super().__init__(required) + + class DataState: + def __init__(self, data, partiallyFulfilled, fulfilled): + self.data = data + self.partiallyFulfilled = partiallyFulfilled + self.fulfilled = fulfilled + + # Add fruits to array and switch to OrderReceived state + def QAddFruitQuery(self, result: Result): + if self.data is None: + self.data = result.queryfruits + else: + for fruitname in result.queryfruits.keys(): + self.data[fruitname] = result.queryfruits[fruitname] + result.fruits = self.data + result.qtype = "ListFruit" + return (self.data, self.partiallyFulfilled, self.fulfilled) + + # Remove fruits from array + def QRemoveFruitQuery(self, result: Result): + result.qtype = "" + if self.data is None: + result.qtype = "NoFruitToRemove" + else: + for fruitname in result.queryfruits.keys(): + removedValue = self.data.pop(fruitname, "NoFruitMatched") + if removedValue == "NoFruitMatched": + result.qtype = "NoFruitMatched" + if result.qtype != "NoFruitMatched": + result.qtype = "ListFruit" + result.fruits = self.data + return (self.data, self.partiallyFulfilled, self.fulfilled) + + # Change the fruits array + def QChangeFruitQuery(self, result: Result): + pass + + # Inform what fruits are available + def QFruitOptionsQuery(self, result: Result): + result.qtype = "FruitOptions" + return (self.data, self.partiallyFulfilled, self.fulfilled) + + # User wants to stop conversation + def QCancelOrder(self, result: Result): + result.qtype = "CancelOrder" + return (self.data, self.partiallyFulfilled, self.fulfilled) + + class PartiallyFulfilledState(DataState): + def __init__(self, data, partiallyFulfilled, fulfilled): + super().__init__(data, partiallyFulfilled, fulfilled) + + # User is happy with the order, switch to confirm state + def QNo(self, result: Result): + result.qtype = "FruitsFulfilled" + self.partiallyFulfilled = True + return (self.data, self.partiallyFulfilled, self.fulfilled) + + # User wants to add more to the order, ask what + def QYes(self, result: Result): + result.qtype = "FruitOrderNotFinished" + return (self.data, self.partiallyFulfilled, self.fulfilled) + + class FulfilledState(DataState): + def __init__(self, data, partiallyFulfilled, fulfilled): + super().__init__(data, partiallyFulfilled, fulfilled) + + # The order is correct, say the order is confirmed + def QYes(self, result: Result): + result.qtype = "OrderComplete" + self.fulfilled = True + return (self.data, self.partiallyFulfilled, self.fulfilled) + + # Order was wrong, ask the user to start again + def QNo(self, result: Result): + result.qtype = "OrderWrong" + self.partiallyFulfilled = False + return (self.data, self.partiallyFulfilled, self.fulfilled) + + +class OrderReceivedState(FruitState): + def __init__(self, required=True): + super().__init__(required) + + # User is happy with the order, switch to confirm state + def QNo(self, result: Result): + result.qtype = "FruitsFulfilled" + self.fulfilled = True + return ConfirmOrderState(self.fruits, self.date) + + # User wants to add more to the order, ask what + def QYes(self, result: Result): + result.qtype = "FruitOrderNotFinished" + return FruitState(self.fruits, self.date) + + +class ConfirmOrderState(FruitState): + def __init__(self, fruits, date): + self.fruits = fruits + self.date = date + + # The order is correct, say the order is confirmed + def QYes(self, result: Result): + result.qtype = "OrderComplete" + + # Order was wrong, ask the user to start again + def QNo(self, result: Result): + result.qtype = "OrderWrong" + self.fruits.fulfilled = False + return FruitState(self.fruits, self.date) diff --git a/queries/fruit_seller/resource.py b/queries/fruit_seller/resource.py new file mode 100644 index 00000000..7691b2d8 --- /dev/null +++ b/queries/fruit_seller/resource.py @@ -0,0 +1,37 @@ +from typing import Any + + +class Resource: + def __init__(self, required: bool = False): + self.required = required + self.data: Any = None + self.partiallyFulfilled: bool = False + self.fulfilled: bool = False + self.state = None + + def isRequired(self): + return self.required + + def getData(self): + return self.data + + def isFulfilled(self): + return self.fulfilled + + def setData(self, data: Any): + self.data = data + + def setFulfilled(self, fulfilled: bool): + self.fulfilled = fulfilled + + +""" Three classes implemented for each resource + class DataState(): + pass + + class PartiallyFulfilledState(): + pass + + class FulfillState(DataState): + pass +""" From f7f1b3ca964b417e12236fdbd21feefac3c0e766 Mon Sep 17 00:00:00 2001 From: Logi E Date: Tue, 21 Jun 2022 11:05:09 +0000 Subject: [PATCH 039/371] allow saving client data, fixed imports and such --- queries/fruit_seller/__init__.py | 0 queries/fruit_seller/fruitseller.yaml | 19 ++++++++ queries/fruit_seller/fruitstate.py | 2 +- queries/{fruit_seller => }/fruitseller.py | 54 ++++++++++++++++------- 4 files changed, 58 insertions(+), 17 deletions(-) create mode 100644 queries/fruit_seller/__init__.py rename queries/{fruit_seller => }/fruitseller.py (78%) diff --git a/queries/fruit_seller/__init__.py b/queries/fruit_seller/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/queries/fruit_seller/fruitseller.yaml b/queries/fruit_seller/fruitseller.yaml index d5ff5879..beca089e 100644 --- a/queries/fruit_seller/fruitseller.yaml +++ b/queries/fruit_seller/fruitseller.yaml @@ -3,6 +3,25 @@ variables: - &Success 1 - &Cancel -1 +resources: + - Fruits: + type: "list" + required: true + verification_function: "check_fruits" + prompt: "Hvaða ávexti má bjóða þér?" + - Date: + type: "date" + required: true + verification_function: "check_date" + prompt: "Hvenær viltu fá ávextina?" + + + + + + + + states: - Start: Dialogue Entry: true diff --git a/queries/fruit_seller/fruitstate.py b/queries/fruit_seller/fruitstate.py index 21902d86..b36a104a 100644 --- a/queries/fruit_seller/fruitstate.py +++ b/queries/fruit_seller/fruitstate.py @@ -1,5 +1,5 @@ from tree import Result -from .resource import Resource +from queries.fruit_seller.resource import Resource from reynir import NounPhrase from typing import Optional, List diff --git a/queries/fruit_seller/fruitseller.py b/queries/fruitseller.py similarity index 78% rename from queries/fruit_seller/fruitseller.py rename to queries/fruitseller.py index fc5f3f7b..52e5ee60 100644 --- a/queries/fruit_seller/fruitseller.py +++ b/queries/fruitseller.py @@ -1,25 +1,33 @@ +from typing import cast + import logging +import pickle +import base64 + from query import Query, QueryStateDict from tree import Result, Node from queries import gen_answer, parse_num -from fruitstate import FruitStateManager -import pickle +from queries.fruit_seller.fruitstate import FruitStateManager # Indicate that this module wants to handle parse trees for queries, # as opposed to simple literal text strings HANDLE_TREE = True # The grammar nonterminals this module wants to handle -QUERY_NONTERMINALS = {"QFruitStartQuery", "QFruitQuery"} +QUERY_NONTERMINALS = {"QFruit"} # The context-free grammar for the queries recognized by this plug-in module GRAMMAR = """ -Query → QFruitStartQuery | QFruitQuery +Query → + QFruit '?'? + +QFruit → + QFruitStartQuery | QFruitQuery QFruitStartQuery → - "ég" "vill" "kaupa" "ávexti" - | "ég" "vil" "kaupa" "ávexti" + "ég" "vill" "kaupa"? "ávexti" + | "ég" "vil" "kaupa"? "ávexti" | "mig" "langar" "að" "kaupa" "ávexti" "hjá"? "þér"? | "mig" "langar" "að" "panta" "ávexti" "hjá"? "þér"? | "get" "ég" "keypt" "ávexti" "hjá" "þér" '?'? @@ -79,7 +87,7 @@ | "hvaða" "ávexti" "ertu" "með" '?'? | "hvaða" "ávextir" "eru" "í" "boði" '?'? -QFruitList → QNumOfFruit QNumOfFruit* +QFruitList → QNumOfFruit QNumOfFruit* QNumOfFruit → QNum? QFruit "og"? @@ -100,11 +108,11 @@ """ # fruitStateManager = FruitStateManager() +_START_CONVERSATION_QTYPE = "QFruitStartQuery" def QFruitStartQuery(node: Node, params: QueryStateDict, result: Result): - params[0]["_state"]["query"].set_client_data("conversation", "fruit_seller") - result.qtype = "QFruitStartQuery" + result.qtype = _START_CONVERSATION_QTYPE def QAddFruitQuery(node: Node, params: QueryStateDict, result: Result): @@ -154,29 +162,43 @@ def QFruit(node: Node, params: QueryStateDict, result: Result): result.fruit = fruit -def updateClientData(query, fruitStateManager): - d = pickle.dumps(fruitStateManager) - query.set_client_data("conversation_data", d) +def updateClientData(query: Query, fruitStateManager: FruitStateManager): + d: str = base64.b64encode(pickle.dumps(fruitStateManager)).decode("utf-8") + print("STORING DATA:", d) + query.set_client_data("conversation_data", {"obj": d}) def sentence(state: QueryStateDict, result: Result) -> None: """Called when sentence processing is complete""" q: Query = state["query"] + conv_state = q.client_data("conversation") + conv_data = q.client_data("conversation_data") + qt = result.get("qtype") + print(conv_state, conv_data, qt) # checka hvort user se i samtali med q.client_data - if q.client_data("conversation") != "fruit_seller" or "qtype" not in result: + if qt != _START_CONVERSATION_QTYPE and conv_state is None: q.set_error("E_QUERY_NOT_UNDERSTOOD") return # Successfully matched a query type try: - if result.qtype == "QFruitStartQuery": + if result.qtype == _START_CONVERSATION_QTYPE: fruitStateManager = FruitStateManager() fruitStateManager.startFruitOrder() + print("i am here 1") + q.set_client_data("conversation", {"in_dialogue": "fruit_seller"}) else: - fruitStateManager = pickle.loads(q.client_data("conversation_data")) + print("i am here 2") + print(conv_data.get("obj")) + fruitStateManager = pickle.loads( + base64.b64decode(conv_data.get("obj").encode("utf-8")) + ) + print("i am here 3") fruitStateManager.stateMethod(result.qtype, result) + print("i am here 4") updateClientData(q, fruitStateManager) ans = fruitStateManager.ans + print("i am here 5") if result.qtype == "OrderComplete" or result.qtype == "CancelOrder": q.set_client_data("conversation", None) q.set_client_data("conversation_data", None) @@ -185,6 +207,6 @@ def sentence(state: QueryStateDict, result: Result) -> None: return except Exception as e: logging.warning( - "Exception {0} while processing date query '{1}'".format(e, q.query) + "Exception {0} while processing fruit seller query '{1}'".format(e, q.query) ) q.set_error("E_EXCEPTION: {0}".format(e)) From 1a0e0424207b94b7fcabb74bb5df02d8d7c79ca0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Tue, 21 Jun 2022 11:58:33 +0000 Subject: [PATCH 040/371] Added typing --- queries/fruit_seller/fruitstate.py | 102 ++++++++++++++++------------- queries/fruit_seller/resource.py | 6 +- queries/fruitseller.py | 14 ++-- 3 files changed, 65 insertions(+), 57 deletions(-) diff --git a/queries/fruit_seller/fruitstate.py b/queries/fruit_seller/fruitstate.py index b36a104a..2c69463a 100644 --- a/queries/fruit_seller/fruitstate.py +++ b/queries/fruit_seller/fruitstate.py @@ -1,54 +1,59 @@ +from typing import Any, Optional, List + from tree import Result from queries.fruit_seller.resource import Resource from reynir import NounPhrase -from typing import Optional, List class FruitStateManager: def __init__(self): self.resources: List[Resource] = [] - self.fruitState = None + self.fruitState: Optional[Resource] = None self.ans: Optional[str] = None - def startFruitOrder(self): + def startFruitOrder(self) -> None: # Order here is the priority of each resource self.resources.append(FruitState(required=True)) self.resources.append(OrderReceivedState(required=True)) self.updateState("FruitStart") - def generateAnswer(self, type): + def generateAnswer(self, type: str) -> None: if type == "FruitStart": self.ans = "Hvaða ávexti má bjóða þér?" elif type == "ListFruit": - if len(self.fruitState.data) != 0: - self.ans = "Komið! Pöntunin samanstendur af " - for fruitname in self.fruitState.data.keys(): - fruitNumber = self.fruitState.data[fruitname] - if fruitname == "banani": - self.ans += ( - "banana " - if (fruitNumber == 1) - else f"{fruitNumber} bönunum " - ) - elif fruitname == "appelsína": - self.ans += ( - "appelsínu " - if (fruitNumber == 1) - else f"{fruitNumber} appelsínum " - ) - elif fruitname == "pera": - self.ans += ( - "peru " if (fruitNumber == 1) else f"{fruitNumber} perum " - ) - elif fruitname == "epli": - self.ans += ( - "epli " if (fruitNumber == 1) else f"{fruitNumber} eplum " - ) - else: - self.ans += f"{'' if (fruitNumber == 1) else f'{fruitNumber} '}{NounPhrase(fruitname).dative} " - self.ans = self.ans.rstrip() + ". Var það eitthvað fleira?" + if self.fruitState is not None: + if len(self.fruitState.data) != 0: + self.ans = "Komið! Pöntunin samanstendur af " + for fruitname in self.fruitState.data.keys(): + fruitNumber = self.fruitState.data[fruitname] + if fruitname == "banani": + self.ans += ( + "banana " + if (fruitNumber == 1) + else f"{fruitNumber} bönunum " + ) + elif fruitname == "appelsína": + self.ans += ( + "appelsínu " + if (fruitNumber == 1) + else f"{fruitNumber} appelsínum " + ) + elif fruitname == "pera": + self.ans += ( + "peru " if (fruitNumber == 1) else f"{fruitNumber} perum " + ) + elif fruitname == "epli": + self.ans += ( + "epli " if (fruitNumber == 1) else f"{fruitNumber} eplum " + ) + else: + self.ans += f"{'' if (fruitNumber == 1) else f'{fruitNumber} '}{NounPhrase(fruitname).dative} " + self.ans = self.ans.rstrip() + ". Var það eitthvað fleira?" + else: + self.ans = "Komið! Karfan er núna tóm. Hvaða ávexti má bjóða þér?" else: - self.ans = "Komið! Karfan er núna tóm. Hvaða ávexti má bjóða þér?" + self.ans = "Kom upp villa, reyndu aftur." + elif type == "FruitOrderNotFinished": self.ans = "Hverju viltu að bæta við pöntunina?" elif type == "FruitsFulfilled": @@ -70,7 +75,7 @@ def generateAnswer(self, type): elif type == "NoFruitToRemove": self.ans = "Engir ávextir eru í körfunni til að fjarlægja." - def updateState(self, type): + def updateState(self, type: str) -> None: for resource in self.resources: if resource.required and not resource.fulfilled: if resource.data is None: @@ -93,27 +98,30 @@ def updateState(self, type): self.generateAnswer(type) print("Current state: ", self.fruitState.state) - def stateMethod(self, methodName, result): + def stateMethod(self, methodName: str, result: Result): try: - method = getattr(self.fruitState.state, methodName) - if method is not None: - ( - self.fruitState.data, - self.fruitState.partiallyFulfilled, - self.fruitState.fulfilled, - ) = method(result) - self.updateState(result.qtype) + if self.fruitState is not None: + method = getattr(self.fruitState.state, methodName) + if method is not None: + ( + self.fruitState.data, + self.fruitState.partiallyFulfilled, + self.fruitState.fulfilled, + ) = method(result) + self.updateState(result.qtype) + else: + self.ans = "Kom upp villa, reyndu aftur." except Exception as e: print("Error: ", e) result.qtype = "FruitMethodNotFound" class FruitState(Resource): - def __init__(self, required=True): + def __init__(self, required: bool = True): super().__init__(required) class DataState: - def __init__(self, data, partiallyFulfilled, fulfilled): + def __init__(self, data: Any, partiallyFulfilled: bool, fulfilled: bool): self.data = data self.partiallyFulfilled = partiallyFulfilled self.fulfilled = fulfilled @@ -159,7 +167,7 @@ def QCancelOrder(self, result: Result): return (self.data, self.partiallyFulfilled, self.fulfilled) class PartiallyFulfilledState(DataState): - def __init__(self, data, partiallyFulfilled, fulfilled): + def __init__(self, data: Any, partiallyFulfilled: bool, fulfilled: bool): super().__init__(data, partiallyFulfilled, fulfilled) # User is happy with the order, switch to confirm state @@ -174,7 +182,7 @@ def QYes(self, result: Result): return (self.data, self.partiallyFulfilled, self.fulfilled) class FulfilledState(DataState): - def __init__(self, data, partiallyFulfilled, fulfilled): + def __init__(self, data: Any, partiallyFulfilled: bool, fulfilled: bool): super().__init__(data, partiallyFulfilled, fulfilled) # The order is correct, say the order is confirmed @@ -191,7 +199,7 @@ def QNo(self, result: Result): class OrderReceivedState(FruitState): - def __init__(self, required=True): + def __init__(self, required: bool = True): super().__init__(required) # User is happy with the order, switch to confirm state diff --git a/queries/fruit_seller/resource.py b/queries/fruit_seller/resource.py index 7691b2d8..8edcd56c 100644 --- a/queries/fruit_seller/resource.py +++ b/queries/fruit_seller/resource.py @@ -9,13 +9,13 @@ def __init__(self, required: bool = False): self.fulfilled: bool = False self.state = None - def isRequired(self): + def isRequired(self) -> bool: return self.required - def getData(self): + def getData(self) -> Any: return self.data - def isFulfilled(self): + def isFulfilled(self) -> bool: return self.fulfilled def setData(self, data: Any): diff --git a/queries/fruitseller.py b/queries/fruitseller.py index 52e5ee60..29beaa55 100644 --- a/queries/fruitseller.py +++ b/queries/fruitseller.py @@ -30,7 +30,7 @@ | "ég" "vil" "kaupa"? "ávexti" | "mig" "langar" "að" "kaupa" "ávexti" "hjá"? "þér"? | "mig" "langar" "að" "panta" "ávexti" "hjá"? "þér"? - | "get" "ég" "keypt" "ávexti" "hjá" "þér" '?'? + | "get" "ég" "keypt" "ávexti" "hjá" "þér" QFruitQuery → QAddFruitQuery @@ -80,12 +80,12 @@ "en" | "í" "staðinn" "fyrir" QFruitOptionsQuery → - "hvað" "er" "í" "boði" '?'? - | "hverjir" "eru" "valmöguleikarnir" '?'? - | "hvaða" "valmöguleikar" "eru" "í" "boði" '?'? - | "hvaða" "valmöguleikar" "eru" "til" '?'? - | "hvaða" "ávexti" "ertu" "með" '?'? - | "hvaða" "ávextir" "eru" "í" "boði" '?'? + "hvað" "er" "í" "boði" + | "hverjir" "eru" "valmöguleikarnir" + | "hvaða" "valmöguleikar" "eru" "í" "boði" + | "hvaða" "valmöguleikar" "eru" "til" + | "hvaða" "ávexti" "ertu" "með" + | "hvaða" "ávextir" "eru" "í" "boði" QFruitList → QNumOfFruit QNumOfFruit* From a5e764c837557c47fbdbc0b229d160bdb66ba3a8 Mon Sep 17 00:00:00 2001 From: Logi E Date: Tue, 21 Jun 2022 11:59:41 +0000 Subject: [PATCH 041/371] added helper functions in query.py and fixed some type error --- queries/fruitseller.py | 55 +++++++++++++++++++++++------------------- query.py | 14 ++++++++++- 2 files changed, 43 insertions(+), 26 deletions(-) diff --git a/queries/fruitseller.py b/queries/fruitseller.py index 29beaa55..403c2bd2 100644 --- a/queries/fruitseller.py +++ b/queries/fruitseller.py @@ -1,4 +1,4 @@ -from typing import cast +from typing import Optional, cast import logging import pickle @@ -19,7 +19,7 @@ # The context-free grammar for the queries recognized by this plug-in module GRAMMAR = """ -Query → +Query → QFruit '?'? QFruit → @@ -32,16 +32,16 @@ | "mig" "langar" "að" "panta" "ávexti" "hjá"? "þér"? | "get" "ég" "keypt" "ávexti" "hjá" "þér" -QFruitQuery → - QAddFruitQuery - | QRemoveFruitQuery - | QChangeFruitQuery +QFruitQuery → + QAddFruitQuery + | QRemoveFruitQuery + | QChangeFruitQuery | QFruitOptionsQuery | QYes | QNo | QCancelOrder -QAddFruitQuery → +QAddFruitQuery → "já"? "má" "ég" "fá" QFruitList | "já"? "get" "ég" "fengið" QFruitList | "já"? "gæti" "ég" "fengið" QFruitList @@ -67,7 +67,7 @@ QChangeFruitQuery → QChangeStart QFruitList QChangeConnector QFruitList -QChangeStart → +QChangeStart → "breyttu" | "ég" "vil" "frekar" | "ég" "vill" "frekar" @@ -76,7 +76,7 @@ | "ég" "vil" "breyta" | "ég" "vill" "breyta" -QChangeConnector → +QChangeConnector → "en" | "í" "staðinn" "fyrir" QFruitOptionsQuery → @@ -109,6 +109,7 @@ # fruitStateManager = FruitStateManager() _START_CONVERSATION_QTYPE = "QFruitStartQuery" +_DIALOGUE_NAME = "fruit_seller" def QFruitStartQuery(node: Node, params: QueryStateDict, result: Result): @@ -164,19 +165,19 @@ def QFruit(node: Node, params: QueryStateDict, result: Result): def updateClientData(query: Query, fruitStateManager: FruitStateManager): d: str = base64.b64encode(pickle.dumps(fruitStateManager)).decode("utf-8") - print("STORING DATA:", d) - query.set_client_data("conversation_data", {"obj": d}) + query.set_client_data(_DIALOGUE_NAME, {"state": d}) def sentence(state: QueryStateDict, result: Result) -> None: """Called when sentence processing is complete""" q: Query = state["query"] - conv_state = q.client_data("conversation") - conv_data = q.client_data("conversation_data") + dialogue_state = q.get_dialogue_state() qt = result.get("qtype") - print(conv_state, conv_data, qt) + # checka hvort user se i samtali med q.client_data - if qt != _START_CONVERSATION_QTYPE and conv_state is None: + if qt != _START_CONVERSATION_QTYPE and not ( + dialogue_state and dialogue_state.get("in_dialogue") == _DIALOGUE_NAME + ): q.set_error("E_QUERY_NOT_UNDERSTOOD") return @@ -185,23 +186,27 @@ def sentence(state: QueryStateDict, result: Result) -> None: if result.qtype == _START_CONVERSATION_QTYPE: fruitStateManager = FruitStateManager() fruitStateManager.startFruitOrder() - print("i am here 1") - q.set_client_data("conversation", {"in_dialogue": "fruit_seller"}) + q.start_dialogue(_DIALOGUE_NAME) else: - print("i am here 2") - print(conv_data.get("obj")) + if dialogue_state is None: + q.set_error("E_QUERY_NOT_UNDERSTOOD") + return + fruitmanager_serialized: Optional[str] = cast( + Optional[str], dialogue_state.get("obj") + ) + if not fruitmanager_serialized: + q.set_error("E_QUERY_NOT_UNDERSTOOD") + return fruitStateManager = pickle.loads( - base64.b64decode(conv_data.get("obj").encode("utf-8")) + base64.b64decode(fruitmanager_serialized.encode("utf-8")) ) - print("i am here 3") fruitStateManager.stateMethod(result.qtype, result) - print("i am here 4") + updateClientData(q, fruitStateManager) ans = fruitStateManager.ans - print("i am here 5") + if result.qtype == "OrderComplete" or result.qtype == "CancelOrder": - q.set_client_data("conversation", None) - q.set_client_data("conversation_data", None) + q.end_dialogue() q.set_answer(*gen_answer(ans)) return diff --git a/query.py b/query.py index 7d41bb3c..d624e98e 100755 --- a/query.py +++ b/query.py @@ -141,7 +141,7 @@ def __call__(self, w: str, *, filter_func: Optional[BinFilterFunc] = None) -> st _IGNORED_PREFIX_RE = r"^({0})\s*".format("|".join(_IGNORED_QUERY_PREFIXES)) # Auto-capitalization corrections _CAPITALIZATION_REPLACEMENTS = (("í Dag", "í dag"),) - +_DIALOGUE_DATA_KEY = "dialogue" def beautify_query(query: str) -> str: """Return a minimally beautified version of the given query string""" @@ -892,6 +892,18 @@ def set_client_data(self, key: str, data: ClientDataDict) -> None: return Query.store_query_data(self.client_id, key, data) + def get_dialogue_state(self) -> Optional[ClientDataDict]: + """Fetch the dialogue state for a client""" + return self.client_data(_DIALOGUE_DATA_KEY) + + def start_dialogue(self, dialogue_name: str) -> None: + """Set the clients state to be in the given dialogue""" + self.set_client_data(_DIALOGUE_DATA_KEY, {"in_dialogue": dialogue_name}) + + def end_dialogue(self) -> None: + """End the client's current dialogue""" + self.set_client_data(_DIALOGUE_DATA_KEY, {}) + @staticmethod def store_query_data(client_id: str, key: str, data: ClientDataDict) -> bool: """Save client query data in the database, under the given key""" From 3f9eb455852c25ccaca80e2e5b330ee8652e32a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Tue, 21 Jun 2022 14:05:59 +0000 Subject: [PATCH 042/371] Working on generalizing FruitStateManager --- queries/fruit_seller/fruitstate.py | 73 +++++++++++++----------------- queries/fruitseller.py | 20 +++++--- query.py | 4 +- 3 files changed, 46 insertions(+), 51 deletions(-) diff --git a/queries/fruit_seller/fruitstate.py b/queries/fruit_seller/fruitstate.py index 2c69463a..015240ae 100644 --- a/queries/fruit_seller/fruitstate.py +++ b/queries/fruit_seller/fruitstate.py @@ -3,52 +3,39 @@ from tree import Result from queries.fruit_seller.resource import Resource from reynir import NounPhrase - - -class FruitStateManager: +from queries import natlang_seq, sing_or_plur + +def _list_items(items: Any) -> str: + item_list: List[str] = [] + for name in items.keys(): + number: int = items[name] + # TODO: get general plural form + plural_name: str = NounPhrase(name).dative or name + item_list.append(sing_or_plur(number, name, plural_name)) + return natlang_seq(item_list) + +class DialogueStateManager: def __init__(self): self.resources: List[Resource] = [] - self.fruitState: Optional[Resource] = None + self.resourceState: Optional[Resource] = None self.ans: Optional[str] = None - def startFruitOrder(self) -> None: + def initialize_resources(self, dialogue: str) -> None: # Order here is the priority of each resource + # TODO: parse yaml, add resources from yaml file self.resources.append(FruitState(required=True)) self.resources.append(OrderReceivedState(required=True)) - self.updateState("FruitStart") + self.updateState(dialogue) def generateAnswer(self, type: str) -> None: - if type == "FruitStart": + if type == "QFruitStartQuery": self.ans = "Hvaða ávexti má bjóða þér?" elif type == "ListFruit": - if self.fruitState is not None: - if len(self.fruitState.data) != 0: + if self.resourceState is not None: + if len(self.resourceState.data) != 0: self.ans = "Komið! Pöntunin samanstendur af " - for fruitname in self.fruitState.data.keys(): - fruitNumber = self.fruitState.data[fruitname] - if fruitname == "banani": - self.ans += ( - "banana " - if (fruitNumber == 1) - else f"{fruitNumber} bönunum " - ) - elif fruitname == "appelsína": - self.ans += ( - "appelsínu " - if (fruitNumber == 1) - else f"{fruitNumber} appelsínum " - ) - elif fruitname == "pera": - self.ans += ( - "peru " if (fruitNumber == 1) else f"{fruitNumber} perum " - ) - elif fruitname == "epli": - self.ans += ( - "epli " if (fruitNumber == 1) else f"{fruitNumber} eplum " - ) - else: - self.ans += f"{'' if (fruitNumber == 1) else f'{fruitNumber} '}{NounPhrase(fruitname).dative} " - self.ans = self.ans.rstrip() + ". Var það eitthvað fleira?" + _list_items(self.resourceState.data) + self.ans += ". Var það eitthvað fleira?" else: self.ans = "Komið! Karfan er núna tóm. Hvaða ávexti má bjóða þér?" else: @@ -79,8 +66,8 @@ def updateState(self, type: str) -> None: for resource in self.resources: if resource.required and not resource.fulfilled: if resource.data is None: - if self.fruitState is not resource: - self.fruitState = resource + if self.resourceState is not resource: + self.resourceState = resource resource.state = resource.DataState( resource.data, resource.partiallyFulfilled, resource.fulfilled ) @@ -96,17 +83,17 @@ def updateState(self, type: str) -> None: ) break self.generateAnswer(type) - print("Current state: ", self.fruitState.state) + print("Current state: ", self.resourceState.state) def stateMethod(self, methodName: str, result: Result): try: - if self.fruitState is not None: - method = getattr(self.fruitState.state, methodName) + if self.resourceState is not None: + method = getattr(self.resourceState.state, methodName) if method is not None: ( - self.fruitState.data, - self.fruitState.partiallyFulfilled, - self.fruitState.fulfilled, + self.resourceState.data, + self.resourceState.partiallyFulfilled, + self.resourceState.fulfilled, ) = method(result) self.updateState(result.qtype) else: @@ -120,6 +107,8 @@ class FruitState(Resource): def __init__(self, required: bool = True): super().__init__(required) + #def generate_answer + class DataState: def __init__(self, data: Any, partiallyFulfilled: bool, fulfilled: bool): self.data = data diff --git a/queries/fruitseller.py b/queries/fruitseller.py index 403c2bd2..11fccf44 100644 --- a/queries/fruitseller.py +++ b/queries/fruitseller.py @@ -7,7 +7,7 @@ from query import Query, QueryStateDict from tree import Result, Node from queries import gen_answer, parse_num -from queries.fruit_seller.fruitstate import FruitStateManager +from queries.fruit_seller.fruitstate import DialogueStateManager # Indicate that this module wants to handle parse trees for queries, # as opposed to simple literal text strings @@ -163,7 +163,7 @@ def QFruit(node: Node, params: QueryStateDict, result: Result): result.fruit = fruit -def updateClientData(query: Query, fruitStateManager: FruitStateManager): +def updateClientData(query: Query, fruitStateManager: DialogueStateManager): d: str = base64.b64encode(pickle.dumps(fruitStateManager)).decode("utf-8") query.set_client_data(_DIALOGUE_NAME, {"state": d}) @@ -171,30 +171,36 @@ def updateClientData(query: Query, fruitStateManager: FruitStateManager): def sentence(state: QueryStateDict, result: Result) -> None: """Called when sentence processing is complete""" q: Query = state["query"] - dialogue_state = q.get_dialogue_state() + dialogue_state = q.get_dialogue_state() or {} qt = result.get("qtype") # checka hvort user se i samtali med q.client_data if qt != _START_CONVERSATION_QTYPE and not ( dialogue_state and dialogue_state.get("in_dialogue") == _DIALOGUE_NAME ): + print("User not in dialogue") q.set_error("E_QUERY_NOT_UNDERSTOOD") return # Successfully matched a query type try: + print("Qtypy: ", result.qtype) if result.qtype == _START_CONVERSATION_QTYPE: - fruitStateManager = FruitStateManager() - fruitStateManager.startFruitOrder() - q.start_dialogue(_DIALOGUE_NAME) + fruitStateManager = DialogueStateManager() + fruitStateManager.initialize_resources(_START_CONVERSATION_QTYPE) + dialogue_state.update({"state": fruitStateManager}) + q.start_dialogue(_DIALOGUE_NAME, dialogue_state) else: if dialogue_state is None: + print("Dialogue state is none") q.set_error("E_QUERY_NOT_UNDERSTOOD") return + print(dialogue_state) fruitmanager_serialized: Optional[str] = cast( - Optional[str], dialogue_state.get("obj") + Optional[str], dialogue_state.get("state") ) if not fruitmanager_serialized: + print("Fruitmanager not serialized") q.set_error("E_QUERY_NOT_UNDERSTOOD") return fruitStateManager = pickle.loads( diff --git a/query.py b/query.py index d624e98e..8c084bcc 100755 --- a/query.py +++ b/query.py @@ -896,9 +896,9 @@ def get_dialogue_state(self) -> Optional[ClientDataDict]: """Fetch the dialogue state for a client""" return self.client_data(_DIALOGUE_DATA_KEY) - def start_dialogue(self, dialogue_name: str) -> None: + def start_dialogue(self, dialogue_name: str, data: Dict[str, Any]) -> None: """Set the clients state to be in the given dialogue""" - self.set_client_data(_DIALOGUE_DATA_KEY, {"in_dialogue": dialogue_name}) + self.set_client_data(_DIALOGUE_DATA_KEY, {"in_dialogue": dialogue_name, **data}) def end_dialogue(self) -> None: """End the client's current dialogue""" From 596f999ea22de4cc1c5e12fcdb9e18048957a5b5 Mon Sep 17 00:00:00 2001 From: Logi E Date: Tue, 21 Jun 2022 14:18:33 +0000 Subject: [PATCH 043/371] fixed a serialization bug --- queries/fruit_seller/fruitstate.py | 15 ++++++++++++++- queries/fruitseller.py | 16 ++++++++-------- query.py | 8 ++++++-- 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/queries/fruit_seller/fruitstate.py b/queries/fruit_seller/fruitstate.py index 015240ae..27dbd9df 100644 --- a/queries/fruit_seller/fruitstate.py +++ b/queries/fruit_seller/fruitstate.py @@ -1,10 +1,14 @@ from typing import Any, Optional, List +import pickle +import base64 + from tree import Result from queries.fruit_seller.resource import Resource from reynir import NounPhrase from queries import natlang_seq, sing_or_plur + def _list_items(items: Any) -> str: item_list: List[str] = [] for name in items.keys(): @@ -14,6 +18,7 @@ def _list_items(items: Any) -> str: item_list.append(sing_or_plur(number, name, plural_name)) return natlang_seq(item_list) + class DialogueStateManager: def __init__(self): self.resources: List[Resource] = [] @@ -102,12 +107,20 @@ def stateMethod(self, methodName: str, result: Result): print("Error: ", e) result.qtype = "FruitMethodNotFound" + @classmethod + def serialize(cls, instance: "DialogueStateManager") -> str: + return base64.b64encode(pickle.dumps(instance)).decode("utf-8") + + @classmethod + def deserialize(cls, serialized: str) -> "DialogueStateManager": + return pickle.loads(base64.b64decode(serialized.encode("utf-8"))) + class FruitState(Resource): def __init__(self, required: bool = True): super().__init__(required) - #def generate_answer + # def generate_answer class DataState: def __init__(self, data: Any, partiallyFulfilled: bool, fulfilled: bool): diff --git a/queries/fruitseller.py b/queries/fruitseller.py index 11fccf44..e62e719b 100644 --- a/queries/fruitseller.py +++ b/queries/fruitseller.py @@ -1,8 +1,6 @@ from typing import Optional, cast import logging -import pickle -import base64 from query import Query, QueryStateDict from tree import Result, Node @@ -164,8 +162,9 @@ def QFruit(node: Node, params: QueryStateDict, result: Result): def updateClientData(query: Query, fruitStateManager: DialogueStateManager): - d: str = base64.b64encode(pickle.dumps(fruitStateManager)).decode("utf-8") - query.set_client_data(_DIALOGUE_NAME, {"state": d}) + query.set_client_data( + _DIALOGUE_NAME, {"state": DialogueStateManager.serialize(fruitStateManager)} + ) def sentence(state: QueryStateDict, result: Result) -> None: @@ -188,8 +187,9 @@ def sentence(state: QueryStateDict, result: Result) -> None: if result.qtype == _START_CONVERSATION_QTYPE: fruitStateManager = DialogueStateManager() fruitStateManager.initialize_resources(_START_CONVERSATION_QTYPE) - dialogue_state.update({"state": fruitStateManager}) - q.start_dialogue(_DIALOGUE_NAME, dialogue_state) + q.start_dialogue( + _DIALOGUE_NAME, DialogueStateManager.serialize(fruitStateManager) + ) else: if dialogue_state is None: print("Dialogue state is none") @@ -203,8 +203,8 @@ def sentence(state: QueryStateDict, result: Result) -> None: print("Fruitmanager not serialized") q.set_error("E_QUERY_NOT_UNDERSTOOD") return - fruitStateManager = pickle.loads( - base64.b64decode(fruitmanager_serialized.encode("utf-8")) + fruitStateManager = DialogueStateManager.deserialize( + fruitmanager_serialized ) fruitStateManager.stateMethod(result.qtype, result) diff --git a/query.py b/query.py index 8c084bcc..d67ec069 100755 --- a/query.py +++ b/query.py @@ -143,6 +143,7 @@ def __call__(self, w: str, *, filter_func: Optional[BinFilterFunc] = None) -> st _CAPITALIZATION_REPLACEMENTS = (("í Dag", "í dag"),) _DIALOGUE_DATA_KEY = "dialogue" + def beautify_query(query: str) -> str: """Return a minimally beautified version of the given query string""" # Make sure the query starts with an uppercase letter @@ -896,9 +897,12 @@ def get_dialogue_state(self) -> Optional[ClientDataDict]: """Fetch the dialogue state for a client""" return self.client_data(_DIALOGUE_DATA_KEY) - def start_dialogue(self, dialogue_name: str, data: Dict[str, Any]) -> None: + def start_dialogue(self, dialogue_name: str, state_manager_serialized: str) -> None: """Set the clients state to be in the given dialogue""" - self.set_client_data(_DIALOGUE_DATA_KEY, {"in_dialogue": dialogue_name, **data}) + self.set_client_data( + _DIALOGUE_DATA_KEY, + {"in_dialogue": dialogue_name, "state": state_manager_serialized}, + ) def end_dialogue(self) -> None: """End the client's current dialogue""" From f4d00658c40c8c795c63bfdb39218b2669462bb9 Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Tue, 21 Jun 2022 14:44:15 +0000 Subject: [PATCH 044/371] Smart Things js and HTML, IoT Hue split into IoTHue and IoTConnect Smart Things js and HTML, IoT Hue split into IoTHue and IoTConnect --- queries/iot_connect.py | 187 ++++++++++++++++++ queries/iot_hue.py | 29 +-- queries/js/IoT_Embla/Philips_Hue/connect.js | 0 queries/js/IoT_Embla/Smart_Things/st.html | 24 +++ queries/js/IoT_Embla/Smart_Things/st.js | 161 +++++++++++++++ .../IoT_Embla/Smart_Things/st_connecthub.js | 49 +++++ 6 files changed, 424 insertions(+), 26 deletions(-) create mode 100644 queries/iot_connect.py delete mode 100644 queries/js/IoT_Embla/Philips_Hue/connect.js create mode 100644 queries/js/IoT_Embla/Smart_Things/st.html create mode 100644 queries/js/IoT_Embla/Smart_Things/st.js create mode 100644 queries/js/IoT_Embla/Smart_Things/st_connecthub.js diff --git a/queries/iot_connect.py b/queries/iot_connect.py new file mode 100644 index 00000000..471c3b4b --- /dev/null +++ b/queries/iot_connect.py @@ -0,0 +1,187 @@ +""" + + Greynir: Natural language processing for Icelandic + + Randomness query response module + + Copyright (C) 2022 Miðeind ehf. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see http://www.gnu.org/licenses/. + + This query module handles queries related to the generation + of random numbers, e.g. "Kastaðu tengingi", "Nefndu tölu milli 5 og 10", etc. + +""" + + +from typing import Dict, Mapping, Optional, cast +from typing_extensions import TypedDict + +import logging +import random +import json +import flask + +from query import Query, QueryStateDict, AnswerTuple +from queries import gen_answer, read_jsfile, read_grammar_file +from tree import Result, Node + + +class SmartLights(TypedDict): + selected_light: str + philips_hue: Dict[str, str] + + +class DeviceData(TypedDict): + smartlights: SmartLights + + +_IoT_QTYPE = "IoTConnect" + +TOPIC_LEMMAS = [ + "tengja", +] + + +def help_text(lemma: str) -> str: + """Help text to return when query.py is unable to parse a query but + one of the above lemmas is found in it""" + return "Ég skil þig ef þú segir til dæmis: {0}.".format( + random.choice( + ( + "Tengu miðstöðina", + "Tengdu ljósin" + ) + ) + ) + + + +# This module wants to handle parse trees for queries +HANDLE_TREE = True + +# The grammar nonterminals this module wants to handle +QUERY_NONTERMINALS = {"QIoTConnect"} + +# The context-free grammar for the queries recognized by this plug-in module +# GRAMMAR = read_grammar_file("iot_hue") + +GRAMMAR = f""" + +/þgf = þgf +/ef = ef + +Query → + QIoTConnect '?'? + +QIoTConnect → + QIoTConnectLights + | QIoTConnectHub + + +QIoTConnectLights → + "tengdu" "ljósin" + +QIoTConnectHub → + "tengdu" "miðstöðina" + +""" + +def QIoTConnectLights(node: Node, params: QueryStateDict, result: Result) -> None: + result.qtype = "connect_lights" + result.action = "connect_lights" + +def QIoTConnectHub(node: Node, params: QueryStateDict, result: Result) -> None: + print("Connect Hub") + result.qtype = "connect_hub" + result.action = "connect_hub" + + +def sentence(state: QueryStateDict, result: Result) -> None: + """Called when sentence processing is complete""" + q: Query = state["query"] + changing_color = result.get("changing_color", False) + changing_scene = result.get("changing_scene", False) + changing_brightness = result.get("changing_brightness", False) + print("error?", sum((changing_color, changing_scene, changing_brightness)) > 1) + if ( + sum((changing_color, changing_scene, changing_brightness)) > 1 + or "qtype" not in result + ): + q.set_error("E_QUERY_NOT_UNDERSTOOD") + return + + q.set_qtype(result.qtype) + if result.qtype == "connect_lights": + host = str(flask.request.host) + print("host: ", host) + smartdevice_type = "smartlight" + client_id = str(q.client_id) + print("client_id:", client_id) + js = read_jsfile("IoT_Embla/Philips_Hue/hub.js") + js += f"syncConnectHub('{client_id}','{host}');" + answer = "Philips Hue miðstöðin hefur verið tengd" + voice_answer = answer + response = dict(answer=answer) + q.set_answer(response, answer, voice_answer) + q.set_command(js) + return + elif result.qtype == "connect_hub": + host = str(flask.request.host) + print("host: ", host) + smartdevice_type = "smarthub" + client_id = str(q.client_id) + print("client_id:", client_id) + js = read_jsfile("IoT_Embla/Smart_Things/st_connecthub.js") + js += f"syncConnectHub('{client_id}','{host}');" + answer = "Smart Things miðstöðin hefur verið tengd" + voice_answer = answer + response = dict(answer=answer) + q.set_answer(response, answer, voice_answer) + q.set_command(js) + return + + + # smartdevice_type = "smartlights" + # client_id = str(q.client_id) + # print("client_id:", client_id) + + # # Fetch relevant data from the device_data table to perform an action on the lights + # device_data = cast(Optional[DeviceData], q.client_data(smartdevice_type)) + # print("device data :", device_data) + + # selected_light: Optional[str] = None + # hue_credentials: Optional[Dict[str, str]] = None + + # if device_data is not None and smartdevice_type in device_data: + # dev = device_data[smartdevice_type] + # assert dev is not None + # selected_light = dev.get("selected_light") + # hue_credentials = dev.get("philips_hue") + # bridge_ip = hue_credentials.get("ipAddress") + # username = hue_credentials.get("username") + + # if not device_data or not hue_credentials: + # answer = "ég var að kveikja ljósin! " + # q.set_answer(*gen_answer(answer)) + # return + + # # Successfully matched a query type + # print("bridge_ip: ", bridge_ip) + # print("username: ", username) + # print("selected light :", selected_light) + # print("hue credentials :", hue_credentials) + + + + # f"var BRIDGE_IP = '192.168.1.68';var USERNAME = 'p3obluiXT13IbHMpp4X63ZvZnpNRdbqqMt723gy2';" diff --git a/queries/iot_hue.py b/queries/iot_hue.py index 3f7792c5..250dd65f 100755 --- a/queries/iot_hue.py +++ b/queries/iot_hue.py @@ -114,14 +114,10 @@ def help_text(lemma: str) -> str: /ef = ef Query → - QIoT + QIoT '?'? QIoT → - QIoTQuery '?'? - | QIoTConnectLights '?'? - -QIoTConnectLights → - "tengdu" "ljósin" + QIoTQuery QIoTQuery -> QIoTMakeVerb QIoTMakeRest @@ -552,11 +548,6 @@ def QIoTLightName(node: Node, params: QueryStateDict, result: Result) -> None: result["light_name"] = result._indefinite -def QIoTConnectLights(node: Node, params: QueryStateDict, result: Result) -> None: - result.qtype = "connect_lights" - result.action = "connect_lights" - - # Convert color name into hue # Taken from home.py _COLOR_NAME_TO_CIE: Mapping[str, float] = { @@ -585,21 +576,6 @@ def sentence(state: QueryStateDict, result: Result) -> None: return q.set_qtype(result.qtype) - if result.qtype == "connect_lights": - host = str(flask.request.host) - print("host: ", host) - smartdevice_type = "smartlights" - client_id = str(q.client_id) - print("client_id:", client_id) - js = read_jsfile("IoT_Embla/Philips_Hue/hub.js") - js += f"syncConnectHub('{client_id}','{host}');" - answer = "Philips Hue miðstöðin hefur verið tengd" - voice_answer = answer - response = dict(answer=answer) - q.set_answer(response, answer, voice_answer) - q.set_command(js) - - return smartdevice_type = "smartlights" client_id = str(q.client_id) @@ -610,6 +586,7 @@ def sentence(state: QueryStateDict, result: Result) -> None: print("device data :", device_data) selected_light: Optional[str] = None + print("selected light:", selected_light) hue_credentials: Optional[Dict[str, str]] = None if device_data is not None and smartdevice_type in device_data: diff --git a/queries/js/IoT_Embla/Philips_Hue/connect.js b/queries/js/IoT_Embla/Philips_Hue/connect.js deleted file mode 100644 index e69de29b..00000000 diff --git a/queries/js/IoT_Embla/Smart_Things/st.html b/queries/js/IoT_Embla/Smart_Things/st.html new file mode 100644 index 00000000..e836903d --- /dev/null +++ b/queries/js/IoT_Embla/Smart_Things/st.html @@ -0,0 +1,24 @@ + + + + + + JS Hue test + + + + +
+

SmartThings Test

+
+ + +
+
+ + +
+
+ + + diff --git a/queries/js/IoT_Embla/Smart_Things/st.js b/queries/js/IoT_Embla/Smart_Things/st.js new file mode 100644 index 00000000..7e07a183 --- /dev/null +++ b/queries/js/IoT_Embla/Smart_Things/st.js @@ -0,0 +1,161 @@ +const AUTH_TOKEN = "Bearer 85dabaad-10de-4f74-94a7-0dee4685982e"; + +function turnOnLight() { + var myHeaders = new Headers(); + myHeaders.append("Authorization", AUTH_TOKEN); + myHeaders.append("Content-Type", "application/json"); + + var raw = JSON.stringify({ + commands: [ + { + component: "main", + capability: "switch", + command: "on", + }, + ], + }); + + var requestOptions = { + method: "POST", + headers: myHeaders, + body: raw, + redirect: "follow", + }; + + fetch( + "https://api.smartthings.com/v1/devices/7d47b44f-057c-4320-9777-3d1eadca106e/commands", + requestOptions + ) + .then((response) => response.text()) + .then((result) => console.log(result)) + .catch((error) => console.log("error", error)); +} + +function turnOffLight() { + var myHeaders = new Headers(); + myHeaders.append("Authorization", AUTH_TOKEN); + myHeaders.append("Content-Type", "application/json"); + + var raw = JSON.stringify({ + commands: [ + { + component: "main", + capability: "switch", + command: "off", + }, + ], + }); + + var requestOptions = { + method: "POST", + headers: myHeaders, + body: raw, + redirect: "follow", + }; + + fetch( + "https://api.smartthings.com/v1/devices/7d47b44f-057c-4320-9777-3d1eadca106e/commands", + requestOptions + ) + .then((response) => response.text()) + .then((result) => console.log(result)) + .catch((error) => console.log("error", error)); +} + +function raiseBrightness() { + var myHeaders = new Headers(); + myHeaders.append("Authorization", AUTH_TOKEN); + myHeaders.append("Content-Type", "application/json"); + + var raw = JSON.stringify({ + commands: [ + { + component: "main", + capability: "switchLevel", + command: "setLevel", + arguments: "rate"[100], + }, + ], + }); + + var requestOptions = { + method: "POST", + headers: myHeaders, + body: raw, + redirect: "follow", + }; + + fetch( + "https://api.smartthings.com/v1/devices/7d47b44f-057c-4320-9777-3d1eadca106e/commands", + requestOptions + ) + .then((response) => response.text()) + .then((result) => console.log(result)) + .catch((error) => console.log("error", error)); +} + +function lowerBrightness() { + var myHeaders = new Headers(); + myHeaders.append("Authorization", AUTH_TOKEN); + myHeaders.append("Content-Type", "application/json"); + + var raw = JSON.stringify({ + commands: [ + { + component: "main", + capability: "switchLevel", + command: "setLevel", + arguments: [1], + }, + ], + }); + + var requestOptions = { + method: "POST", + headers: myHeaders, + body: raw, + redirect: "follow", + }; + + fetch( + "https://api.smartthings.com/v1/devices/7d47b44f-057c-4320-9777-3d1eadca106e/commands", + requestOptions + ) + .then((response) => response.text()) + .then((result) => console.log(result)) + .catch((error) => console.log("error", error)); +} + +function smartThingsWrapper(device, capability, command, arguments = null) { + var myHeaders = new Headers(); + myHeaders.append("Authorization", AUTH_TOKEN); + myHeaders.append("Content-Type", "application/json"); + + var raw = JSON.stringify({ + commands: [ + { + component: device, + capability: capability, + command: command, + if(arguments) { + arguments: [arguments]; + }, + }, + ], + }); + + var requestOptions = { + method: "POST", + headers: myHeaders, + body: raw, + redirect: "follow", + }; + + fetch( + `"https://api.smartthings.com/v1/devices/${device}/commands"`, + requestOptions + ) + .then((response) => response.text()) + .then((result) => console.log(result)) + .catch((error) => console.log("error", error)); +} diff --git a/queries/js/IoT_Embla/Smart_Things/st_connecthub.js b/queries/js/IoT_Embla/Smart_Things/st_connecthub.js new file mode 100644 index 00000000..d4502b02 --- /dev/null +++ b/queries/js/IoT_Embla/Smart_Things/st_connecthub.js @@ -0,0 +1,49 @@ +async function storeDevice(data, ipAddress) { + console.log("store device"); + return fetch(`http://${ipAddress}/register_query_data.api`, { + method: "POST", + body: JSON.stringify(data), + headers: { + "Content-Type": "application/json", + }, + }) + .then((resp) => resp.json()) + .then((obj) => { + return obj; + }) + .catch((err) => { + console.log("Error while storing user"); + }); +} + +// bearer token 64780d2b-b763-433d-95ca-3eaaf5e10642 +async function connectHub(clientID, ipAddress) { + console.log("connect hub"); + let bearerToken = "64780d2b-b763-433d-95ca-3eaaf5e10642"; + + try { + const data = { + client_id: clientID, + key: "smart_hubs", + data: { + hubs: { + selected_hub: "smart_things", + smart_things: { + bearer_token: bearerToken, + }, + }, + }, + }; + + const result = await storeDevice(data, ipAddress); + console.log("result: ", result); + return "Tenging við snjalltæki tókst"; + } catch (error) { + console.log(error); + return "Ekki tókst að tengja snjalltæki"; + } +} + +function syncConnectHub(cliendID, ipAdress) { + connectHub(cliendID, ipAdress); +} From ecf094ca8f73f21f0268b769c5fde3184b38ddd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Tue, 21 Jun 2022 15:25:41 +0000 Subject: [PATCH 045/371] Fruit list text now generalized --- queries/fruit_seller/fruitstate.py | 3 ++- queries/fruitseller.py | 24 ++++++++++++------------ 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/queries/fruit_seller/fruitstate.py b/queries/fruit_seller/fruitstate.py index 27dbd9df..df30424e 100644 --- a/queries/fruit_seller/fruitstate.py +++ b/queries/fruit_seller/fruitstate.py @@ -33,13 +33,14 @@ def initialize_resources(self, dialogue: str) -> None: self.updateState(dialogue) def generateAnswer(self, type: str) -> None: + # TODO: Move answers into each state and get the answer here if type == "QFruitStartQuery": self.ans = "Hvaða ávexti má bjóða þér?" elif type == "ListFruit": if self.resourceState is not None: if len(self.resourceState.data) != 0: self.ans = "Komið! Pöntunin samanstendur af " - _list_items(self.resourceState.data) + self.ans += _list_items(self.resourceState.data) self.ans += ". Var það eitthvað fleira?" else: self.ans = "Komið! Karfan er núna tóm. Hvaða ávexti má bjóða þér?" diff --git a/queries/fruitseller.py b/queries/fruitseller.py index e62e719b..a5027317 100644 --- a/queries/fruitseller.py +++ b/queries/fruitseller.py @@ -12,19 +12,20 @@ HANDLE_TREE = True # The grammar nonterminals this module wants to handle -QUERY_NONTERMINALS = {"QFruit"} +QUERY_NONTERMINALS = {"QFruitSeller"} # The context-free grammar for the queries recognized by this plug-in module GRAMMAR = """ Query → - QFruit '?'? + QFruitSeller '?'? -QFruit → +QFruitSeller → QFruitStartQuery | QFruitQuery QFruitStartQuery → - "ég" "vill" "kaupa"? "ávexti" + "ávöxtur" + | "ég" "vill" "kaupa"? "ávexti" | "ég" "vil" "kaupa"? "ávexti" | "mig" "langar" "að" "kaupa" "ávexti" "hjá"? "þér"? | "mig" "langar" "að" "panta" "ávexti" "hjá"? "þér"? @@ -40,7 +41,7 @@ | QCancelOrder QAddFruitQuery → - "já"? "má" "ég" "fá" QFruitList + "já"? "má"? "ég"? "fá"? QFruitList | "já"? "get" "ég" "fengið" QFruitList | "já"? "gæti" "ég" "fengið" QFruitList | "já"? "ég" "vil" "fá" QFruitList @@ -140,7 +141,7 @@ def QNo(node: Node, params: QueryStateDict, result: Result): def QNumOfFruit(node: Node, params: QueryStateDict, result: Result): if "queryfruits" not in result: - result.queryfruits = {} + result.queryfruits = dict() if "fruitnumber" not in result: result.queryfruits[result.fruit] = 1 else: @@ -163,7 +164,10 @@ def QFruit(node: Node, params: QueryStateDict, result: Result): def updateClientData(query: Query, fruitStateManager: DialogueStateManager): query.set_client_data( - _DIALOGUE_NAME, {"state": DialogueStateManager.serialize(fruitStateManager)} + "dialogue", { + "in_dialogue": _DIALOGUE_NAME, + "state": DialogueStateManager.serialize(fruitStateManager) + } ) @@ -177,13 +181,11 @@ def sentence(state: QueryStateDict, result: Result) -> None: if qt != _START_CONVERSATION_QTYPE and not ( dialogue_state and dialogue_state.get("in_dialogue") == _DIALOGUE_NAME ): - print("User not in dialogue") q.set_error("E_QUERY_NOT_UNDERSTOOD") return # Successfully matched a query type try: - print("Qtypy: ", result.qtype) if result.qtype == _START_CONVERSATION_QTYPE: fruitStateManager = DialogueStateManager() fruitStateManager.initialize_resources(_START_CONVERSATION_QTYPE) @@ -192,20 +194,18 @@ def sentence(state: QueryStateDict, result: Result) -> None: ) else: if dialogue_state is None: - print("Dialogue state is none") q.set_error("E_QUERY_NOT_UNDERSTOOD") return - print(dialogue_state) fruitmanager_serialized: Optional[str] = cast( Optional[str], dialogue_state.get("state") ) if not fruitmanager_serialized: - print("Fruitmanager not serialized") q.set_error("E_QUERY_NOT_UNDERSTOOD") return fruitStateManager = DialogueStateManager.deserialize( fruitmanager_serialized ) + fruitStateManager.stateMethod(result.qtype, result) updateClientData(q, fruitStateManager) From de5af6b73645d61b9126b8c2a8679737cf7b8367 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Wed, 22 Jun 2022 09:58:33 +0000 Subject: [PATCH 046/371] generate_answer moved into FruitState to make it more general --- queries/fruit_seller/fruitstate.py | 76 +++++++++++++++--------------- queries/fruit_seller/resource.py | 16 +------ queries/fruitseller.py | 8 ++-- 3 files changed, 46 insertions(+), 54 deletions(-) diff --git a/queries/fruit_seller/fruitstate.py b/queries/fruit_seller/fruitstate.py index df30424e..84110cba 100644 --- a/queries/fruit_seller/fruitstate.py +++ b/queries/fruit_seller/fruitstate.py @@ -32,41 +32,11 @@ def initialize_resources(self, dialogue: str) -> None: self.resources.append(OrderReceivedState(required=True)) self.updateState(dialogue) - def generateAnswer(self, type: str) -> None: - # TODO: Move answers into each state and get the answer here - if type == "QFruitStartQuery": - self.ans = "Hvaða ávexti má bjóða þér?" - elif type == "ListFruit": - if self.resourceState is not None: - if len(self.resourceState.data) != 0: - self.ans = "Komið! Pöntunin samanstendur af " - self.ans += _list_items(self.resourceState.data) - self.ans += ". Var það eitthvað fleira?" - else: - self.ans = "Komið! Karfan er núna tóm. Hvaða ávexti má bjóða þér?" - else: - self.ans = "Kom upp villa, reyndu aftur." - - elif type == "FruitOrderNotFinished": - self.ans = "Hverju viltu að bæta við pöntunina?" - elif type == "FruitsFulfilled": - self.ans = "Frábært! Á ég að staðfesta pöntunina?" - elif type == "FruitMethodNotFound": - self.ans = "Ég get ekki tekið við þessari beiðni strax." - elif type == "OrderComplete": - self.ans = "Frábært, pöntunin er staðfest!" - elif type == "OrderWrong": - self.ans = "Leitt að heyra, viltu hætta við pöntunina eða breyta henni?" - elif type == "CancelOrder": - self.ans = "Móttekið. Hætti við pöntunina." - elif type == "FruitOptions": - self.ans = "Hægt er að panta appelsínur, banana, epli og perur." - elif type == "FruitRemoved": - self.ans = "Karfan hefur verið uppfærð. Var það eitthvað fleira?" - elif type == "NoFruitMatched": - self.ans = "Enginn ávöxtur í körfunni passaði við beiðnina á undan." - elif type == "NoFruitToRemove": - self.ans = "Engir ávextir eru í körfunni til að fjarlægja." + def generate_answer(self, type: str) -> None: + if self.resourceState is not None: + self.ans = self.resourceState.generate_answer(type) + else: + self.ans = "Kom upp villa, reyndu aftur." def updateState(self, type: str) -> None: for resource in self.resources: @@ -88,7 +58,7 @@ def updateState(self, type: str) -> None: resource.data, resource.partiallyFulfilled, resource.fulfilled ) break - self.generateAnswer(type) + self.generate_answer(type) print("Current state: ", self.resourceState.state) def stateMethod(self, methodName: str, result: Result): @@ -121,7 +91,39 @@ class FruitState(Resource): def __init__(self, required: bool = True): super().__init__(required) - # def generate_answer + def generate_answer(self, type: str) -> str: + ans: str = "" + if type == "QFruitStartQuery": + ans = "Hvaða ávexti má bjóða þér?" + elif type == "ListFruit": + if len(self.data) != 0: + ans = "Komið! Pöntunin samanstendur af " + ans += _list_items(self.data) + ans += ". Var það eitthvað fleira?" + else: + ans = "Komið! Karfan er núna tóm. Hvaða ávexti má bjóða þér?" + + elif type == "FruitOrderNotFinished": + ans = "Hverju viltu að bæta við pöntunina?" + elif type == "FruitsFulfilled": + ans = "Frábært! Á ég að staðfesta pöntunina?" + elif type == "FruitMethodNotFound": + self.ans = "Ég get ekki tekið við þessari beiðni strax." + elif type == "OrderComplete": + ans = "Frábært, pöntunin er staðfest!" + elif type == "OrderWrong": + ans = "Leitt að heyra, viltu hætta við pöntunina eða breyta henni?" + elif type == "CancelOrder": + ans = "Móttekið. Hætti við pöntunina." + elif type == "FruitOptions": + ans = "Hægt er að panta appelsínur, banana, epli og perur." + elif type == "FruitRemoved": + ans = "Karfan hefur verið uppfærð. Var það eitthvað fleira?" + elif type == "NoFruitMatched": + ans = "Enginn ávöxtur í körfunni passaði við beiðnina á undan." + elif type == "NoFruitToRemove": + ans = "Engir ávextir eru í körfunni til að fjarlægja." + return ans class DataState: def __init__(self, data: Any, partiallyFulfilled: bool, fulfilled: bool): diff --git a/queries/fruit_seller/resource.py b/queries/fruit_seller/resource.py index 8edcd56c..f0fb987b 100644 --- a/queries/fruit_seller/resource.py +++ b/queries/fruit_seller/resource.py @@ -9,20 +9,8 @@ def __init__(self, required: bool = False): self.fulfilled: bool = False self.state = None - def isRequired(self) -> bool: - return self.required - - def getData(self) -> Any: - return self.data - - def isFulfilled(self) -> bool: - return self.fulfilled - - def setData(self, data: Any): - self.data = data - - def setFulfilled(self, fulfilled: bool): - self.fulfilled = fulfilled + def generate_answer(self, type: str) -> str: + return "" """ Three classes implemented for each resource diff --git a/queries/fruitseller.py b/queries/fruitseller.py index a5027317..2c0ceff1 100644 --- a/queries/fruitseller.py +++ b/queries/fruitseller.py @@ -90,9 +90,11 @@ QNumOfFruit → QNum? QFruit "og"? -QNum → 'einn' | 'tveir' | 'þrír' | 'fjórir' | "fimm" | "sex" | "sjö" - | "átta" | "níu" | "tíu" | "ellefu" | "tólf" | "þrettán" | "fjórtán" - | "fimmtán" | "sextán" | "sautján" | "átján" | "nítján" +QNum → + # to is a declinable number word ('tveir/tvo/tveim/tveggja') + # töl is an undeclinable number word ('sautján') + # tala is a number ('17') + to | töl | tala QFruit → 'banani' | 'epli' | 'pera' | 'appelsína' From 0ef14cfe3f55064783e8adbf202a01ca4040ca92 Mon Sep 17 00:00:00 2001 From: Logi E Date: Wed, 22 Jun 2022 10:01:53 +0000 Subject: [PATCH 047/371] modifications to resource classes, added sample pyyaml function --- queries/__init__.py | 10 ++++- queries/fruit_seller/fruitseller.yaml | 58 +++++++++++++------------- queries/fruit_seller/fruitstate.py | 18 ++++---- queries/fruit_seller/resource.py | 60 +++++++++++++++++++-------- queries/fruitseller.py | 9 ++-- query.py | 8 ++-- requirements.txt | 1 + 7 files changed, 101 insertions(+), 63 deletions(-) diff --git a/queries/__init__.py b/queries/__init__.py index 4e2ece21..c78b9964 100755 --- a/queries/__init__.py +++ b/queries/__init__.py @@ -44,6 +44,7 @@ import os import re import locale +import yaml from urllib.parse import urlencode from functools import lru_cache from xml.dom import minidom # type: ignore @@ -672,7 +673,14 @@ def read_jsfile(filename: str) -> str: fpath = os.path.join(basepath, "js", filename) with open(fpath, mode="r") as file: return cast(str, jsmin(file.read())) - + +def load_yaml_file(filename: str) -> Any: + basepath, _ = os.path.split(os.path.realpath(__file__)) + fpath = os.path.join(basepath, filename) + obj = None + with open(fpath, mode="r") as file: + obj = yaml.safe_load(file) + return obj def read_grammar_file(filename: str, **format_kwargs: str) -> str: """ diff --git a/queries/fruit_seller/fruitseller.yaml b/queries/fruit_seller/fruitseller.yaml index beca089e..c167aeaa 100644 --- a/queries/fruit_seller/fruitseller.yaml +++ b/queries/fruit_seller/fruitseller.yaml @@ -5,38 +5,40 @@ variables: resources: - Fruits: - type: "list" - required: true - verification_function: "check_fruits" prompt: "Hvaða ávexti má bjóða þér?" + resource_nonterminal: "QFruitList" + type: "list[str]" + required: true + repeatable: true + # verification_function: "check_fruits" + confirmation_prompt: "Pöntunin samanstendur af {curr_order}. Viltu staðfesta pöntunina?" + repeat_prompt: "Pöntunin samanstendur af {curr_order}. Verður það eitthvað fleira?" + next_states: + - QNo: #"verification_prompt" + - QYes: "Date" + - QNo: "repeat_prompt" + - QCancel: *Cancel - Date: type: "date" required: true verification_function: "check_date" prompt: "Hvenær viltu fá ávextina?" - - - - - - - -states: - - Start: - Dialogue Entry: true - NextState: - OnYes: TakeOrder - - TakeOrder: - Prompt: "Hvernig ávexti má bjóða þér?" - RepeatPrompt: "Pöntunin þín samanstendur af {curr_order}. Verður það eitthvað fleira?" - NextState: - OnYes: TakeOrder - OnNo: "OrderReceived" - Cancel: *Cancel - - ConfirmOrder: - Prompt: "Pöntunin þín er {curr_order}. Viltu staðfesta pöntunina?" - NextState: - OnYes: *Success - OnNo: "TakeOrder" - Cancel: *Cancel +# states: +# - Start: +# Dialogue Entry: true +# NextState: +# OnYes: TakeOrder +# - TakeOrder: +# Prompt: "Hvernig ávexti má bjóða þér?" +# RepeatPrompt: "Pöntunin þín samanstendur af {curr_order}. Verður það eitthvað fleira?" +# NextState: +# OnYes: TakeOrder +# OnNo: "OrderReceived" +# Cancel: *Cancel +# - ConfirmOrder: +# Prompt: "Pöntunin þín er {curr_order}. Viltu staðfesta pöntunina?" +# NextState: +# OnYes: *Success +# OnNo: "TakeOrder" +# Cancel: *Cancel diff --git a/queries/fruit_seller/fruitstate.py b/queries/fruit_seller/fruitstate.py index df30424e..66f58bc3 100644 --- a/queries/fruit_seller/fruitstate.py +++ b/queries/fruit_seller/fruitstate.py @@ -4,9 +4,9 @@ import base64 from tree import Result -from queries.fruit_seller.resource import Resource +from queries.fruit_seller.resource import ListResource, Resource, ResourceState from reynir import NounPhrase -from queries import natlang_seq, sing_or_plur +from queries import natlang_seq, sing_or_plur, load_yaml_file def _list_items(items: Any) -> str: @@ -27,9 +27,12 @@ def __init__(self): def initialize_resources(self, dialogue: str) -> None: # Order here is the priority of each resource + obj = load_yaml_file("fruit_seller/fruitseller.yaml") + # print(obj["resources"]) # TODO: parse yaml, add resources from yaml file - self.resources.append(FruitState(required=True)) - self.resources.append(OrderReceivedState(required=True)) + + self.resources.append(FruitState(prompt="Hvaða ávexti má bjóða þér?")) + self.resources.append(OrderReceivedState()) self.updateState(dialogue) def generateAnswer(self, type: str) -> None: @@ -70,7 +73,7 @@ def generateAnswer(self, type: str) -> None: def updateState(self, type: str) -> None: for resource in self.resources: - if resource.required and not resource.fulfilled: + if resource.required and resource.state is not ResourceState.FULFILLED: if resource.data is None: if self.resourceState is not resource: self.resourceState = resource @@ -117,10 +120,7 @@ def deserialize(cls, serialized: str) -> "DialogueStateManager": return pickle.loads(base64.b64decode(serialized.encode("utf-8"))) -class FruitState(Resource): - def __init__(self, required: bool = True): - super().__init__(required) - +class FruitState(ListResource): # def generate_answer class DataState: diff --git a/queries/fruit_seller/resource.py b/queries/fruit_seller/resource.py index 8edcd56c..3c542125 100644 --- a/queries/fruit_seller/resource.py +++ b/queries/fruit_seller/resource.py @@ -1,28 +1,54 @@ -from typing import Any +from typing import Any, Union, List, Optional +from enum import Enum, auto +from datetime import datetime +from dataclasses import dataclass +BaseResourceTypes = Union[str, int, float, bool, datetime, None] +ListResourceType = List[BaseResourceTypes] + + +class ResourceState(Enum): + UNFULFILLED = auto() + PARTIALLY_FULFILLED = auto() + FULFILLED = auto() + CONFIRMED = auto() + # SKIPPED = auto() + + +@dataclass class Resource: - def __init__(self, required: bool = False): - self.required = required - self.data: Any = None - self.partiallyFulfilled: bool = False - self.fulfilled: bool = False - self.state = None + required: bool = True + data: Any = None + state: ResourceState = ResourceState.UNFULFILLED + prompt: str = "" + repeat_prompt: Optional[str] = None + confirm_prompt: Optional[str] = None + cancel_prompt: Optional[str] = None + _repeat_count: int = 0 + + def next_action(self) -> Any: + raise NotImplementedError() + + +class ListResource(Resource): + data: ListResourceType = [] + available_options: Optional[ListResourceType] = None + + def list_available_options(self) -> str: + raise NotImplementedError() + - def isRequired(self) -> bool: - return self.required +class YesNoResource(Resource): + data: Optional[bool] = None - def getData(self) -> Any: - return self.data - def isFulfilled(self) -> bool: - return self.fulfilled +class DatetimeResource(Resource): + data: Optional[datetime] = None - def setData(self, data: Any): - self.data = data - def setFulfilled(self, fulfilled: bool): - self.fulfilled = fulfilled +class NumberResource(Resource): + data: Optional[int] = None """ Three classes implemented for each resource diff --git a/queries/fruitseller.py b/queries/fruitseller.py index a5027317..03831317 100644 --- a/queries/fruitseller.py +++ b/queries/fruitseller.py @@ -2,7 +2,7 @@ import logging -from query import Query, QueryStateDict +from query import DIALOGUE_DATA_KEY, Query, QueryStateDict from tree import Result, Node from queries import gen_answer, parse_num from queries.fruit_seller.fruitstate import DialogueStateManager @@ -164,10 +164,11 @@ def QFruit(node: Node, params: QueryStateDict, result: Result): def updateClientData(query: Query, fruitStateManager: DialogueStateManager): query.set_client_data( - "dialogue", { + DIALOGUE_DATA_KEY, + { "in_dialogue": _DIALOGUE_NAME, - "state": DialogueStateManager.serialize(fruitStateManager) - } + "state": DialogueStateManager.serialize(fruitStateManager), + }, ) diff --git a/query.py b/query.py index d67ec069..f3926256 100755 --- a/query.py +++ b/query.py @@ -141,7 +141,7 @@ def __call__(self, w: str, *, filter_func: Optional[BinFilterFunc] = None) -> st _IGNORED_PREFIX_RE = r"^({0})\s*".format("|".join(_IGNORED_QUERY_PREFIXES)) # Auto-capitalization corrections _CAPITALIZATION_REPLACEMENTS = (("í Dag", "í dag"),) -_DIALOGUE_DATA_KEY = "dialogue" +DIALOGUE_DATA_KEY = "dialogue" def beautify_query(query: str) -> str: @@ -895,18 +895,18 @@ def set_client_data(self, key: str, data: ClientDataDict) -> None: def get_dialogue_state(self) -> Optional[ClientDataDict]: """Fetch the dialogue state for a client""" - return self.client_data(_DIALOGUE_DATA_KEY) + return self.client_data(DIALOGUE_DATA_KEY) def start_dialogue(self, dialogue_name: str, state_manager_serialized: str) -> None: """Set the clients state to be in the given dialogue""" self.set_client_data( - _DIALOGUE_DATA_KEY, + DIALOGUE_DATA_KEY, {"in_dialogue": dialogue_name, "state": state_manager_serialized}, ) def end_dialogue(self) -> None: """End the client's current dialogue""" - self.set_client_data(_DIALOGUE_DATA_KEY, {}) + self.set_client_data(DIALOGUE_DATA_KEY, {}) @staticmethod def store_query_data(client_id: str, key: str, data: ClientDataDict) -> bool: diff --git a/requirements.txt b/requirements.txt index df47de7d..da473e9f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -26,4 +26,5 @@ rjsmin>=1.1.0 odfpy>=1.4.1 pdfminer.six>=20201018 python-youtube>=0.8.1 +PyYAML==6.0 azure-cognitiveservices-speech>=1.19.0 From cfac6edaf4bc9bef76584b4ed08f2f19c3561912 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Wed, 22 Jun 2022 11:37:07 +0000 Subject: [PATCH 048/371] Start of loading resources from yaml file --- queries/fruit_seller/fruitstate.py | 71 ++++++++++++++++-------------- queries/fruitseller.py | 4 +- 2 files changed, 40 insertions(+), 35 deletions(-) diff --git a/queries/fruit_seller/fruitstate.py b/queries/fruit_seller/fruitstate.py index 32f15809..8b5de870 100644 --- a/queries/fruit_seller/fruitstate.py +++ b/queries/fruit_seller/fruitstate.py @@ -2,9 +2,10 @@ import pickle import base64 +from Greynir.query import ClientDataDict from tree import Result -from queries.fruit_seller.resource import ListResource, Resource, ResourceState +from queries.fruit_seller.resource import DatetimeResource, ListResource, Resource, ResourceState from reynir import NounPhrase from queries import natlang_seq, sing_or_plur, load_yaml_file @@ -20,15 +21,29 @@ def _list_items(items: Any) -> str: class DialogueStateManager: - def __init__(self): + def __init__(self, yaml_file: str, client_data: Optional[ClientDataDict], result: Result): + obj = load_yaml_file(yaml_file) + print(obj) self.resources: List[Resource] = [] + for resource in obj["resources"]: + newResource: Resource + if resource["type"] == "list[str]": + newResource = ListResource() + else: # resource["type"] == "date" + newResource = DatetimeResource() + newResource.required = resource["required"] + newResource.prompt = resource["prompt"] + newResource.repeat_prompt = resource["repeat_prompt"] if resource["repeatable"] else None + newResource.confirm_prompt = resource["confirm_prompt"] + newResource.cancel_prompt = resource["cancel_prompt"] + self.resourceState: Optional[Resource] = None self.ans: Optional[str] = None def initialize_resources(self, dialogue: str) -> None: # Order here is the priority of each resource obj = load_yaml_file("fruit_seller/fruitseller.yaml") - # print(obj["resources"]) + print("Resources: ", obj["resources"]) # TODO: parse yaml, add resources from yaml file self.resources.append(FruitState(prompt="Hvaða ávexti má bjóða þér?")) @@ -47,19 +62,13 @@ def updateState(self, type: str) -> None: if resource.data is None: if self.resourceState is not resource: self.resourceState = resource - resource.state = resource.DataState( - resource.data, resource.partiallyFulfilled, resource.fulfilled - ) + resource.state = resource.DataState(resource.data) break - elif not resource.partiallyFulfilled: - resource.state = resource.PartiallyFulfilledState( - resource.data, resource.partiallyFulfilled, resource.fulfilled - ) + elif resource.state is not ResourceState.PARTIALLY_FULFILLED: + resource.state = resource.PartiallyFulfilledState(resource.data) break - elif resource.partiallyFulfilled: - resource.state = resource.FulfilledState( - resource.data, resource.partiallyFulfilled, resource.fulfilled - ) + elif resource.state is ResourceState.PARTIALLY_FULFILLED: + resource.state = resource.FulfilledState(resource.data) break self.generate_answer(type) print("Current state: ", self.resourceState.state) @@ -71,8 +80,7 @@ def stateMethod(self, methodName: str, result: Result): if method is not None: ( self.resourceState.data, - self.resourceState.partiallyFulfilled, - self.resourceState.fulfilled, + self.resourceState.state ) = method(result) self.updateState(result.qtype) else: @@ -127,10 +135,8 @@ def generate_answer(self, type: str) -> str: return ans class DataState: - def __init__(self, data: Any, partiallyFulfilled: bool, fulfilled: bool): + def __init__(self, data: Any): self.data = data - self.partiallyFulfilled = partiallyFulfilled - self.fulfilled = fulfilled # Add fruits to array and switch to OrderReceived state def QAddFruitQuery(self, result: Result): @@ -141,7 +147,7 @@ def QAddFruitQuery(self, result: Result): self.data[fruitname] = result.queryfruits[fruitname] result.fruits = self.data result.qtype = "ListFruit" - return (self.data, self.partiallyFulfilled, self.fulfilled) + return (self.data, ResourceState.PARTIALLY_FULFILLED) # Remove fruits from array def QRemoveFruitQuery(self, result: Result): @@ -156,7 +162,7 @@ def QRemoveFruitQuery(self, result: Result): if result.qtype != "NoFruitMatched": result.qtype = "ListFruit" result.fruits = self.data - return (self.data, self.partiallyFulfilled, self.fulfilled) + return (self.data, ResourceState.PARTIALLY_FULFILLED if len(self.data) != 0 else ResourceState.UNFULFILLED) # Change the fruits array def QChangeFruitQuery(self, result: Result): @@ -165,43 +171,40 @@ def QChangeFruitQuery(self, result: Result): # Inform what fruits are available def QFruitOptionsQuery(self, result: Result): result.qtype = "FruitOptions" - return (self.data, self.partiallyFulfilled, self.fulfilled) + return (self.data, ResourceState.UNFULFILLED if self.data is None else ResourceState.PARTIALLY_FULFILLED) # User wants to stop conversation def QCancelOrder(self, result: Result): result.qtype = "CancelOrder" - return (self.data, self.partiallyFulfilled, self.fulfilled) + return (self.data, ResourceState.UNFULFILLED) class PartiallyFulfilledState(DataState): - def __init__(self, data: Any, partiallyFulfilled: bool, fulfilled: bool): - super().__init__(data, partiallyFulfilled, fulfilled) + def __init__(self, data: Any): + super().__init__(data) # User is happy with the order, switch to confirm state def QNo(self, result: Result): result.qtype = "FruitsFulfilled" - self.partiallyFulfilled = True - return (self.data, self.partiallyFulfilled, self.fulfilled) + return (self.data, ResourceState.FULFILLED) # User wants to add more to the order, ask what def QYes(self, result: Result): result.qtype = "FruitOrderNotFinished" - return (self.data, self.partiallyFulfilled, self.fulfilled) + return (self.data, ResourceState.PARTIALLY_FULFILLED) class FulfilledState(DataState): - def __init__(self, data: Any, partiallyFulfilled: bool, fulfilled: bool): - super().__init__(data, partiallyFulfilled, fulfilled) + def __init__(self, data: Any): + super().__init__(data) # The order is correct, say the order is confirmed def QYes(self, result: Result): result.qtype = "OrderComplete" - self.fulfilled = True - return (self.data, self.partiallyFulfilled, self.fulfilled) + return (self.data, ResourceState.CONFIRMED) # Order was wrong, ask the user to start again def QNo(self, result: Result): result.qtype = "OrderWrong" - self.partiallyFulfilled = False - return (self.data, self.partiallyFulfilled, self.fulfilled) + return (self.data, ResourceState.FULFILLED) class OrderReceivedState(FruitState): diff --git a/queries/fruitseller.py b/queries/fruitseller.py index 1029e860..010f9a2a 100644 --- a/queries/fruitseller.py +++ b/queries/fruitseller.py @@ -187,10 +187,12 @@ def sentence(state: QueryStateDict, result: Result) -> None: q.set_error("E_QUERY_NOT_UNDERSTOOD") return + dsm = DialogueStateManager("fruit_seller/fruitseller.yaml", q.client_data(DIALOGUE_DATA_KEY), result) + answer = dsm.generate_answer() # Successfully matched a query type try: if result.qtype == _START_CONVERSATION_QTYPE: - fruitStateManager = DialogueStateManager() + fruitStateManager = DialogueStateManager("fruit_seller/fruitseller.yaml", q.client_data(DIALOGUE_DATA_KEY), result) fruitStateManager.initialize_resources(_START_CONVERSATION_QTYPE) q.start_dialogue( _DIALOGUE_NAME, DialogueStateManager.serialize(fruitStateManager) From 36af806e2e5052054087c105415c683d23fa3cd9 Mon Sep 17 00:00:00 2001 From: Logi E Date: Wed, 22 Jun 2022 11:59:04 +0000 Subject: [PATCH 049/371] =?UTF-8?q?man=20ekki=20alveg=20hva=C3=B0=20eg=20g?= =?UTF-8?q?er=C3=B0i,=20en=20er=20a=C3=B0=20ey=C3=B0ileggja=20get/set=20di?= =?UTF-8?q?alogue=20f=C3=B6ll=20=C3=AD=20query.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- queries/__init__.py | 47 ++++++++++++++++++--- queries/fruit_seller/fruitseller.yaml | 54 ++++++------------------ queries/fruit_seller/fruitstate.py | 61 +++++++++++++++++---------- queries/fruit_seller/resource.py | 7 +++ queries/fruitseller.py | 22 ++++++---- query.py | 13 +++--- 6 files changed, 120 insertions(+), 84 deletions(-) diff --git a/queries/__init__.py b/queries/__init__.py index c78b9964..19480c41 100755 --- a/queries/__init__.py +++ b/queries/__init__.py @@ -36,6 +36,7 @@ Union, cast, ) +from typing_extensions import TypedDict from tree import Node import logging @@ -674,13 +675,6 @@ def read_jsfile(filename: str) -> str: with open(fpath, mode="r") as file: return cast(str, jsmin(file.read())) -def load_yaml_file(filename: str) -> Any: - basepath, _ = os.path.split(os.path.realpath(__file__)) - fpath = os.path.join(basepath, filename) - obj = None - with open(fpath, mode="r") as file: - obj = yaml.safe_load(file) - return obj def read_grammar_file(filename: str, **format_kwargs: str) -> str: """ @@ -712,3 +706,42 @@ def join_grammar_files(folder: str) -> str: with open(os.path.join(fpath, fname), mode="r") as file: grammar.append(file.read()) return "\n".join(grammar) + + +class ResourceType(TypedDict, total=False): + """ + Representation of a single resource in a dialogue. + """ + + prompt: str + type: str + repeat_prompt: Optional[str] + required: bool + repeatable: bool + # verification_function: "check_fruits" + confirm_prompt: Optional[str] + # next_states: List[Any] + # - QNo: #"verification_prompt" + # - QYes: "Date" + # - QNo: "repeat_prompt" + # - QCancel: *Cancel + + +class DialogueStructureType(TypedDict): + """ + A dialogue structure is a list of resources used in a dialogue. + """ + + dialogue_name: str + variables: Optional[List[Any]] + resources: List[ResourceType] + + +def load_dialogue_structure(filename: str) -> DialogueStructureType: + """Loads dialogue structure from YAML file.""" + basepath, _ = os.path.split(os.path.realpath(__file__)) + fpath = os.path.join(basepath, filename) + obj = None + with open(fpath, mode="r") as file: + obj = yaml.safe_load(file) + return obj diff --git a/queries/fruit_seller/fruitseller.yaml b/queries/fruit_seller/fruitseller.yaml index c167aeaa..38fb8202 100644 --- a/queries/fruit_seller/fruitseller.yaml +++ b/queries/fruit_seller/fruitseller.yaml @@ -1,44 +1,18 @@ -# Example YAML file for fruitseller dialogue +dialogue_name: "fruitseller" variables: - &Success 1 - &Cancel -1 - resources: - - Fruits: - prompt: "Hvaða ávexti má bjóða þér?" - resource_nonterminal: "QFruitList" - type: "list[str]" - required: true - repeatable: true - # verification_function: "check_fruits" - confirmation_prompt: "Pöntunin samanstendur af {curr_order}. Viltu staðfesta pöntunina?" - repeat_prompt: "Pöntunin samanstendur af {curr_order}. Verður það eitthvað fleira?" - next_states: - - QNo: #"verification_prompt" - - QYes: "Date" - - QNo: "repeat_prompt" - - QCancel: *Cancel - - Date: - type: "date" - required: true - verification_function: "check_date" - prompt: "Hvenær viltu fá ávextina?" - -# states: -# - Start: -# Dialogue Entry: true -# NextState: -# OnYes: TakeOrder -# - TakeOrder: -# Prompt: "Hvernig ávexti má bjóða þér?" -# RepeatPrompt: "Pöntunin þín samanstendur af {curr_order}. Verður það eitthvað fleira?" -# NextState: -# OnYes: TakeOrder -# OnNo: "OrderReceived" -# Cancel: *Cancel -# - ConfirmOrder: -# Prompt: "Pöntunin þín er {curr_order}. Viltu staðfesta pöntunina?" -# NextState: -# OnYes: *Success -# OnNo: "TakeOrder" -# Cancel: *Cancel + - name: "Fruits" + prompt: "Hvaða ávexti má bjóða þér?" + # resource_nonterminal: "QFruitList" + type: "ListResource" + repeatable: true + # verification_function: "check_fruits" + confirm_prompt: "Pöntunin samanstendur af {curr_order}. Viltu staðfesta pöntunina?" + repeat_prompt: "Pöntunin samanstendur af {curr_order}. Verður það eitthvað fleira?" + - name: "Date" + type: "DatetimeResource" + # verification_function: "check_date" + prompt: "Hvenær viltu fá ávextina?" + confirm_prompt: "Afhending pöntunar er {dt}. Viltu staðfesta afhendingartímann?" diff --git a/queries/fruit_seller/fruitstate.py b/queries/fruit_seller/fruitstate.py index 8b5de870..73adad0d 100644 --- a/queries/fruit_seller/fruitstate.py +++ b/queries/fruit_seller/fruitstate.py @@ -2,12 +2,17 @@ import pickle import base64 -from Greynir.query import ClientDataDict from tree import Result -from queries.fruit_seller.resource import DatetimeResource, ListResource, Resource, ResourceState +from query import DialogueStructureType +from queries.fruit_seller.resource import ( + DatetimeResource, + ListResource, + Resource, + ResourceState, +) from reynir import NounPhrase -from queries import natlang_seq, sing_or_plur, load_yaml_file +from queries import natlang_seq, sing_or_plur, load_dialogue_structure def _list_items(items: Any) -> str: @@ -21,29 +26,35 @@ def _list_items(items: Any) -> str: class DialogueStateManager: - def __init__(self, yaml_file: str, client_data: Optional[ClientDataDict], result: Result): - obj = load_yaml_file(yaml_file) + def __init__( + self, yaml_file: str, saved_state: DialogueStructureType, result: Result + ): + obj = load_dialogue_structure(yaml_file) print(obj) self.resources: List[Resource] = [] for resource in obj["resources"]: newResource: Resource - if resource["type"] == "list[str]": - newResource = ListResource() - else: # resource["type"] == "date" - newResource = DatetimeResource() - newResource.required = resource["required"] - newResource.prompt = resource["prompt"] - newResource.repeat_prompt = resource["repeat_prompt"] if resource["repeatable"] else None - newResource.confirm_prompt = resource["confirm_prompt"] - newResource.cancel_prompt = resource["cancel_prompt"] + print(resource) + if resource.get("type") == "ListResource": + newResource = ListResource(**resource) + else: # resource["type"] == "date" + newResource = DatetimeResource(**resource) + print(resource["type"]) + print(newResource) + # newResource.required = resource["required"] + # newResource.prompt = resource["prompt"] + # newResource.repeat_prompt = resource["repeat_prompt"] if resource["repeatable"] else None + # newResource.confirm_prompt = resource["confirm_prompt"] + # newResource.cancel_prompt = resource["cancel_prompt"] self.resourceState: Optional[Resource] = None self.ans: Optional[str] = None def initialize_resources(self, dialogue: str) -> None: # Order here is the priority of each resource - obj = load_yaml_file("fruit_seller/fruitseller.yaml") + obj = load_dialogue_structure("fruit_seller/fruitseller.yaml") print("Resources: ", obj["resources"]) + # print(obj["resources"]) # TODO: parse yaml, add resources from yaml file self.resources.append(FruitState(prompt="Hvaða ávexti má bjóða þér?")) @@ -78,10 +89,7 @@ def stateMethod(self, methodName: str, result: Result): if self.resourceState is not None: method = getattr(self.resourceState.state, methodName) if method is not None: - ( - self.resourceState.data, - self.resourceState.state - ) = method(result) + (self.resourceState.data, self.resourceState.state) = method(result) self.updateState(result.qtype) else: self.ans = "Kom upp villa, reyndu aftur." @@ -99,7 +107,6 @@ def deserialize(cls, serialized: str) -> "DialogueStateManager": class FruitState(ListResource): - def generate_answer(self, type: str) -> str: ans: str = "" if type == "QFruitStartQuery": @@ -162,7 +169,12 @@ def QRemoveFruitQuery(self, result: Result): if result.qtype != "NoFruitMatched": result.qtype = "ListFruit" result.fruits = self.data - return (self.data, ResourceState.PARTIALLY_FULFILLED if len(self.data) != 0 else ResourceState.UNFULFILLED) + return ( + self.data, + ResourceState.PARTIALLY_FULFILLED + if len(self.data) != 0 + else ResourceState.UNFULFILLED, + ) # Change the fruits array def QChangeFruitQuery(self, result: Result): @@ -171,7 +183,12 @@ def QChangeFruitQuery(self, result: Result): # Inform what fruits are available def QFruitOptionsQuery(self, result: Result): result.qtype = "FruitOptions" - return (self.data, ResourceState.UNFULFILLED if self.data is None else ResourceState.PARTIALLY_FULFILLED) + return ( + self.data, + ResourceState.UNFULFILLED + if self.data is None + else ResourceState.PARTIALLY_FULFILLED, + ) # User wants to stop conversation def QCancelOrder(self, result: Result): diff --git a/queries/fruit_seller/resource.py b/queries/fruit_seller/resource.py index b5feb6c9..0316432d 100644 --- a/queries/fruit_seller/resource.py +++ b/queries/fruit_seller/resource.py @@ -18,10 +18,13 @@ class ResourceState(Enum): @dataclass class Resource: + name: str = "" required: bool = True data: Any = None state: ResourceState = ResourceState.UNFULFILLED prompt: str = "" + type: str = "" + repeatable: bool = False repeat_prompt: Optional[str] = None confirm_prompt: Optional[str] = None cancel_prompt: Optional[str] = None @@ -41,6 +44,10 @@ class ListResource(Resource): def list_available_options(self) -> str: raise NotImplementedError() +# TODO: +# ExactlyOneResource (choose one resource from options) +# SetResource (a set of resources)? +# ... class YesNoResource(Resource): data: Optional[bool] = None diff --git a/queries/fruitseller.py b/queries/fruitseller.py index 010f9a2a..bb365a5d 100644 --- a/queries/fruitseller.py +++ b/queries/fruitseller.py @@ -4,7 +4,7 @@ from query import DIALOGUE_DATA_KEY, Query, QueryStateDict from tree import Result, Node -from queries import gen_answer, parse_num +from queries import DialogueStructureType, gen_answer, parse_num from queries.fruit_seller.fruitstate import DialogueStateManager # Indicate that this module wants to handle parse trees for queries, @@ -108,7 +108,6 @@ """ -# fruitStateManager = FruitStateManager() _START_CONVERSATION_QTYPE = "QFruitStartQuery" _DIALOGUE_NAME = "fruit_seller" @@ -167,10 +166,6 @@ def QFruit(node: Node, params: QueryStateDict, result: Result): def updateClientData(query: Query, fruitStateManager: DialogueStateManager): query.set_client_data( DIALOGUE_DATA_KEY, - { - "in_dialogue": _DIALOGUE_NAME, - "state": DialogueStateManager.serialize(fruitStateManager), - }, ) @@ -187,12 +182,21 @@ def sentence(state: QueryStateDict, result: Result) -> None: q.set_error("E_QUERY_NOT_UNDERSTOOD") return - dsm = DialogueStateManager("fruit_seller/fruitseller.yaml", q.client_data(DIALOGUE_DATA_KEY), result) - answer = dsm.generate_answer() + dsm = DialogueStateManager( + "fruit_seller/fruitseller.yaml", + cast(DialogueStructureType, q.client_data(DIALOGUE_DATA_KEY) or {}), + result, + ) + + # answer = dsm.generate_answer() # Successfully matched a query type try: if result.qtype == _START_CONVERSATION_QTYPE: - fruitStateManager = DialogueStateManager("fruit_seller/fruitseller.yaml", q.client_data(DIALOGUE_DATA_KEY), result) + fruitStateManager = DialogueStateManager( + "fruit_seller/fruitseller.yaml", + q.client_data(DIALOGUE_DATA_KEY), + result, + ) fruitStateManager.initialize_resources(_START_CONVERSATION_QTYPE) q.start_dialogue( _DIALOGUE_NAME, DialogueStateManager.serialize(fruitStateManager) diff --git a/query.py b/query.py index f3926256..b85364cd 100755 --- a/query.py +++ b/query.py @@ -74,6 +74,7 @@ from islenska.bindb import BinFilterFunc from tree import Tree, TreeStateDict, Node +from queries import DialogueStructureType # from nertokenizer import recognize_entities from images import get_image_url @@ -89,7 +90,9 @@ ContextDict = Dict[str, Any] # Client data -ClientDataDict = Dict[str, Union[str, int, float, bool, Dict[str, str]]] +ClientDataDict = Union[ + Dict[str, Union[str, int, float, bool, Dict[str, str]]], DialogueStructureType +] # Answer tuple (corresponds to parameter list of Query.set_answer()) AnswerTuple = Tuple[ResponseType, str, Optional[str]] @@ -897,15 +900,13 @@ def get_dialogue_state(self) -> Optional[ClientDataDict]: """Fetch the dialogue state for a client""" return self.client_data(DIALOGUE_DATA_KEY) - def start_dialogue(self, dialogue_name: str, state_manager_serialized: str) -> None: + def start_dialogue(self, ds: DialogueStructureType) -> None: """Set the clients state to be in the given dialogue""" - self.set_client_data( - DIALOGUE_DATA_KEY, - {"in_dialogue": dialogue_name, "state": state_manager_serialized}, - ) + self.set_client_data(DIALOGUE_DATA_KEY, ds) def end_dialogue(self) -> None: """End the client's current dialogue""" + # TODO: Remove line from database? self.set_client_data(DIALOGUE_DATA_KEY, {}) @staticmethod From c9b6aa297489b3dc81c3de04059cd121b0aa4c73 Mon Sep 17 00:00:00 2001 From: Logi E Date: Wed, 22 Jun 2022 12:45:25 +0000 Subject: [PATCH 050/371] working on dialoguestatemanager functions --- queries/fruit_seller/fruitstate.py | 14 ++++---------- queries/fruitseller.py | 26 +++----------------------- query.py | 10 +++++----- 3 files changed, 12 insertions(+), 38 deletions(-) diff --git a/queries/fruit_seller/fruitstate.py b/queries/fruit_seller/fruitstate.py index 73adad0d..e6ec1f20 100644 --- a/queries/fruit_seller/fruitstate.py +++ b/queries/fruit_seller/fruitstate.py @@ -27,25 +27,19 @@ def _list_items(items: Any) -> str: class DialogueStateManager: def __init__( - self, yaml_file: str, saved_state: DialogueStructureType, result: Result + self, yaml_file: str, saved_state: Optional[DialogueStructureType] = None ): obj = load_dialogue_structure(yaml_file) print(obj) self.resources: List[Resource] = [] for resource in obj["resources"]: newResource: Resource - print(resource) if resource.get("type") == "ListResource": newResource = ListResource(**resource) - else: # resource["type"] == "date" + else: newResource = DatetimeResource(**resource) - print(resource["type"]) - print(newResource) - # newResource.required = resource["required"] - # newResource.prompt = resource["prompt"] - # newResource.repeat_prompt = resource["repeat_prompt"] if resource["repeatable"] else None - # newResource.confirm_prompt = resource["confirm_prompt"] - # newResource.cancel_prompt = resource["cancel_prompt"] + # newResource.__dict__.update(saved_state) + self.resources.append(newResource) self.resourceState: Optional[Resource] = None self.ans: Optional[str] = None diff --git a/queries/fruitseller.py b/queries/fruitseller.py index bb365a5d..3784d426 100644 --- a/queries/fruitseller.py +++ b/queries/fruitseller.py @@ -163,12 +163,6 @@ def QFruit(node: Node, params: QueryStateDict, result: Result): result.fruit = fruit -def updateClientData(query: Query, fruitStateManager: DialogueStateManager): - query.set_client_data( - DIALOGUE_DATA_KEY, - ) - - def sentence(state: QueryStateDict, result: Result) -> None: """Called when sentence processing is complete""" q: Query = state["query"] @@ -182,25 +176,12 @@ def sentence(state: QueryStateDict, result: Result) -> None: q.set_error("E_QUERY_NOT_UNDERSTOOD") return - dsm = DialogueStateManager( - "fruit_seller/fruitseller.yaml", - cast(DialogueStructureType, q.client_data(DIALOGUE_DATA_KEY) or {}), - result, - ) + dsm = DialogueStateManager("fruit_seller/fruitseller.yaml", q.get_dialogue_state()) - # answer = dsm.generate_answer() # Successfully matched a query type try: if result.qtype == _START_CONVERSATION_QTYPE: - fruitStateManager = DialogueStateManager( - "fruit_seller/fruitseller.yaml", - q.client_data(DIALOGUE_DATA_KEY), - result, - ) - fruitStateManager.initialize_resources(_START_CONVERSATION_QTYPE) - q.start_dialogue( - _DIALOGUE_NAME, DialogueStateManager.serialize(fruitStateManager) - ) + q.set_dialogue_state() else: if dialogue_state is None: q.set_error("E_QUERY_NOT_UNDERSTOOD") @@ -217,8 +198,7 @@ def sentence(state: QueryStateDict, result: Result) -> None: fruitStateManager.stateMethod(result.qtype, result) - updateClientData(q, fruitStateManager) - ans = fruitStateManager.ans + ans = fruitStateManager.generate_answer() if result.qtype == "OrderComplete" or result.qtype == "CancelOrder": q.end_dialogue() diff --git a/query.py b/query.py index b85364cd..4b5ffd7f 100755 --- a/query.py +++ b/query.py @@ -896,12 +896,12 @@ def set_client_data(self, key: str, data: ClientDataDict) -> None: return Query.store_query_data(self.client_id, key, data) - def get_dialogue_state(self) -> Optional[ClientDataDict]: - """Fetch the dialogue state for a client""" - return self.client_data(DIALOGUE_DATA_KEY) + def get_dialogue_state(self) -> Optional[DialogueStructureType]: + """Load the dialogue state for a client""" + return cast(DialogueStructureType, self.client_data(DIALOGUE_DATA_KEY)) - def start_dialogue(self, ds: DialogueStructureType) -> None: - """Set the clients state to be in the given dialogue""" + def set_dialogue_state(self, ds: DialogueStructureType) -> None: + """Save the state of a dialogue for a client""" self.set_client_data(DIALOGUE_DATA_KEY, ds) def end_dialogue(self) -> None: From 149846d0f69dbcb584855d9ed7671bdec5c57905 Mon Sep 17 00:00:00 2001 From: Logi E Date: Wed, 22 Jun 2022 12:53:01 +0000 Subject: [PATCH 051/371] added update function to Resource --- queries/__init__.py | 5 ++++- queries/fruit_seller/fruitseller.yaml | 1 + queries/fruit_seller/fruitstate.py | 5 +++-- queries/fruit_seller/resource.py | 5 +++++ 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/queries/__init__.py b/queries/__init__.py index 19480c41..168616b3 100755 --- a/queries/__init__.py +++ b/queries/__init__.py @@ -713,6 +713,7 @@ class ResourceType(TypedDict, total=False): Representation of a single resource in a dialogue. """ + name: str prompt: str type: str repeat_prompt: Optional[str] @@ -720,13 +721,15 @@ class ResourceType(TypedDict, total=False): repeatable: bool # verification_function: "check_fruits" confirm_prompt: Optional[str] + data: Any + # state: int # TODO: wrong type? + cancel_prompt: Optional[str] # next_states: List[Any] # - QNo: #"verification_prompt" # - QYes: "Date" # - QNo: "repeat_prompt" # - QCancel: *Cancel - class DialogueStructureType(TypedDict): """ A dialogue structure is a list of resources used in a dialogue. diff --git a/queries/fruit_seller/fruitseller.yaml b/queries/fruit_seller/fruitseller.yaml index 38fb8202..a62870a2 100644 --- a/queries/fruit_seller/fruitseller.yaml +++ b/queries/fruit_seller/fruitseller.yaml @@ -2,6 +2,7 @@ dialogue_name: "fruitseller" variables: - &Success 1 - &Cancel -1 +# Resources, in order resources: - name: "Fruits" prompt: "Hvaða ávexti má bjóða þér?" diff --git a/queries/fruit_seller/fruitstate.py b/queries/fruit_seller/fruitstate.py index e6ec1f20..118ff195 100644 --- a/queries/fruit_seller/fruitstate.py +++ b/queries/fruit_seller/fruitstate.py @@ -32,13 +32,14 @@ def __init__( obj = load_dialogue_structure(yaml_file) print(obj) self.resources: List[Resource] = [] - for resource in obj["resources"]: + for i, resource in enumerate(obj["resources"]): newResource: Resource if resource.get("type") == "ListResource": newResource = ListResource(**resource) else: newResource = DatetimeResource(**resource) - # newResource.__dict__.update(saved_state) + if saved_state and i < len(saved_state["resources"]): + newResource.update(saved_state["resources"][i]) self.resources.append(newResource) self.resourceState: Optional[Resource] = None diff --git a/queries/fruit_seller/resource.py b/queries/fruit_seller/resource.py index 0316432d..dbfc75c9 100644 --- a/queries/fruit_seller/resource.py +++ b/queries/fruit_seller/resource.py @@ -4,6 +4,8 @@ from datetime import datetime from dataclasses import dataclass +from queries import ResourceType + BaseResourceTypes = Union[str, int, float, bool, datetime, None] ListResourceType = List[BaseResourceTypes] @@ -36,6 +38,9 @@ def next_action(self) -> Any: def generate_answer(self, type: str) -> str: raise NotImplementedError() + def update(self, new_data: Optional[ResourceType]) -> None: + if new_data: + self.__dict__.update(new_data) class ListResource(Resource): data: ListResourceType = [] From e47e7734b7a3a05f2d445ed3ee07978a767d8d51 Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Wed, 22 Jun 2022 14:51:49 +0000 Subject: [PATCH 052/371] Smartthings updated Smartthings updated --- queries/iot_connect.py | 1 + queries/iot_hue.py | 1 + .../IoT_Embla/Smart_Things/fuse_search_st.js | 32 + queries/js/IoT_Embla/Smart_Things/st.html | 4 + queries/js/IoT_Embla/Smart_Things/st.js | 15 +- .../IoT_Embla/Smart_Things/st_connecthub.js | 3 +- queries/js/IoT_Embla/Smart_Things/test.js | 24426 ++++++++++++++++ queries/js/IoT_Embla/Sonos/connect_sonos.js | 0 8 files changed, 24478 insertions(+), 4 deletions(-) create mode 100644 queries/js/IoT_Embla/Smart_Things/fuse_search_st.js create mode 100644 queries/js/IoT_Embla/Smart_Things/test.js create mode 100644 queries/js/IoT_Embla/Sonos/connect_sonos.js diff --git a/queries/iot_connect.py b/queries/iot_connect.py index 471c3b4b..f5511b45 100644 --- a/queries/iot_connect.py +++ b/queries/iot_connect.py @@ -61,6 +61,7 @@ def help_text(lemma: str) -> str: ( "Tengu miðstöðina", "Tengdu ljósin" + "Tengdu hátalarann" ) ) ) diff --git a/queries/iot_hue.py b/queries/iot_hue.py index 928f7cc1..c9090704 100755 --- a/queries/iot_hue.py +++ b/queries/iot_hue.py @@ -597,6 +597,7 @@ def sentence(state: QueryStateDict, result: Result) -> None: # Fetch relevant data from the device_data table to perform an action on the lights device_data = cast(Optional[DeviceData], q.client_data(smartdevice_type)) + print("location :", q.location) print("device data :", device_data) selected_light: Optional[str] = None diff --git a/queries/js/IoT_Embla/Smart_Things/fuse_search_st.js b/queries/js/IoT_Embla/Smart_Things/fuse_search_st.js new file mode 100644 index 00000000..42c88333 --- /dev/null +++ b/queries/js/IoT_Embla/Smart_Things/fuse_search_st.js @@ -0,0 +1,32 @@ +/* +Fuzzy search function that returns an object in the form of {result: (Object), score: (Number)} +* @param {String} query - The search term +* @param {Object} data - The data to search +*/ +function smartThingsFuzzySearch(query, data) { + // Restructure data to be searchable by name + var newData = Object.keys(data).map(function (key) { + return { ID: key, info: data[key] }; + }); + // Fuzzy search for the query term (returns an array of objects) + var fuse = new Fuse(newData, { + keys: ["info", "info.name"], + includeScore: true, + shouldSort: true, + threshold: 0.5, + }); + let searchResult = fuse.search(query); + + let resultObject = new Object(); + console.log("result: ", searchResult); + if (searchResult[0] === undefined) { + console.log("no match found"); + return null; + } else { + // Structure the return object to be in the form of {result: (Object), score: (Number)} + resultObject.result = searchResult[0].item; + resultObject.score = searchResult[0].score; + console.log("resultObject :", resultObject); + return resultObject; + } +} diff --git a/queries/js/IoT_Embla/Smart_Things/st.html b/queries/js/IoT_Embla/Smart_Things/st.html index e836903d..bffccf9e 100644 --- a/queries/js/IoT_Embla/Smart_Things/st.html +++ b/queries/js/IoT_Embla/Smart_Things/st.html @@ -18,6 +18,10 @@

SmartThings Test

+ + ui4 + string + diff --git a/queries/js/IoT_Embla/Smart_Things/st.js b/queries/js/IoT_Embla/Smart_Things/st.js index 7e07a183..3e069ba2 100644 --- a/queries/js/IoT_Embla/Smart_Things/st.js +++ b/queries/js/IoT_Embla/Smart_Things/st.js @@ -1,4 +1,4 @@ -const AUTH_TOKEN = "Bearer 85dabaad-10de-4f74-94a7-0dee4685982e"; +const AUTH_TOKEN = ""; function turnOnLight() { var myHeaders = new Headers(); @@ -126,7 +126,17 @@ function lowerBrightness() { .catch((error) => console.log("error", error)); } -function smartThingsWrapper(device, capability, command, arguments = null) { +function smartThingsWrapper( + query, + device, + capability, + command, + arguments = null +) { + + targetObject = smartThingsFuzzySearch(query) + + var myHeaders = new Headers(); myHeaders.append("Authorization", AUTH_TOKEN); myHeaders.append("Content-Type", "application/json"); @@ -159,3 +169,4 @@ function smartThingsWrapper(device, capability, command, arguments = null) { .then((result) => console.log(result)) .catch((error) => console.log("error", error)); } + diff --git a/queries/js/IoT_Embla/Smart_Things/st_connecthub.js b/queries/js/IoT_Embla/Smart_Things/st_connecthub.js index d4502b02..e73efd1b 100644 --- a/queries/js/IoT_Embla/Smart_Things/st_connecthub.js +++ b/queries/js/IoT_Embla/Smart_Things/st_connecthub.js @@ -17,9 +17,8 @@ async function storeDevice(data, ipAddress) { } // bearer token 64780d2b-b763-433d-95ca-3eaaf5e10642 -async function connectHub(clientID, ipAddress) { +async function connectHub(clientID, ipAddress, bearerToken) { console.log("connect hub"); - let bearerToken = "64780d2b-b763-433d-95ca-3eaaf5e10642"; try { const data = { diff --git a/queries/js/IoT_Embla/Smart_Things/test.js b/queries/js/IoT_Embla/Smart_Things/test.js new file mode 100644 index 00000000..a16d5bcb --- /dev/null +++ b/queries/js/IoT_Embla/Smart_Things/test.js @@ -0,0 +1,24426 @@ +(window["webpackJsonp"] = window["webpackJsonp"] || []).push([ + ["chunk-vendors"], + { + "0029": function (t, e) { + t.exports = + "constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split( + "," + ); + }, + "012f": function (t, e, n) { + "use strict"; + var r = n("d13f"), + i = n("f2fe"), + o = n("bc25"), + a = n("560b"); + t.exports = function (t) { + r(r.S, t, { + from: function (t) { + var e, + n, + r, + s, + u = arguments[1]; + return ( + i(this), + (e = void 0 !== u), + e && i(u), + void 0 == t + ? new this() + : ((n = []), + e + ? ((r = 0), + (s = o(u, arguments[2], 2)), + a(t, !1, function (t) { + n.push(s(t, r++)); + })) + : a(t, !1, n.push, n), + new this(n)) + ); + }, + }); + }; + }, + "0185": function (t, e, n) { + var r = n("e5fa"); + t.exports = function (t) { + return Object(r(t)); + }; + }, + "01f9": function (t, e, n) { + "use strict"; + var r = n("2d00"), + i = n("5ca1"), + o = n("2aba"), + a = n("32e9"), + s = n("84f2"), + u = n("41a0"), + c = n("7f20"), + f = n("38fd"), + l = n("2b4c")("iterator"), + d = !([].keys && "next" in [].keys()), + p = "@@iterator", + h = "keys", + v = "values", + y = function () { + return this; + }; + t.exports = function (t, e, n, m, g, b, _) { + u(n, e, m); + var w, + x, + k, + S = function (t) { + if (!d && t in A) return A[t]; + switch (t) { + case h: + return function () { + return new n(this, t); + }; + case v: + return function () { + return new n(this, t); + }; + } + return function () { + return new n(this, t); + }; + }, + E = e + " Iterator", + O = g == v, + T = !1, + A = t.prototype, + C = A[l] || A[p] || (g && A[g]), + j = C || S(g), + I = g ? (O ? S("entries") : j) : void 0, + M = ("Array" == e && A.entries) || C; + if ( + (M && + ((k = f(M.call(new t()))), + k !== Object.prototype && + k.next && + (c(k, E, !0), + r || "function" == typeof k[l] || a(k, l, y))), + O && + C && + C.name !== v && + ((T = !0), + (j = function () { + return C.call(this); + })), + (r && !_) || (!d && !T && A[l]) || a(A, l, j), + (s[e] = j), + (s[E] = y), + g) + ) + if ( + ((w = { + values: O ? j : S(v), + keys: b ? j : S(h), + entries: I, + }), + _) + ) + for (x in w) x in A || o(A, x, w[x]); + else i(i.P + i.F * (d || T), e, w); + return w; + }; + }, + "0234": function (t, e, n) { + "use strict"; + function r(t) { + for (var e = 1; e < arguments.length; e++) { + var n = null != arguments[e] ? arguments[e] : {}, + r = Object.keys(n); + "function" === typeof Object.getOwnPropertySymbols && + (r = r.concat( + Object.getOwnPropertySymbols(n).filter(function ( + t + ) { + return Object.getOwnPropertyDescriptor(n, t) + .enumerable; + }) + )), + r.forEach(function (e) { + i(t, e, n[e]); + }); + } + return t; + } + function i(t, e, n) { + return ( + e in t + ? Object.defineProperty(t, e, { + value: n, + enumerable: !0, + configurable: !0, + writable: !0, + }) + : (t[e] = n), + t + ); + } + function o(t) { + return ( + (o = + "function" === typeof Symbol && + "symbol" === typeof Symbol.iterator + ? function (t) { + return typeof t; + } + : function (t) { + return t && + "function" === typeof Symbol && + t.constructor === Symbol && + t !== Symbol.prototype + ? "symbol" + : typeof t; + }), + o(t) + ); + } + Object.defineProperty(e, "__esModule", { value: !0 }), + (e.pushParams = c), + (e.popParams = f), + (e.withParams = h), + (e._setTarget = e.target = void 0); + var a = [], + s = null; + e.target = s; + var u = function (t) { + e.target = s = t; + }; + function c() { + null !== s && a.push(s), (e.target = s = {}); + } + function f() { + var t = s, + n = (e.target = s = a.pop() || null); + return ( + n && + (Array.isArray(n.$sub) || (n.$sub = []), + n.$sub.push(t)), + t + ); + } + function l(t) { + if ("object" !== o(t) || Array.isArray(t)) + throw new Error("params must be an object"); + e.target = s = r({}, s, t); + } + function d(t, e) { + return p(function (n) { + return function () { + n(t); + for ( + var r = arguments.length, i = new Array(r), o = 0; + o < r; + o++ + ) + i[o] = arguments[o]; + return e.apply(this, i); + }; + }); + } + function p(t) { + var e = t(l); + return function () { + c(); + try { + for ( + var t = arguments.length, n = new Array(t), r = 0; + r < t; + r++ + ) + n[r] = arguments[r]; + return e.apply(this, n); + } finally { + f(); + } + }; + } + function h(t, e) { + return "object" === o(t) && void 0 !== e ? d(t, e) : p(t); + } + e._setTarget = u; + }, + "02d7": function (t, e, n) { + var r = n("6f8a"), + i = n("b5aa"), + o = n("1b55")("species"); + t.exports = function (t) { + var e; + return ( + i(t) && + ((e = t.constructor), + "function" != typeof e || + (e !== Array && !i(e.prototype)) || + (e = void 0), + r(e) && ((e = e[o]), null === e && (e = void 0))), + void 0 === e ? Array : e + ); + }; + }, + "02f4": function (t, e, n) { + var r = n("4588"), + i = n("be13"); + t.exports = function (t) { + return function (e, n) { + var o, + a, + s = String(i(e)), + u = r(n), + c = s.length; + return u < 0 || u >= c + ? t + ? "" + : void 0 + : ((o = s.charCodeAt(u)), + o < 55296 || + o > 56319 || + u + 1 === c || + (a = s.charCodeAt(u + 1)) < 56320 || + a > 57343 + ? t + ? s.charAt(u) + : o + : t + ? s.slice(u, u + 2) + : a - 56320 + ((o - 55296) << 10) + 65536); + }; + }; + }, + "034c": function (t, e, n) { + var r = n("7d8a"), + i = n("adf3"); + t.exports = function (t) { + return function () { + if (r(this) != t) + throw TypeError(t + "#toJSON isn't generic"); + return i(this); + }; + }; + }, + "0390": function (t, e, n) { + "use strict"; + var r = n("02f4")(!0); + t.exports = function (t, e, n) { + return e + (n ? r(t, e).length : 1); + }; + }, + "061b": function (t, e, n) { + t.exports = n("7017"); + }, + "0780": function (t, e, n) { + "use strict"; + var r = n("3adc").f, + i = n("7108"), + o = n("3904"), + a = n("bc25"), + s = n("b0bc"), + u = n("560b"), + c = n("e4a9"), + f = n("245b"), + l = n("1be4"), + d = n("7d95"), + p = n("6277").fastKey, + h = n("1fca"), + v = d ? "_s" : "size", + y = function (t, e) { + var n, + r = p(e); + if ("F" !== r) return t._i[r]; + for (n = t._f; n; n = n.n) if (n.k == e) return n; + }; + t.exports = { + getConstructor: function (t, e, n, c) { + var f = t(function (t, r) { + s(t, f, e, "_i"), + (t._t = e), + (t._i = i(null)), + (t._f = void 0), + (t._l = void 0), + (t[v] = 0), + void 0 != r && u(r, n, t[c], t); + }); + return ( + o(f.prototype, { + clear: function () { + for ( + var t = h(this, e), n = t._i, r = t._f; + r; + r = r.n + ) + (r.r = !0), + r.p && (r.p = r.p.n = void 0), + delete n[r.i]; + (t._f = t._l = void 0), (t[v] = 0); + }, + delete: function (t) { + var n = h(this, e), + r = y(n, t); + if (r) { + var i = r.n, + o = r.p; + delete n._i[r.i], + (r.r = !0), + o && (o.n = i), + i && (i.p = o), + n._f == r && (n._f = i), + n._l == r && (n._l = o), + n[v]--; + } + return !!r; + }, + forEach: function (t) { + h(this, e); + var n, + r = a( + t, + arguments.length > 1 + ? arguments[1] + : void 0, + 3 + ); + while ((n = n ? n.n : this._f)) { + r(n.v, n.k, this); + while (n && n.r) n = n.p; + } + }, + has: function (t) { + return !!y(h(this, e), t); + }, + }), + d && + r(f.prototype, "size", { + get: function () { + return h(this, e)[v]; + }, + }), + f + ); + }, + def: function (t, e, n) { + var r, + i, + o = y(t, e); + return ( + o + ? (o.v = n) + : ((t._l = o = + { + i: (i = p(e, !0)), + k: e, + v: n, + p: (r = t._l), + n: void 0, + r: !1, + }), + t._f || (t._f = o), + r && (r.n = o), + t[v]++, + "F" !== i && (t._i[i] = o)), + t + ); + }, + getEntry: y, + setStrong: function (t, e, n) { + c( + t, + e, + function (t, n) { + (this._t = h(t, e)), + (this._k = n), + (this._l = void 0); + }, + function () { + var t = this, + e = t._k, + n = t._l; + while (n && n.r) n = n.p; + return t._t && (t._l = n = n ? n.n : t._t._f) + ? f( + 0, + "keys" == e + ? n.k + : "values" == e + ? n.v + : [n.k, n.v] + ) + : ((t._t = void 0), f(1)); + }, + n ? "entries" : "values", + !n, + !0 + ), + l(e); + }, + }; + }, + "07c8": function (t, e, n) { + var r = n("d13f"); + r(r.S, "Object", { setPrototypeOf: n("6494").set }); + }, + "097d": function (t, e, n) { + "use strict"; + var r = n("5ca1"), + i = n("8378"), + o = n("7726"), + a = n("ebd6"), + s = n("bcaa"); + r(r.P + r.R, "Promise", { + finally: function (t) { + var e = a(this, i.Promise || o.Promise), + n = "function" == typeof t; + return this.then( + n + ? function (n) { + return s(e, t()).then(function () { + return n; + }); + } + : t, + n + ? function (n) { + return s(e, t()).then(function () { + throw n; + }); + } + : t + ); + }, + }); + }, + "0a0a": function (t, e, n) { + var r = n("da3c"), + i = n("a7d3"), + o = n("b457"), + a = n("fda1"), + s = n("3adc").f; + t.exports = function (t) { + var e = i.Symbol || (i.Symbol = o ? {} : r.Symbol || {}); + "_" == t.charAt(0) || t in e || s(e, t, { value: a.f(t) }); + }; + }, + "0a49": function (t, e, n) { + var r = n("9b43"), + i = n("626a"), + o = n("4bf8"), + a = n("9def"), + s = n("cd1c"); + t.exports = function (t, e) { + var n = 1 == t, + u = 2 == t, + c = 3 == t, + f = 4 == t, + l = 6 == t, + d = 5 == t || l, + p = e || s; + return function (e, s, h) { + for ( + var v, + y, + m = o(e), + g = i(m), + b = r(s, h, 3), + _ = a(g.length), + w = 0, + x = n ? p(e, _) : u ? p(e, 0) : void 0; + _ > w; + w++ + ) + if ((d || w in g) && ((v = g[w]), (y = b(v, w, m)), t)) + if (n) x[w] = y; + else if (y) + switch (t) { + case 3: + return !0; + case 5: + return v; + case 6: + return w; + case 2: + x.push(v); + } + else if (f) return !1; + return l ? -1 : c || f ? f : x; + }; + }; + }, + "0bfb": function (t, e, n) { + "use strict"; + var r = n("cb7c"); + t.exports = function () { + var t = r(this), + e = ""; + return ( + t.global && (e += "g"), + t.ignoreCase && (e += "i"), + t.multiline && (e += "m"), + t.unicode && (e += "u"), + t.sticky && (e += "y"), + e + ); + }; + }, + "0d42": function (t, e, n) { + "use strict"; + var r = { + log: console.log.bind(console), + warn: console.warn.bind(console), + error: console.error.bind(console), + }, + i = "?"; + function o(t) { + var e = [], + n = h(t, "stack"); + return ( + n && + n.split("\n").forEach(function (t) { + var n = u(t) || f(t) || p(t); + n && (!n.func && n.line && (n.func = i), e.push(n)); + }), + { message: h(t, "message"), name: h(t, "name"), stack: e } + ); + } + var a = + /^\s*at (.*?) ?\(((?:file|https?|blob|chrome-extension|native|eval|webpack||\/).*?)(?::(\d+))?(?::(\d+))?\)?\s*$/i, + s = /\((\S*)(?::(\d+))(?::(\d+))\)/; + function u(t) { + var e = a.exec(t); + if (e) { + var n = e[2] && 0 === e[2].indexOf("native"), + r = e[2] && 0 === e[2].indexOf("eval"), + o = s.exec(e[2]); + return ( + r && o && ((e[2] = o[1]), (e[3] = o[2]), (e[4] = o[3])), + { + args: n ? [e[2]] : [], + column: e[4] ? +e[4] : void 0, + func: e[1] || i, + line: e[3] ? +e[3] : void 0, + url: n ? void 0 : e[2], + } + ); + } + } + var c = + /^\s*at (?:((?:\[object object\])?.+) )?\(?((?:file|ms-appx|https?|webpack|blob):.*?):(\d+)(?::(\d+))?\)?\s*$/i; + function f(t) { + var e = c.exec(t); + if (e) + return { + args: [], + column: e[4] ? +e[4] : void 0, + func: e[1] || i, + line: +e[3], + url: e[2], + }; + } + var l = + /^\s*(.*?)(?:\((.*?)\))?(?:^|@)((?:file|https?|blob|chrome|webpack|resource|capacitor|\[native).*?|[^@]*bundle)(?::(\d+))?(?::(\d+))?\s*$/i, + d = /(\S+) line (\d+)(?: > eval line \d+)* > eval/i; + function p(t) { + var e = l.exec(t); + if (e) { + var n = e[3] && e[3].indexOf(" > eval") > -1, + r = d.exec(e[3]); + return ( + n && + r && + ((e[3] = r[1]), (e[4] = r[2]), (e[5] = void 0)), + { + args: e[2] ? e[2].split(",") : [], + column: e[5] ? +e[5] : void 0, + func: e[1] || i, + line: e[4] ? +e[4] : void 0, + url: e[3], + } + ); + } + } + function h(t, e) { + if ("object" === typeof t && t && e in t) { + var n = t[e]; + return "string" === typeof n ? n : void 0; + } + } + var v = 1e3, + y = 60 * v, + m = 60 * y, + g = 24 * m, + b = 365 * g, + _ = 1024; + function w(t, e, n) { + var r, + i, + o = !n || void 0 === n.leading || n.leading, + a = !n || void 0 === n.trailing || n.trailing, + s = !1; + return { + throttled: function () { + for (var n = [], u = 0; u < arguments.length; u++) + n[u] = arguments[u]; + s + ? (r = n) + : (o ? t.apply(void 0, n) : (r = n), + (s = !0), + (i = setTimeout(function () { + a && r && t.apply(void 0, r), + (s = !1), + (r = void 0); + }, e))); + }, + cancel: function () { + clearTimeout(i), (s = !1), (r = void 0); + }, + }; + } + function x(t) { + for (var e = [], n = 1; n < arguments.length; n++) + e[n - 1] = arguments[n]; + return ( + e.forEach(function (e) { + for (var n in e) + Object.prototype.hasOwnProperty.call(e, n) && + (t[n] = e[n]); + }), + t + ); + } + function k(t) { + return x({}, t); + } + function S(t) { + return t + ? ( + parseInt(t, 10) ^ + ((16 * Math.random()) >> (parseInt(t, 10) / 4)) + ).toString(16) + : "" + .concat(1e7, "-") + .concat(1e3, "-") + .concat(4e3, "-") + .concat(8e3, "-") + .concat(1e11) + .replace(/[018]/g, S); + } + function E(t) { + return 0 !== t && 100 * Math.random() <= t; + } + function O(t, e) { + return +t.toFixed(e); + } + function T() {} + function A(t, e, n) { + if (null === t || void 0 === t) return JSON.stringify(t); + var r = [!1, void 0]; + C(t) && ((r = [!0, t.toJSON]), delete t.toJSON); + var i, + o, + a = [!1, void 0]; + "object" === typeof t && + ((i = Object.getPrototypeOf(t)), + C(i) && ((a = [!0, i.toJSON]), delete i.toJSON)); + try { + o = JSON.stringify(t, e, n); + } catch (s) { + o = ""; + } finally { + r[0] && (t.toJSON = r[1]), a[0] && (i.toJSON = a[1]); + } + return o; + } + function C(t) { + return ( + "object" === typeof t && + null !== t && + Object.prototype.hasOwnProperty.call(t, "toJSON") + ); + } + function j(t, e) { + return -1 !== t.indexOf(e); + } + function I(t, e) { + for (var n = 0; n < t.length; n += 1) { + var r = t[n]; + if (e(r, n, t)) return r; + } + } + function M(t, e) { + for (var n = t.length - 1; n >= 0; n -= 1) { + var r = t[n]; + if (e(r, n, t)) return r; + } + } + function L(t) { + return P(t) && t >= 0 && t <= 100; + } + function P(t) { + return "number" === typeof t; + } + function N(t) { + return Object.keys(t).map(function (e) { + return t[e]; + }); + } + function R(t, e) { + return Object.keys(t).some(function (n) { + return t[n] === e; + }); + } + function $(t) { + return Object.keys(t).map(function (e) { + return [e, t[e]]; + }); + } + function D(t) { + return 0 === Object.keys(t).length; + } + function F(t, e) { + for (var n = {}, r = 0, i = Object.keys(t); r < i.length; r++) { + var o = i[r]; + n[o] = e(t[o]); + } + return n; + } + function z() { + if ("object" === typeof globalThis) return globalThis; + Object.defineProperty(Object.prototype, "_dd_temp_", { + get: function () { + return this; + }, + configurable: !0, + }); + var t = _dd_temp_; + return ( + delete Object.prototype._dd_temp_, + "object" !== typeof t && + (t = + "object" === typeof self + ? self + : "object" === typeof window + ? window + : {}), + t + ); + } + function U() { + return B(window.location); + } + function B(t) { + if (t.origin) return t.origin; + var e = t.host.replace(/(:80|:443)$/, ""); + return "".concat(t.protocol, "//").concat(e); + } + function V(t, e) { + var n = new RegExp("(?:^|;)\\s*".concat(e, "\\s*=\\s*([^;]+)")), + r = n.exec(t); + return r ? r[1] : void 0; + } + function H(t, e, n) { + void 0 === n && (n = ""); + var r = t.charCodeAt(e - 1), + i = r >= 55296 && r <= 56319, + o = i ? e + 1 : e; + return t.length <= o ? t : "".concat(t.slice(0, o)).concat(n); + } + function q(t, e, n, r) { + return W(t, [e], n, r); + } + function W(t, e, n, r) { + var i = void 0 === r ? {} : r, + o = i.once, + a = i.capture, + s = i.passive, + u = Tt( + o + ? function (t) { + f(), n(t); + } + : n + ), + c = s ? { capture: a, passive: s } : a; + e.forEach(function (e) { + return t.addEventListener(e, u, c); + }); + var f = function () { + return e.forEach(function (e) { + return t.removeEventListener(e, u, c); + }); + }; + return { stop: f }; + } + function G(t, e) { + if ( + document.readyState === t || + "complete" === document.readyState + ) + e(); + else { + var n = "complete" === t ? "load" : "DOMContentLoaded"; + q(window, n, e, { once: !0 }); + } + } + function Z(t) { + return null === t + ? "null" + : Array.isArray(t) + ? "array" + : typeof t; + } + function J() { + if ("undefined" !== typeof WeakSet) { + var t = new WeakSet(); + return { + hasAlreadyBeenSeen: function (e) { + var n = t.has(e); + return n || t.add(e), n; + }, + }; + } + var e = []; + return { + hasAlreadyBeenSeen: function (t) { + var n = e.indexOf(t) >= 0; + return n || e.push(t), n; + }, + }; + } + function K(t, e, n) { + if ((void 0 === n && (n = J()), void 0 === e)) return t; + if ("object" !== typeof e || null === e) return e; + if (e instanceof Date) return new Date(e.getTime()); + if (e instanceof RegExp) { + var r = + e.flags || + [ + e.global ? "g" : "", + e.ignoreCase ? "i" : "", + e.multiline ? "m" : "", + e.sticky ? "y" : "", + e.unicode ? "u" : "", + ].join(""); + return new RegExp(e.source, r); + } + if (!n.hasAlreadyBeenSeen(e)) { + if (Array.isArray(e)) { + for ( + var i = Array.isArray(t) ? t : [], o = 0; + o < e.length; + ++o + ) + i[o] = K(i[o], e[o], n); + return i; + } + var a = "object" === Z(t) ? t : {}; + for (var s in e) + Object.prototype.hasOwnProperty.call(e, s) && + (a[s] = K(a[s], e[s], n)); + return a; + } + } + function X(t) { + return K(void 0, t); + } + function Y() { + for (var t, e = [], n = 0; n < arguments.length; n++) + e[n] = arguments[n]; + for (var r = 0, i = e; r < i.length; r++) { + var o = i[r]; + void 0 !== o && null !== o && (t = K(t, o)); + } + return t; + } + function Q(t, e) { + if (window.requestIdleCallback) { + var n = window.requestIdleCallback(Tt(t), e); + return function () { + return window.cancelIdleCallback(n); + }; + } + var r = window.requestAnimationFrame(Tt(t)); + return function () { + return window.cancelAnimationFrame(r); + }; + } + var tt = { + AGENT: "agent", + CONSOLE: "console", + CUSTOM: "custom", + LOGGER: "logger", + NETWORK: "network", + SOURCE: "source", + REPORT: "report", + }; + function et(t, e, n, r) { + return t && (void 0 !== t.message || e instanceof Error) + ? { + message: t.message || "Empty message", + stack: nt(t), + handlingStack: r, + type: t.name, + } + : { + message: "".concat(n, " ").concat(A(e)), + stack: "No stack, consider using an instance of Error", + handlingStack: r, + type: t && t.name, + }; + } + function nt(t) { + var e = rt(t); + return ( + t.stack.forEach(function (t) { + var n = "?" === t.func ? "" : t.func, + r = + t.args && t.args.length > 0 + ? "(".concat(t.args.join(", "), ")") + : "", + i = t.line ? ":".concat(t.line) : "", + o = t.line && t.column ? ":".concat(t.column) : ""; + e += "\n at " + .concat(n) + .concat(r, " @ ") + .concat(t.url) + .concat(i) + .concat(o); + }), + e + ); + } + function rt(t) { + return "".concat(t.name || "Error", ": ").concat(t.message); + } + function it() { + var t, + e = 2, + n = new Error(); + if (!n.stack) + try { + throw n; + } catch (r) { + T(); + } + return ( + At(function () { + var r = o(n); + (r.stack = r.stack.slice(e)), (t = nt(r)); + }), + t + ); + } + var ot, + at, + st = (function () { + function t(t) { + (this.onFirstSubscribe = t), (this.observers = []); + } + return ( + (t.prototype.subscribe = function (t) { + var e = this; + return ( + !this.observers.length && + this.onFirstSubscribe && + (this.onLastUnsubscribe = + this.onFirstSubscribe() || void 0), + this.observers.push(t), + { + unsubscribe: function () { + (e.observers = e.observers.filter( + function (e) { + return t !== e; + } + )), + !e.observers.length && + e.onLastUnsubscribe && + e.onLastUnsubscribe(); + }, + } + ); + }), + (t.prototype.notify = function (t) { + this.observers.forEach(function (e) { + return e(t); + }); + }), + t + ); + })(); + function ut() { + for (var t = [], e = 0; e < arguments.length; e++) + t[e] = arguments[e]; + var n = new st(function () { + var e = t.map(function (t) { + return t.subscribe(function (t) { + return n.notify(t); + }); + }); + return function () { + return e.forEach(function (t) { + return t.unsubscribe(); + }); + }; + }); + return n; + } + function ct(t) { + return { relative: t, timeStamp: ft(t) }; + } + function ft(t) { + var e = Date.now() - performance.now(); + return e > wt() ? Math.round(e + t) : bt(t); + } + function lt() { + return Math.round(Date.now() - (wt() + performance.now())); + } + function dt(t) { + return P(t) ? O(1e6 * t, 0) : t; + } + function pt() { + return Date.now(); + } + function ht() { + return performance.now(); + } + function vt() { + return { relative: ht(), timeStamp: pt() }; + } + function yt() { + return { relative: 0, timeStamp: wt() }; + } + function mt(t, e) { + return e - t; + } + function gt(t) { + return t - wt(); + } + function bt(t) { + return Math.round(wt() + t); + } + function _t(t) { + return t < b; + } + function wt() { + return ( + void 0 === ot && (ot = performance.timing.navigationStart), + ot + ); + } + function xt(t) { + Array.isArray(t) && + (at || (at = new Set(t)), + t + .filter(function (t) { + return "string" === typeof t; + }) + .forEach(function (t) { + at.add(t); + })); + } + function kt(t) { + return !!at && at.has(t); + } + var St, + Et = { + maxMessagesPerPage: 0, + sentMessageCount: 0, + telemetryEnabled: !1, + }; + function Ot(t) { + var e, + n, + r = new st(), + i = new st(); + function o(t) { + return Y({ date: pt() }, void 0 !== e ? e() : {}, t); + } + function a(t) { + return Y( + { + type: "telemetry", + date: pt(), + service: "browser-sdk", + version: "4.8.1", + source: "browser", + _dd: { format_version: 2 }, + telemetry: t, + }, + void 0 !== n ? n() : {} + ); + } + return ( + (Et.telemetryEnabled = E(t.telemetrySampleRate)), + (St = function (t) { + r.notify(o(t)), + kt("telemetry") && + Et.telemetryEnabled && + i.notify(a(t)); + }), + x(Et, { + maxMessagesPerPage: + t.maxInternalMonitoringMessagesPerPage, + sentMessageCount: 0, + }), + { + setExternalContextProvider: function (t) { + e = t; + }, + monitoringMessageObservable: r, + setTelemetryContextProvider: function (t) { + n = t; + }, + telemetryEventObservable: i, + } + ); + } + function Tt(t) { + return function () { + return At(t, this, arguments); + }; + } + function At(t, e, n) { + try { + return t.apply(e, n); + } catch (r) { + Pt(r); + try { + jt(r); + } catch (r) { + Pt(r); + } + } + } + function Ct(t, e) { + Nt(t, e), It(x({ message: t, status: "debug" }, e)); + } + function jt(t) { + It(x({ status: "error" }, Mt(t))); + } + function It(t) { + St && + Et.sentMessageCount < Et.maxMessagesPerPage && + ((Et.sentMessageCount += 1), St(t)); + } + function Mt(t) { + if (t instanceof Error) { + var e = o(t); + return { + error: { kind: e.name, stack: nt(e) }, + message: e.message, + }; + } + return { + error: { stack: "Not an instance of error" }, + message: "Uncaught ".concat(A(t)), + }; + } + function Lt(t) { + Et.debugMode = t; + } + function Pt(t) { + Et.debugMode && r.error("[INTERNAL ERROR]", t); + } + function Nt(t, e) { + Et.debugMode && r.log("[MONITORING MESSAGE]", t, e); + } + function Rt(t, e) { + return function () { + for (var n = [], i = 0; i < arguments.length; i++) + n[i] = arguments[i]; + try { + return t.apply(void 0, n); + } catch (o) { + r.error(e, o); + } + }; + } + function $t(t) { + var e = x( + { + version: "4.8.1", + onReady: function (t) { + t(); + }, + }, + t + ); + return ( + Object.defineProperty(e, "_setDebug", { + get: function () { + return Lt; + }, + enumerable: !1, + }), + e + ); + } + function Dt(t, e, n) { + var r = t[e]; + (t[e] = n), + r && + r.q && + r.q.forEach(function (t) { + return Rt(t, "onReady callback threw an error:")(); + }); + } + function Ft() { + var t = {}; + return { + get: function () { + return t; + }, + add: function (e, n) { + t[e] = n; + }, + remove: function (e) { + delete t[e]; + }, + set: function (e) { + t = e; + }, + }; + } + var zt = 500, + Ut = (function () { + function t() { + this.buffer = []; + } + return ( + (t.prototype.add = function (t) { + var e = this.buffer.push(t); + e > zt && this.buffer.splice(0, 1); + }), + (t.prototype.drain = function () { + this.buffer.forEach(function (t) { + return t(); + }), + (this.buffer.length = 0); + }), + t + ); + })(); + function Bt() { + var t = Ht(); + if (t) + return { + getAllowedWebViewHosts: function () { + return JSON.parse(t.getAllowedWebViewHosts()); + }, + send: function (e, n) { + t.send(JSON.stringify({ eventType: e, event: n })); + }, + }; + } + function Vt(t) { + var e; + void 0 === t && + (t = + null === (e = z().location) || void 0 === e + ? void 0 + : e.hostname); + var n = Bt(); + return ( + !!n && + n.getAllowedWebViewHosts().some(function (e) { + var n = e.replace(/\./g, "\\."), + r = new RegExp("^(.+\\.)*".concat(n, "$")); + return r.test(t); + }) + ); + } + function Ht() { + return z().DatadogEventBridge; + } + var qt, + Wt, + Gt = v; + function Zt(t, e, n, r) { + var i = new Date(); + i.setTime(i.getTime() + n); + var o = "expires=".concat(i.toUTCString()), + a = r && r.crossSite ? "none" : "strict", + s = r && r.domain ? ";domain=".concat(r.domain) : "", + u = r && r.secure ? ";secure" : ""; + document.cookie = "" + .concat(t, "=") + .concat(e, ";") + .concat(o, ";path=/;samesite=") + .concat(a) + .concat(s) + .concat(u); + } + function Jt(t) { + return V(document.cookie, t); + } + function Kt(t, e) { + Zt(t, "", 0, e); + } + function Xt(t) { + if (void 0 === document.cookie || null === document.cookie) + return !1; + try { + var e = "dd_cookie_test_".concat(S()), + n = "test"; + Zt(e, n, v, t); + var i = Jt(e) === n; + return Kt(e, t), i; + } catch (o) { + return r.error(o), !1; + } + } + function Yt() { + if (void 0 === qt) { + var t = "dd_site_test_".concat(S()), + e = "test", + n = window.location.hostname.split("."), + r = n.pop(); + while (n.length && !Jt(t)) + (r = "".concat(n.pop(), ".").concat(r)), + Zt(t, e, v, { domain: r }); + Kt(t, { domain: r }), (qt = r); + } + return qt; + } + function Qt(t) { + return re(t, U()).href; + } + function te(t) { + try { + return !!re(t); + } catch (e) { + return !1; + } + } + function ee(t) { + return B(re(t)); + } + function ne(t) { + var e = re(t).pathname; + return "/" === e[0] ? e : "/".concat(e); + } + function re(t, e) { + if (ie()) return void 0 !== e ? new URL(t, e) : new URL(t); + if (void 0 === e && !/:/.test(t)) + throw new Error("Invalid URL: '".concat(t, "'")); + var n = document, + r = n.createElement("a"); + if (void 0 !== e) { + n = document.implementation.createHTMLDocument(""); + var i = n.createElement("base"); + (i.href = e), n.head.appendChild(i), n.body.appendChild(r); + } + return (r.href = t), r; + } + function ie() { + if (void 0 !== Wt) return Wt; + try { + var t = new URL("http://test/path"); + return (Wt = "http://test/path" === t.href), Wt; + } catch (e) { + Wt = !1; + } + return Wt; + } + var oe = { + logs: "logs", + rum: "rum", + sessionReplay: "session-replay", + }, + ae = { logs: "logs", rum: "rum", sessionReplay: "replay" }, + se = "datadoghq.com"; + function ue(t, e, n, r) { + var i = t.site, + o = void 0 === i ? se : i, + a = t.clientToken, + s = o.split("."), + u = s.pop(), + c = "" + .concat(oe[e], ".browser-intake-") + .concat(s.join("-"), ".") + .concat(u), + f = "https://".concat(c, "/api/v2/").concat(ae[e]), + l = t.proxyUrl && Qt(t.proxyUrl); + return { + build: function () { + var t = + "ddsource=".concat(r || "browser") + + "&ddtags=".concat( + encodeURIComponent( + ["sdk_version:".concat("4.8.1")] + .concat(n) + .join(",") + ) + ) + + "&dd-api-key=".concat(a) + + "&dd-evp-origin-version=".concat( + encodeURIComponent("4.8.1") + ) + + "&dd-evp-origin=browser" + + "&dd-request-id=".concat(S()); + "rum" === e && (t += "&batch_time=".concat(pt())); + var i = "".concat(f, "?").concat(t); + return l + ? "" + .concat(l, "?ddforward=") + .concat(encodeURIComponent(i)) + : i; + }, + buildIntakeUrl: function () { + return l ? "".concat(l, "?ddforward") : f; + }, + }; + } + var ce = 200; + function fe(t) { + var e = t.env, + n = t.service, + r = t.version, + i = t.datacenter, + o = []; + return ( + e && o.push(de("env", e)), + n && o.push(de("service", n)), + r && o.push(de("version", r)), + i && o.push(de("datacenter", i)), + o + ); + } + var le = /[^a-z0-9_:./-]/; + function de(t, e) { + var n = ce - t.length - 1; + (e.length > n || le.test(e)) && + r.warn( + "".concat( + t, + " value doesn't meet tag requirements and will be sanitized" + ) + ); + var i = e.replace(/,/g, "_"); + return "".concat(t, ":").concat(i); + } + function pe(t) { + var e = fe(t), + n = he(t, e), + r = N(n).map(function (t) { + return t.buildIntakeUrl(); + }), + i = ve(t, r, e); + return x( + { + isIntakeUrl: function (t) { + return r.some(function (e) { + return 0 === t.indexOf(e); + }); + }, + replica: i, + }, + n + ); + } + function he(t, e) { + var n = { + logsEndpointBuilder: ue(t, "logs", e), + rumEndpointBuilder: ue(t, "rum", e), + sessionReplayEndpointBuilder: ue(t, "sessionReplay", e), + }; + return t.internalMonitoringApiKey + ? x(n, { + internalMonitoringEndpointBuilder: ue( + x({}, t, { + clientToken: t.internalMonitoringApiKey, + }), + "logs", + e, + "browser-agent-internal-monitoring" + ), + }) + : n; + } + function ve(t, e, n) { + if (t.replica) { + var r = x({}, t, { + site: se, + clientToken: t.replica.clientToken, + }), + i = { + logsEndpointBuilder: ue(r, "logs", n), + rumEndpointBuilder: ue(r, "rum", n), + internalMonitoringEndpointBuilder: ue( + r, + "logs", + n, + "browser-agent-internal-monitoring" + ), + }; + return ( + e.push.apply( + e, + N(i).map(function (t) { + return t.buildIntakeUrl(); + }) + ), + x({ applicationId: t.replica.applicationId }, i) + ); + } + } + var ye = { + ALLOW: "allow", + MASK: "mask", + MASK_USER_INPUT: "mask-user-input", + }; + function me(t) { + var e, n; + if (t && t.clientToken) + if (void 0 === t.sampleRate || L(t.sampleRate)) { + if ( + void 0 === t.telemetrySampleRate || + L(t.telemetrySampleRate) + ) + return ( + xt(t.enableExperimentalFeatures), + x( + { + beforeSend: + t.beforeSend && + Rt( + t.beforeSend, + "beforeSend threw an error:" + ), + cookieOptions: ge(t), + sampleRate: + null !== (e = t.sampleRate) && + void 0 !== e + ? e + : 100, + telemetrySampleRate: + null !== + (n = t.telemetrySampleRate) && + void 0 !== n + ? n + : 20, + service: t.service, + silentMultipleInit: + !!t.silentMultipleInit, + batchBytesLimit: 16 * _, + eventRateLimiterThreshold: 3e3, + maxInternalMonitoringMessagesPerPage: 15, + flushTimeout: 30 * v, + maxBatchSize: 50, + maxMessageSize: 256 * _, + }, + pe(t) + ) + ); + r.error( + "Telemetry Sample Rate should be a number between 0 and 100" + ); + } else + r.error( + "Sample Rate should be a number between 0 and 100" + ); + else + r.error( + "Client Token is not configured, we will not send any data." + ); + } + function ge(t) { + var e = {}; + return ( + (e.secure = be(t)), + (e.crossSite = !!t.useCrossSiteSessionCookie), + t.trackSessionAcrossSubdomains && (e.domain = Yt()), + e + ); + } + function be(t) { + return ( + !!t.useSecureSessionCookie || !!t.useCrossSiteSessionCookie + ); + } + var _e = "datadog-synthetics-public-id", + we = "datadog-synthetics-result-id", + xe = "datadog-synthetics-injects-rum"; + function ke() { + var t = window._DATADOG_SYNTHETICS_PUBLIC_ID || Jt(_e), + e = window._DATADOG_SYNTHETICS_RESULT_ID || Jt(we); + if ("string" === typeof t && "string" === typeof e) + return { test_id: t, result_id: e, injected: Se() }; + } + function Se() { + return Boolean( + window._DATADOG_SYNTHETICS_INJECTS_RUM || Jt(xe) + ); + } + function Ee(t) { + var e, n; + if (t.applicationId) + if ( + void 0 === t.replaySampleRate || + L(t.replaySampleRate) + ) { + if (void 0 !== t.allowedTracingOrigins) { + if (!Array.isArray(t.allowedTracingOrigins)) + return void r.error( + "Allowed Tracing Origins should be an array" + ); + if ( + 0 !== t.allowedTracingOrigins.length && + void 0 === t.service + ) + return void r.error( + "Service need to be configured when tracing is enabled" + ); + } + var i = me(t); + if (i) + return x( + { + applicationId: t.applicationId, + version: t.version, + actionNameAttribute: t.actionNameAttribute, + replaySampleRate: + null !== (e = t.replaySampleRate) && + void 0 !== e + ? e + : 100, + allowedTracingOrigins: + null !== + (n = t.allowedTracingOrigins) && + void 0 !== n + ? n + : [], + trackInteractions: !!t.trackInteractions, + trackViewsManually: !!t.trackViewsManually, + defaultPrivacyLevel: R( + ye, + t.defaultPrivacyLevel + ) + ? t.defaultPrivacyLevel + : ye.MASK_USER_INPUT, + }, + i + ); + } else + r.error( + "Replay Sample Rate should be a number between 0 and 100" + ); + else + r.error( + "Application ID is not configured, no RUM data will be collected." + ); + } + function Oe(t, e, n) { + var i = void 0 === n ? {} : n, + o = i.ignoreInitIfSyntheticsWillInjectRum, + a = void 0 === o || o, + s = !1, + u = Ft(), + c = {}, + f = function () {}, + l = function () {}, + d = new Ut(), + p = function (t, e) { + void 0 === e && (e = pt()), + d.add(function () { + return p(t, e); + }); + }, + h = function (t, e) { + void 0 === e && (e = vt()), + d.add(function () { + return h(t, e); + }); + }, + v = function (t, e) { + void 0 === e && (e = m()), + d.add(function () { + return v(t, e); + }); + }, + y = function (t, e) { + void 0 === e && (e = m()), + d.add(function () { + return y(t, e); + }); + }; + function m() { + return X({ context: u.get(), user: c }); + } + function g(t) { + if (!a || !Se()) { + if (Vt()) t = O(t); + else if (!S(t)) return; + if (E(t)) { + var e = Ee(t); + if (e) { + if (e.trackViewsManually) { + var n = d; + (d = new Ut()), + (h = function (t) { + b(e, t); + }), + n.drain(); + } else b(e); + (l = function () { + return X(t); + }), + (s = !0); + } + } + } + } + function b(n, r) { + var i = t( + n, + function () { + return { + user: c, + context: u.get(), + hasReplay: !!e.isRecording() || void 0, + }; + }, + e, + r + ); + (h = i.startView), + (v = i.addAction), + (y = i.addError), + (p = i.addTiming), + (f = i.getInternalContext), + d.drain(), + e.onRumStart(i.lifeCycle, n, i.session, i.viewContexts); + } + var _ = Tt(function (t) { + var e = "object" === typeof t ? t : { name: t }; + h(e); + }), + w = $t({ + init: Tt(g), + addRumGlobalContext: Tt(u.add), + removeRumGlobalContext: Tt(u.remove), + getRumGlobalContext: Tt(u.get), + setRumGlobalContext: Tt(u.set), + getInternalContext: Tt(function (t) { + return f(t); + }), + getInitConfiguration: Tt(function () { + return l(); + }), + addAction: Tt(function (t, e) { + v({ + name: t, + context: X(e), + startClocks: vt(), + type: "custom", + }); + }), + addError: function (t, e) { + var n = it(); + At(function () { + y({ + error: t, + handlingStack: n, + context: X(e), + startClocks: vt(), + }); + }); + }, + addTiming: Tt(function (t, e) { + p(t, e); + }), + setUser: Tt(function (t) { + var e = k(t); + e ? (c = e) : r.error("Unsupported user:", t); + }), + removeUser: Tt(function () { + c = {}; + }), + startView: _, + startSessionReplayRecording: Tt(e.start), + stopSessionReplayRecording: Tt(e.stop), + }); + return w; + function k(t) { + if ("object" === typeof t && t) { + var e = X(t); + return ( + "id" in e && (e.id = String(e.id)), + "name" in e && (e.name = String(e.name)), + "email" in e && (e.email = String(e.email)), + e + ); + } + } + function S(t) { + return Xt(ge(t)) + ? !T() || + (r.error( + "Execution is not allowed in the current context." + ), + !1) + : (r.warn( + "Cookies are not authorized, we will not send any data." + ), + !1); + } + function E(t) { + return ( + !s || + (t.silentMultipleInit || + r.error("DD_RUM is already initialized."), + !1) + ); + } + function O(t) { + return x({}, t, { + applicationId: "00000000-aaaa-0000-aaaa-000000000000", + clientToken: "empty", + sampleRate: 100, + }); + } + function T() { + return "file:" === window.location.protocol; + } + } + var Te = /[^\u0000-\u007F]/, + Ae = (function () { + function t(t, e, n, r, i, o) { + void 0 === o && (o = T), + (this.request = t), + (this.maxSize = e), + (this.bytesLimit = n), + (this.maxMessageSize = r), + (this.flushTimeout = i), + (this.beforeUnloadCallback = o), + (this.pushOnlyBuffer = []), + (this.upsertBuffer = {}), + (this.bufferBytesSize = 0), + (this.bufferMessageCount = 0), + this.flushOnVisibilityHidden(), + this.flushPeriodically(); + } + return ( + (t.prototype.add = function (t) { + this.addOrUpdate(t); + }), + (t.prototype.upsert = function (t, e) { + this.addOrUpdate(t, e); + }), + (t.prototype.flush = function (t) { + if (0 !== this.bufferMessageCount) { + var e = this.pushOnlyBuffer.concat( + N(this.upsertBuffer) + ); + this.request.send( + e.join("\n"), + this.bufferBytesSize, + t + ), + (this.pushOnlyBuffer = []), + (this.upsertBuffer = {}), + (this.bufferBytesSize = 0), + (this.bufferMessageCount = 0); + } + }), + (t.prototype.sizeInBytes = function (t) { + return Te.test(t) + ? void 0 !== window.TextEncoder + ? new TextEncoder().encode(t).length + : new Blob([t]).size + : t.length; + }), + (t.prototype.addOrUpdate = function (t, e) { + var n = this.process(t), + i = n.processedMessage, + o = n.messageBytesSize; + o >= this.maxMessageSize + ? r.warn( + "Discarded a message whose size was bigger than the maximum allowed size ".concat( + this.maxMessageSize, + "KB." + ) + ) + : (this.hasMessageFor(e) && this.remove(e), + this.willReachedBytesLimitWith(o) && + this.flush("willReachedBytesLimitWith"), + this.push(i, o, e), + this.isFull() && this.flush("isFull")); + }), + (t.prototype.process = function (t) { + var e = A(t), + n = this.sizeInBytes(e); + return { processedMessage: e, messageBytesSize: n }; + }), + (t.prototype.push = function (t, e, n) { + this.bufferMessageCount > 0 && + (this.bufferBytesSize += 1), + void 0 !== n + ? (this.upsertBuffer[n] = t) + : this.pushOnlyBuffer.push(t), + (this.bufferBytesSize += e), + (this.bufferMessageCount += 1); + }), + (t.prototype.remove = function (t) { + var e = this.upsertBuffer[t]; + delete this.upsertBuffer[t]; + var n = this.sizeInBytes(e); + (this.bufferBytesSize -= n), + (this.bufferMessageCount -= 1), + this.bufferMessageCount > 0 && + (this.bufferBytesSize -= 1); + }), + (t.prototype.hasMessageFor = function (t) { + return ( + void 0 !== t && void 0 !== this.upsertBuffer[t] + ); + }), + (t.prototype.willReachedBytesLimitWith = function (t) { + return ( + this.bufferBytesSize + t + 1 >= this.bytesLimit + ); + }), + (t.prototype.isFull = function () { + return ( + this.bufferMessageCount === this.maxSize || + this.bufferBytesSize >= this.bytesLimit + ); + }), + (t.prototype.flushPeriodically = function () { + var t = this; + setTimeout( + Tt(function () { + t.flush("flushPeriodically"), + t.flushPeriodically(); + }), + this.flushTimeout + ); + }), + (t.prototype.flushOnVisibilityHidden = function () { + var t = this; + navigator.sendBeacon && + (q( + window, + "beforeunload", + this.beforeUnloadCallback + ), + q(document, "visibilitychange", function () { + "hidden" === document.visibilityState && + t.flush("visibilitychange"); + }), + q(window, "beforeunload", function () { + return t.flush("beforeunload"); + })); + }), + t + ); + })(), + Ce = !1, + je = (function () { + function t(t, e) { + (this.endpointBuilder = t), (this.bytesLimit = e); + } + return ( + (t.prototype.send = function (t, e, n) { + var r = this.endpointBuilder.build(), + i = + !!navigator.sendBeacon && + e < this.bytesLimit; + if (i) + try { + var o = navigator.sendBeacon(r, t); + if (o) return; + } catch (u) { + Me(u); + } + var a = function (t) { + var o = + null === t || void 0 === t + ? void 0 + : t.currentTarget; + (o.status >= 200 && o.status < 300) || + Ce || + ((Ce = !0), + Ct("XHR fallback failed", { + on_line: navigator.onLine, + size: e, + url: r, + try_beacon: i, + flush_reason: n, + event: { + is_trusted: t.isTrusted, + total: t.total, + loaded: t.loaded, + }, + request: { + status: o.status, + ready_state: o.readyState, + response_text: + o.responseText.slice( + 0, + 512 + ), + }, + })); + }, + s = new XMLHttpRequest(); + s.addEventListener( + "loadend", + Tt(function (t) { + return a(t); + }) + ), + s.open("POST", r, !0), + s.send(t); + }), + t + ); + })(), + Ie = !1; + function Me(t) { + Ie || ((Ie = !0), jt(t)); + } + function Le(t, e, n) { + var r, + i = o(e); + function o(e) { + return new Ae( + new je(e, t.batchBytesLimit), + t.maxBatchSize, + t.batchBytesLimit, + t.maxMessageSize, + t.flushTimeout + ); + } + return ( + n && (r = o(n)), + { + add: function (t) { + i.add(t), r && r.add(t); + }, + } + ); + } + function Pe() { + var t = Ne(), + e = new st(function () { + if (t) { + var n = new t( + Tt(function () { + return e.notify(); + }) + ); + return ( + n.observe(document, { + attributes: !0, + characterData: !0, + childList: !0, + subtree: !0, + }), + function () { + return n.disconnect(); + } + ); + } + }); + return e; + } + function Ne() { + var t, + e = window; + if (e.Zone) { + var n = e.Zone.__symbol__("MutationObserver"); + t = e[n]; + } + return t || (t = e.MutationObserver), t; + } + var Re = "initial_document", + $e = [ + [ + "document", + function (t) { + return Re === t; + }, + ], + [ + "xhr", + function (t) { + return "xmlhttprequest" === t; + }, + ], + [ + "fetch", + function (t) { + return "fetch" === t; + }, + ], + [ + "beacon", + function (t) { + return "beacon" === t; + }, + ], + [ + "css", + function (t, e) { + return /\.css$/i.test(e); + }, + ], + [ + "js", + function (t, e) { + return /\.js$/i.test(e); + }, + ], + [ + "image", + function (t, e) { + return ( + j(["image", "img", "icon"], t) || + null !== + /\.(gif|jpg|jpeg|tiff|png|svg|ico)$/i.exec( + e + ) + ); + }, + ], + [ + "font", + function (t, e) { + return null !== /\.(woff|eot|woff2|ttf)$/i.exec(e); + }, + ], + [ + "media", + function (t, e) { + return ( + j(["audio", "video"], t) || + null !== /\.(mp3|mp4)$/i.exec(e) + ); + }, + ], + ]; + function De(t) { + var e = t.name; + if (!te(e)) + return ( + Ct('Failed to construct URL for "'.concat(t.name, '"')), + "other" + ); + for (var n = ne(e), r = 0, i = $e; r < i.length; r++) { + var o = i[r], + a = o[0], + s = o[1]; + if (s(t.initiatorType, n)) return a; + } + return "other"; + } + function Fe() { + for (var t = [], e = 0; e < arguments.length; e++) + t[e] = arguments[e]; + for (var n = 1; n < t.length; n += 1) + if (t[n - 1] > t[n]) return !1; + return !0; + } + function ze(t) { + return ( + "xmlhttprequest" === t.initiatorType || + "fetch" === t.initiatorType + ); + } + function Ue(t) { + var e = t.duration, + n = t.startTime, + r = t.responseEnd; + return dt(0 === e && n < r ? mt(n, r) : e); + } + function Be(t) { + var e = Ve(t); + if (e) { + var n = e.startTime, + r = e.fetchStart, + i = e.redirectStart, + o = e.redirectEnd, + a = e.domainLookupStart, + s = e.domainLookupEnd, + u = e.connectStart, + c = e.secureConnectionStart, + f = e.connectEnd, + l = e.requestStart, + d = e.responseStart, + p = e.responseEnd, + h = { download: qe(n, d, p), first_byte: qe(n, l, d) }; + return ( + f !== r && + ((h.connect = qe(n, u, f)), + Fe(u, c, f) && (h.ssl = qe(n, c, f))), + s !== r && (h.dns = qe(n, a, s)), + He(t) && (h.redirect = qe(n, i, o)), + h + ); + } + } + function Ve(t) { + if ( + Fe( + t.startTime, + t.fetchStart, + t.domainLookupStart, + t.domainLookupEnd, + t.connectStart, + t.connectEnd, + t.requestStart, + t.responseStart, + t.responseEnd + ) + ) { + if (!He(t)) return t; + var e = t.redirectStart, + n = t.redirectEnd; + if ( + (e < t.startTime && (e = t.startTime), + n < t.startTime && (n = t.fetchStart), + Fe(t.startTime, e, n, t.fetchStart)) + ) + return x({}, t, { redirectEnd: n, redirectStart: e }); + } + } + function He(t) { + return t.fetchStart !== t.startTime; + } + function qe(t, e, n) { + return { duration: dt(mt(e, n)), start: dt(mt(t, e)) }; + } + function We(t) { + if (t.startTime < t.responseStart) return t.decodedBodySize; + } + function Ge(t, e) { + return e && !t.isIntakeUrl(e); + } + var Ze = 2 * y; + function Je(t) { + var e = Ke(t) || Xe(t); + if (e && !(e.traceTime <= Date.now() - Ze)) return e.traceId; + } + function Ke(t) { + var e = t.querySelector("meta[name=dd-trace-id]"), + n = t.querySelector("meta[name=dd-trace-time]"); + return Ye(e && e.content, n && n.content); + } + function Xe(t) { + var e = Qe(t); + if (e) return Ye(V(e, "trace-id"), V(e, "trace-time")); + } + function Ye(t, e) { + var n = e && Number(e); + if (t && n) return { traceId: t, traceTime: n }; + } + function Qe(t) { + for (var e = 0; e < t.childNodes.length; e += 1) { + var n = tn(t.childNodes[e]); + if (n) return n; + } + if (t.body) + for (e = t.body.childNodes.length - 1; e >= 0; e -= 1) { + var r = t.body.childNodes[e]; + n = tn(r); + if (n) return n; + if (!nn(r)) break; + } + } + function tn(t) { + if (t && en(t)) { + var e = /^\s*DATADOG;(.*?)\s*$/.exec(t.data); + if (e) return e[1]; + } + } + function en(t) { + return "#comment" === t.nodeName; + } + function nn(t) { + return "#text" === t.nodeName; + } + function rn() { + return ( + void 0 !== window.performance && "getEntries" in performance + ); + } + function on(t) { + return ( + window.PerformanceObserver && + void 0 !== PerformanceObserver.supportedEntryTypes && + PerformanceObserver.supportedEntryTypes.includes(t) + ); + } + function an() { + return "function" === typeof PerformanceEntry; + } + function sn(t, e) { + if ( + (un(function (n) { + dn(t, e, [n]); + }), + rn()) + ) { + var n = performance.getEntries(); + setTimeout( + Tt(function () { + return dn(t, e, n); + }) + ); + } + if (window.PerformanceObserver) { + var r = Tt(function (n) { + return dn(t, e, n.getEntries()); + }), + i = ["resource", "navigation", "longtask", "paint"], + o = [ + "largest-contentful-paint", + "first-input", + "layout-shift", + ]; + try { + o.forEach(function (t) { + var e = new PerformanceObserver(r); + e.observe({ type: t, buffered: !0 }); + }); + } catch (s) { + i.push.apply(i, o); + } + var a = new PerformanceObserver(r); + a.observe({ entryTypes: i }), + rn() && + "addEventListener" in performance && + performance.addEventListener( + "resourcetimingbufferfull", + function () { + performance.clearResourceTimings(); + } + ); + } + on("navigation") || + cn(function (n) { + dn(t, e, [n]); + }), + on("first-input") || + fn(function (n) { + dn(t, e, [n]); + }); + } + function un(t) { + G("interactive", function () { + var e, + n = { + entryType: "resource", + initiatorType: Re, + traceId: Je(document), + }; + if ( + on("navigation") && + performance.getEntriesByType("navigation").length > 0 + ) { + var r = performance.getEntriesByType("navigation")[0]; + e = x(r.toJSON(), n); + } else { + var i = ln(); + e = x( + i, + { + decodedBodySize: 0, + duration: i.responseEnd, + name: window.location.href, + startTime: 0, + }, + n + ); + } + t(e); + }); + } + function cn(t) { + function e() { + t(x(ln(), { entryType: "navigation" })); + } + G("complete", function () { + setTimeout(Tt(e)); + }); + } + function fn(t) { + var e = Date.now(), + n = !1, + r = W( + window, + [ + "click", + "mousedown", + "keydown", + "touchstart", + "pointerdown", + ], + function (t) { + if (t.cancelable) { + var e = { + entryType: "first-input", + processingStart: ht(), + startTime: t.timeStamp, + }; + "pointerdown" === t.type ? i(e) : o(e); + } + }, + { passive: !0, capture: !0 } + ).stop; + function i(t) { + W( + window, + ["pointerup", "pointercancel"], + function (e) { + "pointerup" === e.type && o(t); + }, + { once: !0 } + ); + } + function o(i) { + if (!n) { + (n = !0), r(); + var o = i.processingStart - i.startTime; + o >= 0 && o < Date.now() - e && t(i); + } + } + } + function ln() { + var t = {}, + e = performance.timing; + for (var n in e) + if (P(e[n])) { + var r = n, + i = e[r]; + t[r] = 0 === i ? 0 : gt(i); + } + return t; + } + function dn(t, e, n) { + var r = n.filter(function (t) { + return ( + "resource" === t.entryType || + "navigation" === t.entryType || + "paint" === t.entryType || + "longtask" === t.entryType || + "largest-contentful-paint" === t.entryType || + "first-input" === t.entryType || + "layout-shift" === t.entryType + ); + }), + i = r.filter(function (t) { + return !pn(t) && !hn(e, t); + }); + i.length && t.notify(0, i); + } + function pn(t) { + return "navigation" === t.entryType && t.loadEventEnd <= 0; + } + function hn(t, e) { + return "resource" === e.entryType && !Ge(t, e.name); + } + function vn(t, e, n) { + var r = 0, + i = !1; + return { + isLimitReached: function () { + if ( + (0 === r && + setTimeout(function () { + r = 0; + }, y), + (r += 1), + r <= e || i) + ) + return (i = !1), !1; + if (r === e + 1) { + i = !0; + try { + n({ + message: "Reached max number of " + .concat(t, "s by minute: ") + .concat(e), + source: tt.AGENT, + startClocks: vt(), + }); + } finally { + i = !1; + } + } + return !0; + }, + }; + } + function yn(t, e, n) { + var r = X(t), + i = n(r); + return ( + e.forEach(function (e) { + var n = mn(t, e), + i = mn(r, e), + o = Z(n), + a = Z(i); + a === o + ? gn(t, e, i) + : "object" !== o || + ("undefined" !== a && "null" !== a) || + gn(t, e, {}); + }), + i + ); + } + function mn(t, e) { + for (var n = t, r = 0, i = e.split("."); r < i.length; r++) { + var o = i[r]; + if (!bn(n, o)) return; + n = n[o]; + } + return n; + } + function gn(t, e, n) { + for (var r = t, i = e.split("."), o = 0; o < i.length; o += 1) { + var a = i[o]; + if (!bn(r, a)) return; + o !== i.length - 1 ? (r = r[a]) : (r[a] = n); + } + } + function bn(t, e) { + return "object" === typeof t && null !== t && e in t; + } + function _n() { + var t, + e = + null === (t = window.Cypress) || void 0 === t + ? void 0 + : t.env("traceId"); + if ("string" === typeof e) return { test_execution_id: e }; + } + var wn = [ + "view.url", + "view.referrer", + "action.target.name", + "error.message", + "error.stack", + "error.resource.url", + "resource.url", + ], + xn = wn.concat(["context"]); + function kn(t, e, n, r, i, o, a) { + var s, + u = function (t) { + e.notify(12, { error: t }); + }, + c = + ((s = {}), + (s["error"] = vn( + "error", + t.eventRateLimiterThreshold, + u + )), + (s["action"] = vn( + "action", + t.eventRateLimiterThreshold, + u + )), + s), + f = ke(), + l = _n(); + e.subscribe(10, function (s) { + var u = s.startTime, + d = s.rawRumEvent, + p = s.domainContext, + h = s.savedCommonContext, + v = s.customerContext, + y = r.findView(u), + m = i.findUrl(u), + g = n.findTrackedSession( + "view" !== d.type ? u : void 0 + ); + if (g && y && m) { + var b = h || a(), + _ = { + _dd: { + format_version: 2, + drift: lt(), + session: { plan: g.hasReplayPlan ? 2 : 1 }, + browser_sdk_version: Vt() + ? "4.8.1" + : void 0, + }, + application: { id: t.applicationId }, + date: pt(), + service: t.service, + source: "browser", + session: { + id: g.id, + type: f + ? "synthetics" + : l + ? "ci_test" + : "user", + }, + synthetics: f, + ci_test: l, + }, + w = o.findActionId(u); + kt("sub-apps") + ? (_.version = t.version) + : (delete y.service, delete y.version); + var x = + En(d) && w + ? Y(_, m, y, { action: { id: w } }, d) + : Y(_, m, y, d); + (x.context = Y(b.context, v)), + "has_replay" in x.session || + (x.session.has_replay = b.hasReplay), + D(b.user) || (x.usr = b.user), + Sn(x, t.beforeSend, p, c) && + (D(x.context) && delete x.context, + e.notify(11, x)); + } + }); + } + function Sn(t, e, n, i) { + var o; + if (e) { + var a = yn(t, "view" === t.type ? wn : xn, function (t) { + return e(t, n); + }); + if (!1 === a && "view" !== t.type) return !1; + !1 === a && + r.warn("Can't dismiss view events using beforeSend!"); + } + var s = + null === (o = i[t.type]) || void 0 === o + ? void 0 + : o.isLimitReached(); + return !s; + } + function En(t) { + return ( + -1 !== ["error", "resource", "long_task"].indexOf(t.type) + ); + } + var On = 500, + Tn = 2500, + An = []; + function Cn() { + document.hasFocus() && jn(); + var t = Mn(jn).stop, + e = Ln(In).stop; + return { + isInForegroundAt: Pn, + selectInForegroundPeriodsFor: Nn, + stop: function () { + (An = []), t(), e(); + }, + }; + } + function jn() { + if (!(An.length > Tn)) { + var t = An[An.length - 1], + e = ht(); + (void 0 !== t && void 0 === t.end) || An.push({ start: e }); + } + } + function In() { + if (0 !== An.length) { + var t = An[An.length - 1], + e = ht(); + void 0 === t.end && (t.end = e); + } + } + function Mn(t) { + return q(window, "focus", function (e) { + e.isTrusted && t(); + }); + } + function Ln(t) { + return q(window, "blur", function (e) { + e.isTrusted && t(); + }); + } + function Pn(t) { + for (var e = An.length - 1; e >= 0; e--) { + var n = An[e]; + if (void 0 !== n.end && t > n.end) break; + if (t > n.start && (void 0 === n.end || t < n.end)) + return !0; + } + return !1; + } + function Nn(t, e) { + for ( + var n = t + e, + r = [], + i = Math.max(0, An.length - On), + o = An.length - 1; + o >= i; + o-- + ) { + var a = An[o]; + if (void 0 !== a.end && t > a.end) break; + if (!(n < a.start)) { + var s = t > a.start ? t : a.start, + u = mt(t, s), + c = void 0 === a.end || n < a.end ? n : a.end, + f = mt(s, c); + r.unshift({ start: dt(u), duration: dt(f) }); + } + } + return r; + } + function Rn(t, e, n, r, i) { + return { + get: function (o) { + var a = n.findView(o), + s = i.findUrl(o), + u = e.findTrackedSession(o); + if (u && a && s) { + var c = r.findActionId(o); + return { + application_id: t, + session_id: u.id, + user_action: c ? { id: c } : void 0, + view: x({}, a.view, s.view), + }; + } + }, + }; + } + var $n = (function () { + function t() { + this.callbacks = {}; + } + return ( + (t.prototype.notify = function (t, e) { + var n = this.callbacks[t]; + n && + n.forEach(function (t) { + return t(e); + }); + }), + (t.prototype.subscribe = function (t, e) { + var n = this; + return ( + this.callbacks[t] || (this.callbacks[t] = []), + this.callbacks[t].push(e), + { + unsubscribe: function () { + n.callbacks[t] = n.callbacks[t].filter( + function (t) { + return e !== t; + } + ); + }, + } + ); + }), + t + ); + })(); + function Dn() { + return Boolean(document.documentMode); + } + function Fn() { + return ( + !!window.chrome || + /HeadlessChrome/.test(window.navigator.userAgent) + ); + } + var zn, + Un = /^([a-z]+)=([a-z0-9-]+)$/, + Bn = "&", + Vn = "_dd_s", + Hn = 10, + qn = 100, + Wn = []; + function Gn(t, e) { + var n; + if ((void 0 === e && (e = 0), zn || (zn = t), t === zn)) + if (e >= qn) Kn(); + else { + var r, + i = tr(); + if (Zn()) { + if (i.lock) return void Jn(t, e); + if ( + ((r = S()), + (i.lock = r), + Yn(i, t.options), + (i = tr()), + i.lock !== r) + ) + return void Jn(t, e); + } + var o = t.process(i); + if (Zn() && ((i = tr()), i.lock !== r)) Jn(t, e); + else { + if ( + (o && Xn(o, t.options), Zn() && (!o || !nr(o))) + ) { + if (((i = tr()), i.lock !== r)) + return void Jn(t, e); + delete i.lock, Yn(i, t.options), (o = i); + } + null === (n = t.after) || + void 0 === n || + n.call(t, o || i), + Kn(); + } + } + else Wn.push(t); + } + function Zn() { + return Fn(); + } + function Jn(t, e) { + setTimeout( + Tt(function () { + Gn(t, e + 1); + }), + Hn + ); + } + function Kn() { + zn = void 0; + var t = Wn.shift(); + t && Gn(t); + } + function Xn(t, e) { + nr(t) + ? rr(e) + : ((t.expire = String(Date.now() + ir)), Yn(t, e)); + } + function Yn(t, e) { + Zt(Vn, Qn(t), ir, e); + } + function Qn(t) { + return $(t) + .map(function (t) { + var e = t[0], + n = t[1]; + return "".concat(e, "=").concat(n); + }) + .join(Bn); + } + function tr() { + var t = Jt(Vn), + e = {}; + return ( + er(t) && + t.split(Bn).forEach(function (t) { + var n = Un.exec(t); + if (null !== n) { + var r = n[1], + i = n[2]; + e[r] = i; + } + }), + e + ); + } + function er(t) { + return void 0 !== t && (-1 !== t.indexOf(Bn) || Un.test(t)); + } + function nr(t) { + return D(t); + } + function rr(t) { + Zt(Vn, "", 0, t); + } + var ir = 15 * y, + or = 4 * m; + function ar(t, e, n) { + var r = new st(), + i = new st(), + o = setInterval(Tt(c), Gt), + a = y(); + function s() { + var e; + Gn({ + options: t, + process: function (t) { + var n = f(t); + return (e = l(n)), n; + }, + after: function (t) { + e && !d() && v(t), (a = t); + }, + }); + } + function u() { + Gn({ + options: t, + process: function (t) { + return d() ? f(t) : void 0; + }, + }); + } + function c() { + Gn({ + options: t, + process: function (t) { + return m(t) ? void 0 : {}; + }, + after: f, + }); + } + function f(t) { + return m(t) || (t = {}), d() && (p(t) ? h() : (a = t)), t; + } + function l(t) { + var r = n(t[e]), + i = r.trackingType, + o = r.isTracked; + return ( + (t[e] = i), + o && + !t.id && + ((t.id = S()), (t.created = String(Date.now()))), + o + ); + } + function d() { + return void 0 !== a[e]; + } + function p(t) { + return a.id !== t.id || a[e] !== t[e]; + } + function h() { + (a = {}), i.notify(); + } + function v(t) { + (a = t), r.notify(); + } + function y() { + var t = tr(); + return m(t) ? t : {}; + } + function m(t) { + return ( + (void 0 === t.created || + Date.now() - Number(t.created) < or) && + (void 0 === t.expire || Date.now() < Number(t.expire)) + ); + } + return { + expandOrRenewSession: w(Tt(s), Gt).throttled, + expandSession: u, + getSession: function () { + return a; + }, + renewObservable: r, + expireObservable: i, + stop: function () { + clearInterval(o); + }, + }; + } + var sr, + ur = 1 / 0, + cr = y, + fr = (function () { + function t(t) { + var e = this; + (this.expireDelay = t), + (this.entries = []), + (this.clearOldContextsInterval = setInterval( + function () { + return e.clearOldContexts(); + }, + cr + )); + } + return ( + (t.prototype.add = function (t, e) { + var n = this, + r = { + context: t, + startTime: e, + endTime: ur, + remove: function () { + var t = n.entries.indexOf(r); + t >= 0 && n.entries.splice(t, 1); + }, + close: function (t) { + r.endTime = t; + }, + }; + return this.entries.unshift(r), r; + }), + (t.prototype.find = function (t) { + void 0 === t && (t = ur); + for ( + var e = 0, n = this.entries; + e < n.length; + e++ + ) { + var r = n[e]; + if (r.startTime <= t) { + if (t <= r.endTime) return r.context; + break; + } + } + }), + (t.prototype.closeActive = function (t) { + var e = this.entries[0]; + e && e.endTime === ur && e.close(t); + }), + (t.prototype.findAll = function (t) { + return ( + void 0 === t && (t = ur), + this.entries + .filter(function (e) { + return ( + e.startTime <= t && t <= e.endTime + ); + }) + .map(function (t) { + return t.context; + }) + ); + }), + (t.prototype.reset = function () { + this.entries = []; + }), + (t.prototype.stop = function () { + clearInterval(this.clearOldContextsInterval); + }), + (t.prototype.clearOldContexts = function () { + var t = ht() - this.expireDelay; + while ( + this.entries.length > 0 && + this.entries[this.entries.length - 1].endTime < + t + ) + this.entries.pop(); + }), + t + ); + })(), + lr = or; + function dr(t) { + var e = new fr(lr); + function n(t) { + return { + service: t.service, + version: t.version, + view: { id: t.id, name: t.name }, + }; + } + return ( + t.subscribe(2, function (t) { + e.add(n(t), t.startClocks.relative); + }), + t.subscribe(4, function (t) { + var n = t.endClocks; + e.closeActive(n.relative); + }), + t.subscribe(8, function () { + e.reset(); + }), + { + findView: function (t) { + return e.find(t); + }, + stop: function () { + e.stop(); + }, + } + ); + } + function pr(t, e, n) { + var r = t[e], + i = n(r), + o = function () { + return i.apply(this, arguments); + }; + return ( + (t[e] = o), + { + stop: function () { + t[e] === o ? (t[e] = r) : (i = r); + }, + } + ); + } + function hr(t, e, n) { + var r = n.before, + i = n.after; + return pr(t, e, function (t) { + return function () { + var e, + n = arguments; + return ( + r && At(r, this, n), + "function" === typeof t && (e = t.apply(this, n)), + i && At(i, this, n), + e + ); + }; + }); + } + function vr(t, e, n) { + var r = Object.getOwnPropertyDescriptor(t, e); + if (!r || !r.set || !r.configurable) return { stop: T }; + var i = function (t, e) { + setTimeout( + Tt(function () { + n(t, e); + }), + 0 + ); + }, + o = function (t) { + r.set.call(this, t), i(this, t); + }; + return ( + Object.defineProperty(t, e, { set: o }), + { + stop: function () { + var n; + (null === + (n = Object.getOwnPropertyDescriptor(t, e)) || + void 0 === n + ? void 0 + : n.set) === o + ? Object.defineProperty(t, e, r) + : (i = T); + }, + } + ); + } + var yr, + mr = new WeakMap(); + function gr() { + return sr || (sr = br()), sr; + } + function br() { + var t = new st(function () { + var e = hr(XMLHttpRequest.prototype, "open", { + before: _r, + }).stop, + n = hr(XMLHttpRequest.prototype, "send", { + before: function () { + wr.call(this, t); + }, + }).stop, + r = hr(XMLHttpRequest.prototype, "abort", { + before: xr, + }).stop; + return function () { + e(), n(), r(); + }; + }); + return t; + } + function _r(t, e) { + mr.set(this, { + state: "open", + method: t, + url: Qt(e.toString()), + }); + } + function wr(t) { + var e = this, + n = mr.get(this); + if (n) { + var r = n; + (r.state = "start"), + (r.startTime = ht()), + (r.startClocks = vt()), + (r.isAborted = !1), + (r.xhr = this); + var i = !1, + o = hr(this, "onreadystatechange", { + before: function () { + this.readyState === XMLHttpRequest.DONE && a(); + }, + }).stop, + a = Tt(function () { + if ( + (e.removeEventListener("loadend", a), o(), !i) + ) { + i = !0; + var s = n; + (s.state = "complete"), + (s.duration = mt( + r.startClocks.timeStamp, + pt() + )), + (s.status = e.status), + t.notify(k(s)); + } + }); + this.addEventListener("loadend", a), t.notify(r); + } + } + function xr() { + var t = mr.get(this); + t && (t.isAborted = !0); + } + function kr() { + return yr || (yr = Sr()), yr; + } + function Sr() { + var t = new st(function () { + if (window.fetch) { + var e = pr(window, "fetch", function (e) { + return function (n, r) { + var i, + o = At(Er, null, [t, n, r]); + return ( + o + ? ((i = e.call(this, o.input, o.init)), + At(Or, null, [t, i, o])) + : (i = e.call(this, n, r)), + i + ); + }; + }).stop; + return e; + } + }); + return t; + } + function Er(t, e, n) { + var r = + (n && n.method) || + ("object" === typeof e && e.method) || + "GET", + i = Qt(("object" === typeof e && e.url) || e), + o = vt(), + a = { + state: "start", + init: n, + input: e, + method: r, + startClocks: o, + url: i, + }; + return t.notify(a), a; + } + function Or(t, e, n) { + var r = function (e) { + var r = n; + (r.state = "complete"), + (r.duration = mt(r.startClocks.timeStamp, pt())), + "stack" in e || e instanceof Error + ? ((r.status = 0), + (r.isAborted = + e instanceof DOMException && + e.code === DOMException.ABORT_ERR), + (r.error = e), + t.notify(r)) + : "status" in e && + ((r.response = e), + (r.responseType = e.type), + (r.status = e.status), + (r.isAborted = !1), + t.notify(r)); + }; + e.then(Tt(r), Tt(r)); + } + function Tr(t) { + 0 !== t.status || + t.isAborted || + ((t.traceId = void 0), (t.spanId = void 0)); + } + function Ar(t, e) { + return { + clearTracingIfNeeded: Tr, + traceFetch: function (n) { + return Cr(t, n, e, function (t) { + var e; + if ( + n.input instanceof Request && + !(null === (e = n.init) || void 0 === e + ? void 0 + : e.headers) + ) + (n.input = new Request(n.input)), + Object.keys(t).forEach(function (e) { + n.input.headers.append(e, t[e]); + }); + else { + n.init = k(n.init); + var r = []; + n.init.headers instanceof Headers + ? n.init.headers.forEach(function (t, e) { + r.push([e, t]); + }) + : Array.isArray(n.init.headers) + ? n.init.headers.forEach(function (t) { + r.push(t); + }) + : n.init.headers && + Object.keys(n.init.headers).forEach( + function (t) { + r.push([t, n.init.headers[t]]); + } + ), + (n.init.headers = r.concat($(t))); + } + }); + }, + traceXhr: function (n, r) { + return Cr(t, n, e, function (t) { + Object.keys(t).forEach(function (e) { + r.setRequestHeader(e, t[e]); + }); + }); + }, + }; + } + function Cr(t, e, n, r) { + Ir() && + jr(t, e.url) && + n.findTrackedSession() && + ((e.traceId = new Pr()), + (e.spanId = new Pr()), + r(Lr(e.traceId, e.spanId))); + } + function jr(t, e) { + for ( + var n = ee(e), r = 0, i = t.allowedTracingOrigins; + r < i.length; + r++ + ) { + var o = i[r]; + if (n === o || (o instanceof RegExp && o.test(n))) + return !0; + } + return !1; + } + function Ir() { + return void 0 !== Mr(); + } + function Mr() { + return window.crypto || window.msCrypto; + } + function Lr(t, e) { + return { + "x-datadog-origin": "rum", + "x-datadog-parent-id": e.toDecimalString(), + "x-datadog-sampled": "1", + "x-datadog-sampling-priority": "1", + "x-datadog-trace-id": t.toDecimalString(), + }; + } + var Pr = (function () { + function t() { + (this.buffer = new Uint8Array(8)), + Mr().getRandomValues(this.buffer), + (this.buffer[0] = 127 & this.buffer[0]); + } + return ( + (t.prototype.toString = function (t) { + var e = this.readInt32(0), + n = this.readInt32(4), + r = ""; + do { + var i = (e % t) * 4294967296 + n; + (e = Math.floor(e / t)), + (n = Math.floor(i / t)), + (r = (i % t).toString(t) + r); + } while (e || n); + return r; + }), + (t.prototype.toDecimalString = function () { + return this.toString(10); + }), + (t.prototype.readInt32 = function (t) { + return ( + 16777216 * this.buffer[t] + + (this.buffer[t + 1] << 16) + + (this.buffer[t + 2] << 8) + + this.buffer[t + 3] + ); + }), + t + ); + })(), + Nr = 1; + function Rr(t, e, n) { + var r = Ar(e, n); + $r(t, e, r), Dr(t, e, r); + } + function $r(t, e, n) { + var r = gr().subscribe(function (r) { + var i = r; + if (Ge(e, i.url)) + switch (i.state) { + case "start": + n.traceXhr(i, i.xhr), + (i.requestIndex = Fr()), + t.notify(5, { + requestIndex: i.requestIndex, + }); + break; + case "complete": + n.clearTracingIfNeeded(i), + t.notify(6, { + duration: i.duration, + method: i.method, + requestIndex: i.requestIndex, + spanId: i.spanId, + startClocks: i.startClocks, + status: i.status, + traceId: i.traceId, + type: "xhr", + url: i.url, + xhr: i.xhr, + }); + break; + } + }); + return { + stop: function () { + return r.unsubscribe(); + }, + }; + } + function Dr(t, e, n) { + var r = kr().subscribe(function (r) { + var i = r; + if (Ge(e, i.url)) + switch (i.state) { + case "start": + n.traceFetch(i), + (i.requestIndex = Fr()), + t.notify(5, { + requestIndex: i.requestIndex, + }); + break; + case "complete": + n.clearTracingIfNeeded(i), + t.notify(6, { + duration: i.duration, + method: i.method, + requestIndex: i.requestIndex, + responseType: i.responseType, + spanId: i.spanId, + startClocks: i.startClocks, + status: i.status, + traceId: i.traceId, + type: "fetch", + url: i.url, + response: i.response, + init: i.init, + input: i.input, + }); + break; + } + }); + return { + stop: function () { + return r.unsubscribe(); + }, + }; + } + function Fr() { + var t = Nr; + return (Nr += 1), t; + } + function zr(t, e) { + void 0 === e && (e = T); + var n = { + errorCount: 0, + longTaskCount: 0, + resourceCount: 0, + userActionCount: 0, + }, + r = t.subscribe(11, function (t) { + var r = t.type; + switch (r) { + case "error": + (n.errorCount += 1), e(n); + break; + case "action": + (n.userActionCount += 1), e(n); + break; + case "long_task": + (n.longTaskCount += 1), e(n); + break; + case "resource": + (n.resourceCount += 1), e(n); + break; + } + }); + return { + stop: function () { + r.unsubscribe(); + }, + eventCounts: n, + }; + } + var Ur = 100, + Br = 100; + function Vr(t, e, n, r) { + var i = qr(t, e); + return Hr(i, n, r); + } + function Hr(t, e, n) { + var r, + i = !1, + o = setTimeout( + Tt(function () { + return c({ hadActivity: !1 }); + }), + Ur + ), + a = + n && + setTimeout( + Tt(function () { + return c({ hadActivity: !0, end: pt() }); + }), + n + ), + s = t.subscribe(function (t) { + var e = t.isBusy; + clearTimeout(o), clearTimeout(r); + var n = pt(); + e || + (r = setTimeout( + Tt(function () { + return c({ hadActivity: !0, end: n }); + }), + Br + )); + }), + u = function () { + (i = !0), + clearTimeout(o), + clearTimeout(r), + clearTimeout(a), + s.unsubscribe(); + }; + function c(t) { + i || (u(), e(t)); + } + return { stop: u }; + } + function qr(t, e) { + var n = new st(function () { + var n, + i = [], + o = 0; + return ( + i.push( + e.subscribe(function () { + return r(o); + }), + t.subscribe(0, function (t) { + t.some(function (t) { + var e = t.entryType; + return "resource" === e; + }) && r(o); + }), + t.subscribe(5, function (t) { + void 0 === n && (n = t.requestIndex), r(++o); + }), + t.subscribe(6, function (t) { + void 0 === n || t.requestIndex < n || r(--o); + }) + ), + function () { + return i.forEach(function (t) { + return t.unsubscribe(); + }); + } + ); + }); + function r(t) { + n.notify({ isBusy: t > 0 }); + } + return n; + } + var Wr = "data-dd-action-name"; + function Gr(t, e) { + return ( + Zr(t, Wr) || + (e && Zr(t, e)) || + ti(t, e, Xr) || + ti(t, e, Yr) || + "" + ); + } + function Zr(t, e) { + var n; + if (si()) n = t.closest("[".concat(e, "]")); + else { + var r = t; + while (r) { + if (r.hasAttribute(e)) { + n = r; + break; + } + r = r.parentElement; + } + } + if (n) { + var i = n.getAttribute(e); + return ni(ei(i.trim())); + } + } + var Jr, + Kr, + Xr = [ + function (t, e) { + if (ai()) { + if ( + "labels" in t && + t.labels && + t.labels.length > 0 + ) + return ii(t.labels[0], e); + } else if (t.id) { + var n = + t.ownerDocument && + t.ownerDocument.querySelector( + 'label[for="'.concat( + t.id.replace('"', '\\"'), + '"]' + ) + ); + return n && ii(n, e); + } + }, + function (t) { + if ("INPUT" === t.nodeName) { + var e = t, + n = e.getAttribute("type"); + if ( + "button" === n || + "submit" === n || + "reset" === n + ) + return e.value; + } + }, + function (t, e) { + if ( + "BUTTON" === t.nodeName || + "LABEL" === t.nodeName || + "button" === t.getAttribute("role") + ) + return ii(t, e); + }, + function (t) { + return t.getAttribute("aria-label"); + }, + function (t, e) { + var n = t.getAttribute("aria-labelledby"); + if (n) + return n + .split(/\s+/) + .map(function (e) { + return ri(t, e); + }) + .filter(function (t) { + return Boolean(t); + }) + .map(function (t) { + return ii(t, e); + }) + .join(" "); + }, + function (t) { + return t.getAttribute("alt"); + }, + function (t) { + return t.getAttribute("name"); + }, + function (t) { + return t.getAttribute("title"); + }, + function (t) { + return t.getAttribute("placeholder"); + }, + function (t, e) { + if ("options" in t && t.options.length > 0) + return ii(t.options[0], e); + }, + ], + Yr = [ + function (t, e) { + return ii(t, e); + }, + ], + Qr = 10; + function ti(t, e, n) { + var r = t, + i = 0; + while ( + i <= Qr && + r && + "BODY" !== r.nodeName && + "HTML" !== r.nodeName && + "HEAD" !== r.nodeName + ) { + for (var o = 0, a = n; o < a.length; o++) { + var s = a[o], + u = s(r, e); + if ("string" === typeof u) { + var c = u.trim(); + if (c) return ni(ei(c)); + } + } + if ("FORM" === r.nodeName) break; + (r = r.parentElement), (i += 1); + } + } + function ei(t) { + return t.replace(/\s+/g, " "); + } + function ni(t) { + return t.length > 100 ? "".concat(H(t, 100), " [...]") : t; + } + function ri(t, e) { + return t.ownerDocument + ? t.ownerDocument.getElementById(e) + : null; + } + function ii(t, e) { + if (!t.isContentEditable) { + if ("innerText" in t) { + var n = t.innerText, + r = function (e) { + for ( + var r = t.querySelectorAll(e), i = 0; + i < r.length; + i += 1 + ) { + var o = r[i]; + if ("innerText" in o) { + var a = o.innerText; + a && + a.trim().length > 0 && + (n = n.replace(a, "")); + } + } + }; + return ( + oi() || r("script, style"), + r("[".concat(Wr, "]")), + e && r("[".concat(e, "]")), + n + ); + } + return t.textContent; + } + } + function oi() { + return !Dn(); + } + function ai() { + return ( + void 0 === Jr && + (Jr = "labels" in HTMLInputElement.prototype), + Jr + ); + } + function si() { + return ( + void 0 === Kr && (Kr = "closest" in HTMLElement.prototype), + Kr + ); + } + var ui = 10 * v, + ci = 5 * y; + function fi(t, e, n) { + var r = n.actionNameAttribute, + i = new fr(ci); + t.subscribe(8, function () { + i.reset(); + }); + var o = li(function (n) { + if (kt("frustration-signals") || !i.find()) { + var o = Gr(n.target, r); + if (o) + var a = di( + t, + e, + "click", + o, + n, + function (t) { + s.close(gt(t)); + }, + function () { + s.remove(); + } + ), + s = i.add(a, a.startClocks.relative); + } + }).stop, + a = { + findActionId: function (t) { + var e; + return kt("frustration-signals") + ? i.findAll(t).map(function (t) { + return t.id; + }) + : null === (e = i.find(t)) || void 0 === e + ? void 0 + : e.id; + }, + }; + return { + stop: function () { + i.findAll().forEach(function (t) { + return t.discard(); + }), + o(); + }, + actionContexts: a, + }; + } + function li(t) { + return q( + window, + "click", + function (e) { + e.target instanceof Element && t(e); + }, + { capture: !0 } + ); + } + function di(t, e, n, r, i, o, a) { + var s, + u = S(), + c = vt(), + f = zr(t), + l = Vr( + t, + e, + function (t) { + t.hadActivity && c.timeStamp <= t.end + ? d(t.end) + : p(); + }, + ui + ).stop; + function d(e) { + h(); + var a = f.eventCounts; + t.notify(1, { + counts: { + errorCount: a.errorCount, + longTaskCount: a.longTaskCount, + resourceCount: a.resourceCount, + }, + duration: mt(c.timeStamp, e), + id: u, + name: r, + startClocks: c, + type: n, + event: i, + }), + o(e); + } + function p() { + h(), a(); + } + function h() { + l(), f.stop(), s && s.unsubscribe(); + } + return ( + kt("frustration-signals") || (s = t.subscribe(2, p)), + { discard: p, id: u, startClocks: c } + ); + } + function pi(t, e, n, r) { + t.subscribe(1, function (e) { + return t.notify(10, hi(e, r)); + }); + var i = { findActionId: T }; + return ( + n.trackInteractions && (i = fi(t, e, n).actionContexts), + { + addAction: function (e, n) { + t.notify( + 10, + x({ savedCommonContext: n }, hi(e, r)) + ); + }, + actionContexts: i, + } + ); + } + function hi(t, e) { + var n = vi(t) + ? { + action: { + error: { count: t.counts.errorCount }, + id: t.id, + loading_time: dt(t.duration), + long_task: { count: t.counts.longTaskCount }, + resource: { count: t.counts.resourceCount }, + }, + } + : void 0, + r = vi(t) ? void 0 : t.context, + i = Y( + { + action: { + id: S(), + target: { name: t.name }, + type: t.type, + }, + date: t.startClocks.timeStamp, + type: "action", + }, + n + ), + o = e.isInForegroundAt(t.startClocks.relative); + return ( + void 0 !== o && (i.view = { in_foreground: o }), + { + customerContext: r, + rawRumEvent: i, + startTime: t.startClocks.relative, + domainContext: vi(t) ? { event: t.event } : {}, + } + ); + } + function vi(t) { + return "custom" !== t.type; + } + var yi = + /^(?:[Uu]ncaught (?:exception: )?)?(?:((?:Eval|Internal|Range|Reference|Syntax|Type|URI|)Error): )?(.*)$/; + function mi(t) { + var e = gi(t).stop, + n = bi(t).stop; + return { + stop: function () { + e(), n(); + }, + }; + } + function gi(t) { + return hr(window, "onerror", { + before: function (e, n, r, i, a) { + var s; + if (a) (s = o(a)), t(s, a); + else { + var u, + c = { url: n, column: i, line: r }, + f = e; + if ("[object String]" === {}.toString.call(e)) { + var l = yi.exec(f); + l && ((u = l[1]), (f = l[2])); + } + (s = { + name: u, + message: "string" === typeof f ? f : void 0, + stack: [c], + }), + t(s, e); + } + }, + }); + } + function bi(t) { + return hr(window, "onunhandledrejection", { + before: function (e) { + var n = e.reason || "Empty reason", + r = o(n); + t(r, n); + }, + }); + } + function _i(t) { + return mi(function (e, n) { + var r = et(e, n, "Uncaught"), + i = r.stack, + o = r.message, + a = r.type; + t.notify({ + message: o, + stack: i, + type: a, + source: tt.SOURCE, + startClocks: vt(), + originalError: n, + handling: "unhandled", + }); + }); + } + var wi = { + log: "log", + debug: "debug", + info: "info", + warn: "warn", + error: "error", + }, + xi = {}; + function ki(t) { + var e = t.map(function (t) { + return xi[t] || (xi[t] = Si(t)), xi[t]; + }); + return ut.apply(void 0, e); + } + function Si(t) { + var e = new st(function () { + var n = console[t]; + return ( + (console[t] = function () { + for (var r = [], i = 0; i < arguments.length; i++) + r[i] = arguments[i]; + n.apply(console, r); + var o = it(); + At(function () { + e.notify(Ei(r, t, o)); + }); + }), + function () { + console[t] = n; + } + ); + }); + return e; + } + function Ei(t, e, n) { + var r, + i = t + .map(function (t) { + return Oi(t); + }) + .join(" "); + if (e === wi.error) { + var a = I(t, function (t) { + return t instanceof Error; + }); + (r = a ? nt(o(a)) : void 0), + (i = "console error: ".concat(i)); + } + return { api: e, message: i, stack: r, handlingStack: n }; + } + function Oi(t) { + return "string" === typeof t + ? t + : t instanceof Error + ? rt(o(t)) + : A(t, void 0, 2); + } + function Ti(t) { + var e = ki([wi.error]).subscribe(function (e) { + return t.notify({ + startClocks: vt(), + message: e.message, + stack: e.stack, + source: tt.CONSOLE, + handling: "handled", + handlingStack: e.handlingStack, + }); + }); + return { + stop: function () { + e.unsubscribe(); + }, + }; + } + var Ai, + Ci, + ji = { + intervention: "intervention", + deprecation: "deprecation", + cspViolation: "csp_violation", + }; + function Ii(t) { + var e = []; + j(t, ji.cspViolation) && e.push(Li()); + var n = t.filter(function (t) { + return t !== ji.cspViolation; + }); + return n.length && e.push(Mi(n)), ut.apply(void 0, e); + } + function Mi(t) { + var e = new st(function () { + if (window.ReportingObserver) { + var n = Tt(function (t) { + return t.forEach(function (t) { + e.notify(Pi(t)); + }); + }), + r = new window.ReportingObserver(n, { + types: t, + buffered: !0, + }); + return ( + r.observe(), + function () { + r.disconnect(); + } + ); + } + }); + return e; + } + function Li() { + var t = new st(function () { + var e = Tt(function (e) { + t.notify(Ni(e)); + }), + n = q(document, "securitypolicyviolation", e).stop; + return n; + }); + return t; + } + function Pi(t) { + var e = t.type, + n = t.body; + return { + type: e, + subtype: n.id, + message: "".concat(e, ": ").concat(n.message), + stack: Ri( + n.id, + n.message, + n.sourceFile, + n.lineNumber, + n.columnNumber + ), + }; + } + function Ni(t) { + var e = ji.cspViolation, + n = "'" + .concat(t.blockedURI, "' blocked by '") + .concat(t.effectiveDirective, "' directive"); + return { + type: ji.cspViolation, + subtype: t.effectiveDirective, + message: "".concat(e, ": ").concat(n), + stack: Ri( + t.effectiveDirective, + "" + .concat(n, ' of the policy "') + .concat(H(t.originalPolicy, 100, "..."), '"'), + t.sourceFile, + t.lineNumber, + t.columnNumber + ), + }; + } + function Ri(t, e, n, r, i) { + return ( + n && + nt({ + name: t, + message: e, + stack: [{ func: "?", url: n, line: r, column: i }], + }) + ); + } + function $i(t) { + var e = Ii([ji.cspViolation, ji.intervention]).subscribe( + function (e) { + return t.notify({ + startClocks: vt(), + message: e.message, + stack: e.stack, + type: e.subtype, + source: tt.REPORT, + handling: "unhandled", + }); + } + ); + return { + stop: function () { + e.unsubscribe(); + }, + }; + } + function Di(t, e) { + var n = new st(); + return ( + Ti(n), + _i(n), + $i(n), + n.subscribe(function (e) { + return t.notify(12, { error: e }); + }), + Fi(t, e) + ); + } + function Fi(t, e) { + return ( + t.subscribe(12, function (n) { + var r = n.error, + i = n.customerContext, + o = n.savedCommonContext; + t.notify( + 10, + x( + { customerContext: i, savedCommonContext: o }, + Ui(r, e) + ) + ); + }), + { + addError: function (e, n) { + var r = e.error, + i = e.handlingStack, + o = e.startClocks, + a = e.context, + s = zi(r, i, o); + t.notify(12, { + customerContext: a, + savedCommonContext: n, + error: s, + }); + }, + } + ); + } + function zi(t, e, n) { + var r = t instanceof Error ? o(t) : void 0; + return x( + { + startClocks: n, + source: tt.CUSTOM, + originalError: t, + handling: "handled", + }, + et(r, t, "Provided", e) + ); + } + function Ui(t, e) { + var n = { + date: t.startClocks.timeStamp, + error: { + id: S(), + message: t.message, + source: t.source, + stack: t.stack, + handling_stack: t.handlingStack, + type: t.type, + handling: t.handling, + source_type: "browser", + }, + type: "error", + }, + r = e.isInForegroundAt(t.startClocks.relative); + return ( + void 0 !== r && (n.view = { in_foreground: r }), + { + rawRumEvent: n, + startTime: t.startClocks.relative, + domainContext: { error: t.originalError }, + } + ); + } + function Bi(t, e) { + t.subscribe(0, function (n) { + for (var r = 0, i = n; r < i.length; r++) { + var o = i[r]; + if ("longtask" !== o.entryType) break; + var a = e.findTrackedSession(o.startTime); + if (!a || a.hasLitePlan) break; + var s = ct(o.startTime), + u = { + date: s.timeStamp, + long_task: { + id: S(), + duration: dt(o.duration), + }, + type: "long_task", + }; + t.notify(10, { + rawRumEvent: u, + startTime: s.relative, + domainContext: { performanceEntry: o.toJSON() }, + }); + } + }); + } + function Vi(t) { + if (performance && "getEntriesByName" in performance) { + var e = performance.getEntriesByName(t.url, "resource"); + if (e.length && "toJSON" in e[0]) { + var n = e + .map(function (t) { + return t.toJSON(); + }) + .filter(Ve) + .filter(function (e) { + return Wi( + e, + t.startClocks.relative, + qi({ + startTime: t.startClocks.relative, + duration: t.duration, + }) + ); + }); + return 1 === n.length + ? n[0] + : 2 === n.length && Hi(n) + ? n[1] + : void 0; + } + } + } + function Hi(t) { + return qi(t[0]) <= t[1].startTime; + } + function qi(t) { + return t.startTime + t.duration; + } + function Wi(t, e, n) { + var r = 1; + return t.startTime >= e - r && qi(t) <= n + r; + } + function Gi(t) { + t.subscribe(6, function (e) { + t.notify(10, Zi(e)); + }), + t.subscribe(0, function (e) { + for (var n = 0, r = e; n < r.length; n++) { + var i = r[n]; + "resource" !== i.entryType || + ze(i) || + t.notify(10, Ji(i)); + } + }); + } + function Zi(t) { + var e = "xhr" === t.type ? "xhr" : "fetch", + n = Vi(t), + r = n ? ct(n.startTime) : t.startClocks, + i = n ? Ki(n) : void 0, + o = Xi(t), + a = Y( + { + date: r.timeStamp, + resource: { + id: S(), + type: e, + duration: dt(t.duration), + method: t.method, + status_code: t.status, + url: t.url, + }, + type: "resource", + }, + o, + i + ); + return { + startTime: r.relative, + rawRumEvent: a, + domainContext: { + performanceEntry: n && Qi(n), + xhr: t.xhr, + response: t.response, + requestInput: t.input, + requestInit: t.init, + error: t.error, + }, + }; + } + function Ji(t) { + var e = De(t), + n = Ki(t), + r = Yi(t), + i = ct(t.startTime), + o = Y( + { + date: i.timeStamp, + resource: { id: S(), type: e, url: t.name }, + type: "resource", + }, + r, + n + ); + return { + startTime: i.relative, + rawRumEvent: o, + domainContext: { performanceEntry: Qi(t) }, + }; + } + function Ki(t) { + return { resource: x({ duration: Ue(t), size: We(t) }, Be(t)) }; + } + function Xi(t) { + var e = t.traceId && t.spanId; + if (e) + return { + _dd: { + span_id: t.spanId.toDecimalString(), + trace_id: t.traceId.toDecimalString(), + }, + }; + } + function Yi(t) { + return t.traceId ? { _dd: { trace_id: t.traceId } } : void 0; + } + function Qi(t) { + return an() && t instanceof PerformanceEntry && t.toJSON(), t; + } + function to(t) { + return ( + void 0 === t && (t = window), + Ai || + ("hidden" === document.visibilityState + ? (Ai = { timeStamp: 0 }) + : ((Ai = { timeStamp: 1 / 0 }), + (Ci = W( + t, + ["pagehide", "visibilitychange"], + function (t) { + ("pagehide" !== t.type && + "hidden" !== + document.visibilityState) || + ((Ai.timeStamp = t.timeStamp), Ci()); + }, + { capture: !0 } + ).stop))), + Ai + ); + } + var eo = 10 * y; + function no(t, e) { + var n = {}; + function r(t) { + x(n, t), e(n); + } + var i = ro(t, r).stop, + o = io(t, function (t) { + return r({ firstContentfulPaint: t }); + }).stop, + a = oo(t, window, function (t) { + r({ largestContentfulPaint: t }); + }).stop, + s = ao(t, function (t) { + var e = t.firstInputDelay, + n = t.firstInputTime; + r({ firstInputDelay: e, firstInputTime: n }); + }).stop; + return { + stop: function () { + i(), o(), a(), s(); + }, + }; + } + function ro(t, e) { + var n = t.subscribe(0, function (t) { + for (var n = 0, r = t; n < r.length; n++) { + var i = r[n]; + "navigation" === i.entryType && + e({ + domComplete: i.domComplete, + domContentLoaded: i.domContentLoadedEventEnd, + domInteractive: i.domInteractive, + loadEvent: i.loadEventEnd, + }); + } + }).unsubscribe; + return { stop: n }; + } + function io(t, e) { + var n = to(), + r = t.subscribe(0, function (t) { + var r = I(t, function (t) { + return ( + "paint" === t.entryType && + "first-contentful-paint" === t.name && + t.startTime < n.timeStamp && + t.startTime < eo + ); + }); + r && e(r.startTime); + }).unsubscribe; + return { stop: r }; + } + function oo(t, e, n) { + var r = to(), + i = 1 / 0, + o = W( + e, + ["pointerdown", "keydown"], + function (t) { + i = t.timeStamp; + }, + { capture: !0, once: !0 } + ).stop, + a = t.subscribe(0, function (t) { + var e = M(t, function (t) { + return ( + "largest-contentful-paint" === t.entryType && + t.startTime < i && + t.startTime < r.timeStamp && + t.startTime < eo + ); + }); + e && n(e.startTime); + }).unsubscribe; + return { + stop: function () { + o(), a(); + }, + }; + } + function ao(t, e) { + var n = to(), + r = t.subscribe(0, function (t) { + var r = I(t, function (t) { + return ( + "first-input" === t.entryType && + t.startTime < n.timeStamp + ); + }); + if (r) { + var i = mt(r.startTime, r.processingStart); + e({ + firstInputDelay: i >= 0 ? i : 0, + firstInputTime: r.startTime, + }); + } + }).unsubscribe; + return { stop: r }; + } + function so(t, e, n, r, i) { + var o, + a = { + eventCounts: { + errorCount: 0, + longTaskCount: 0, + resourceCount: 0, + userActionCount: 0, + }, + }, + s = zr(t, function (t) { + (a.eventCounts = t), n(); + }).stop, + u = uo(t, e, r, i, function (t) { + (a.loadingTime = t), n(); + }), + c = u.stop, + f = u.setLoadEvent; + return ( + lo() + ? ((a.cumulativeLayoutShift = 0), + (o = co(t, function (t) { + (a.cumulativeLayoutShift = t), n(); + }).stop)) + : (o = T), + { + stop: function () { + s(), c(), o(); + }, + setLoadEvent: f, + viewMetrics: a, + } + ); + } + function uo(t, e, n, r, i) { + var o = "initial_load" === n, + a = !0, + s = []; + function u() { + !a && !o && s.length > 0 && i(Math.max.apply(Math, s)); + } + var c = Vr(t, e, function (t) { + a && + ((a = !1), + t.hadActivity && s.push(mt(r.timeStamp, t.end)), + u()); + }).stop; + return { + stop: c, + setLoadEvent: function (t) { + o && ((o = !1), s.push(t), u()); + }, + }; + } + function co(t, e) { + var n = 0, + r = fo(), + i = t.subscribe(0, function (t) { + for (var i = 0, o = t; i < o.length; i++) { + var a = o[i]; + "layout-shift" !== a.entryType || + a.hadRecentInput || + (r.update(a), + r.value() > n && ((n = r.value()), e(O(n, 4)))); + } + }).unsubscribe; + return { stop: i }; + } + function fo() { + var t, + e, + n = 0; + return { + update: function (r) { + var i = + void 0 === t || + r.startTime - e >= v || + r.startTime - t >= 5 * v; + i + ? ((t = e = r.startTime), (n = r.value)) + : ((n += r.value), (e = r.startTime)); + }, + value: function () { + return n; + }, + }; + } + function lo() { + return on("layout-shift"); + } + var po = 3e3, + ho = 5 * y; + function vo(t, e, n, r, i, o) { + var a, + s = d(o), + u = s.stop, + c = s.initialView, + f = c, + l = h().stop; + function d(r) { + var i = yo(e, n, t, "initial_load", yt(), r), + o = no(e, function (t) { + i.updateTimings(t), i.scheduleUpdate(); + }).stop; + return { initialView: i, stop: o }; + } + function p(r, i) { + return yo(e, n, t, "route_change", r, i); + } + function h() { + e.subscribe(8, function () { + f.end(), + (f = p(void 0, { + name: f.name, + service: f.service, + version: f.version, + })); + }), + e.subscribe(9, function () { + f.end(), f.triggerUpdate(); + }); + var t = window.setInterval( + Tt(function () { + f.triggerUpdate(); + }), + ho + ); + return { + stop: function () { + clearInterval(t); + }, + }; + } + function v(t) { + return t.subscribe(function (t) { + var e = t.oldLocation, + n = t.newLocation; + if (go(e, n)) + return f.end(), f.triggerUpdate(), void (f = p()); + }); + } + return ( + i && (a = v(r)), + { + addTiming: function (t, e) { + void 0 === e && (e = pt()), + f.addTiming(t, e), + f.scheduleUpdate(); + }, + startView: function (t, e) { + f.end(e), f.triggerUpdate(), (f = p(e, t)); + }, + stop: function () { + null === a || void 0 === a || a.unsubscribe(), + u(), + l(), + f.end(); + }, + } + ); + } + function yo(t, e, n, r, i, o) { + void 0 === i && (i = vt()); + var a, + s, + u, + c, + f = S(), + l = {}, + d = {}, + p = 0, + h = k(n); + o && ((s = o.name), (u = o.service), (c = o.version)), + t.notify(2, { + id: f, + name: s, + startClocks: i, + service: u, + version: c, + }); + var v = w(Tt(O), po, { leading: !1 }), + y = v.throttled, + m = v.cancel, + g = so(t, e, y, r, i), + b = g.setLoadEvent, + _ = g.stop, + E = g.viewMetrics; + function O() { + p += 1; + var e = void 0 === a ? pt() : a.timeStamp; + t.notify( + 3, + x( + { + customTimings: d, + documentVersion: p, + id: f, + name: s, + service: u, + version: c, + loadingType: r, + location: h, + startClocks: i, + timings: l, + duration: mt(i.timeStamp, e), + isActive: void 0 === a, + }, + E + ) + ); + } + return ( + O(), + { + name: s, + service: u, + version: c, + scheduleUpdate: y, + end: function (e) { + void 0 === e && (e = vt()), + (a = e), + _(), + t.notify(4, { endClocks: a }); + }, + triggerUpdate: function () { + m(), O(); + }, + updateTimings: function (t) { + (l = t), void 0 !== t.loadEvent && b(t.loadEvent); + }, + addTiming: function (t, e) { + var n = _t(e) ? e : mt(i.timeStamp, e); + d[mo(t)] = n; + }, + } + ); + } + function mo(t) { + var e = t.replace(/[^a-zA-Z0-9-_.@$]/g, "_"); + return ( + e !== t && + r.warn( + "Invalid timing name: " + .concat(t, ", sanitized to: ") + .concat(e) + ), + e + ); + } + function go(t, e) { + return ( + t.pathname !== e.pathname || + (!bo(e.hash) && _o(e.hash) !== _o(t.hash)) + ); + } + function bo(t) { + var e = t.substr(1); + return !!document.getElementById(e); + } + function _o(t) { + var e = t.indexOf("?"); + return e < 0 ? t : t.slice(0, e); + } + function wo(t, e, n, r, i, o, a, s) { + return ( + t.subscribe(3, function (e) { + return t.notify(10, xo(e, o, a)); + }), + vo(n, t, r, i, !e.trackViewsManually, s) + ); + } + function xo(t, e, n) { + var r = n.getReplayStats(t.id), + i = { + _dd: { + document_version: t.documentVersion, + replay_stats: r, + }, + date: t.startClocks.timeStamp, + type: "view", + view: { + action: { count: t.eventCounts.userActionCount }, + cumulative_layout_shift: t.cumulativeLayoutShift, + dom_complete: dt(t.timings.domComplete), + dom_content_loaded: dt(t.timings.domContentLoaded), + dom_interactive: dt(t.timings.domInteractive), + error: { count: t.eventCounts.errorCount }, + first_contentful_paint: dt( + t.timings.firstContentfulPaint + ), + first_input_delay: dt(t.timings.firstInputDelay), + first_input_time: dt(t.timings.firstInputTime), + is_active: t.isActive, + name: t.name, + largest_contentful_paint: dt( + t.timings.largestContentfulPaint + ), + load_event: dt(t.timings.loadEvent), + loading_time: ko(dt(t.loadingTime)), + loading_type: t.loadingType, + long_task: { count: t.eventCounts.longTaskCount }, + resource: { count: t.eventCounts.resourceCount }, + time_spent: dt(t.duration), + in_foreground_periods: + e.selectInForegroundPeriodsFor( + t.startClocks.relative, + t.duration + ), + }, + session: { has_replay: !!r || void 0 }, + }; + return ( + D(t.customTimings) || + (i.view.custom_timings = F(t.customTimings, dt)), + { + rawRumEvent: i, + startTime: t.startClocks.relative, + domainContext: { location: t.location }, + } + ); + } + function ko(t) { + return P(t) && t < 0 ? void 0 : t; + } + var So = "_dd", + Eo = "_dd_r", + Oo = "_dd_l", + To = "rum", + Ao = "logs"; + function Co(t) { + var e = Jt(Vn), + n = Jt(So), + r = Jt(Eo), + i = Jt(Oo); + if (!e) { + var o = {}; + n && (o.id = n), + i && /^[01]$/.test(i) && (o[Ao] = i), + r && /^[012]$/.test(r) && (o[To] = r), + Xn(o, t); + } + } + var jo = y, + Io = or, + Mo = []; + function Lo(t, e, n) { + Co(t); + var r = ar(t, e, n); + Mo.push(function () { + return r.stop(); + }); + var i = new fr(Io); + function o() { + return { + id: r.getSession().id, + trackingType: r.getSession()[e], + }; + } + return ( + Mo.push(function () { + return i.stop(); + }), + r.renewObservable.subscribe(function () { + i.add(o(), ht()); + }), + r.expireObservable.subscribe(function () { + i.closeActive(ht()); + }), + r.expandOrRenewSession(), + i.add(o(), yt().relative), + Po(function () { + return r.expandOrRenewSession(); + }), + No(function () { + return r.expandSession(); + }), + { + findActiveSession: function (t) { + return i.find(t); + }, + renewObservable: r.renewObservable, + expireObservable: r.expireObservable, + } + ); + } + function Po(t) { + var e = W( + window, + ["click", "touchstart", "keydown", "scroll"], + t, + { capture: !0, passive: !0 } + ).stop; + Mo.push(e); + } + function No(t) { + var e = Tt(function () { + "visible" === document.visibilityState && t(); + }), + n = q(document, "visibilitychange", e).stop; + Mo.push(n); + var r = setInterval(e, jo); + Mo.push(function () { + clearInterval(r); + }); + } + var Ro = "rum"; + function $o(t, e) { + var n = Lo(t.cookieOptions, Ro, function (e) { + return Fo(t, e); + }); + return ( + n.expireObservable.subscribe(function () { + e.notify(7); + }), + n.renewObservable.subscribe(function () { + e.notify(8); + }), + { + findTrackedSession: function (t) { + var e = n.findActiveSession(t); + if (e && Uo(e.trackingType)) + return { + id: e.id, + hasReplayPlan: "1" === e.trackingType, + hasLitePlan: "2" === e.trackingType, + }; + }, + } + ); + } + function Do() { + var t = { + id: "00000000-aaaa-0000-aaaa-000000000000", + hasReplayPlan: !0, + hasLitePlan: !1, + }; + return { + findTrackedSession: function () { + return t; + }, + }; + } + function Fo(t, e) { + var n; + return ( + (n = zo(e) + ? e + : E(t.sampleRate) + ? E(t.replaySampleRate) + ? "1" + : "2" + : "0"), + { trackingType: n, isTracked: Uo(n) } + ); + } + function zo(t) { + return "0" === t || "1" === t || "2" === t; + } + function Uo(t) { + return "2" === t || "1" === t; + } + function Bo(t, e, n) { + var r = Vo(t, e); + e.subscribe(11, function (t) { + "view" === t.type ? r.upsert(t, t.view.id) : r.add(t); + }), + n.subscribe(function (t) { + return r.add(t); + }); + } + function Vo(t, e) { + var n, + r = o(t.rumEndpointBuilder, function () { + return e.notify(9); + }), + i = t.replica; + function o(e, n) { + return new Ae( + new je(e, t.batchBytesLimit), + t.maxBatchSize, + t.batchBytesLimit, + t.maxMessageSize, + t.flushTimeout, + n + ); + } + function a(t) { + return Y(t, { application: { id: i.applicationId } }); + } + return ( + void 0 !== i && (n = o(i.rumEndpointBuilder)), + { + add: function (t) { + r.add(t), n && n.add(a(t)); + }, + upsert: function (t, e) { + r.upsert(t, e), n && n.upsert(a(t), e); + }, + } + ); + } + function Ho(t) { + var e = Bt(); + t.subscribe(11, function (t) { + e.send("rum", t); + }); + } + var qo = or; + function Wo(t, e, n) { + var r, + i = new fr(qo); + t.subscribe(4, function (t) { + var e = t.endClocks; + i.closeActive(e.relative); + }), + t.subscribe(2, function (t) { + var e = t.startClocks, + o = n.href; + i.add( + a({ url: o, referrer: r || document.referrer }), + e.relative + ), + (r = o); + }); + var o = e.subscribe(function (t) { + var e = t.newLocation, + n = i.find(); + if (n) { + var r = ht(); + i.closeActive(r), + i.add( + a({ url: e.href, referrer: n.view.referrer }), + r + ); + } + }); + function a(t) { + var e = t.url, + n = t.referrer; + return { view: { url: e, referrer: n } }; + } + return { + findUrl: function (t) { + return i.find(t); + }, + stop: function () { + o.unsubscribe(), i.stop(); + }, + }; + } + function Go(t) { + var e = k(t), + n = new st(function () { + var t = Zo(r).stop, + e = Jo(r).stop; + return function () { + t(), e(); + }; + }); + function r() { + if (e.href !== t.href) { + var r = k(t); + n.notify({ newLocation: r, oldLocation: e }), (e = r); + } + } + return n; + } + function Zo(t) { + var e = hr(history, "pushState", { after: t }).stop, + n = hr(history, "replaceState", { after: t }).stop, + r = q(window, "popstate", t).stop; + return { + stop: function () { + e(), n(), r(); + }, + }; + } + function Jo(t) { + return q(window, "hashchange", t); + } + function Ko(t, e, n, r) { + var i = new $n(), + o = Xo(t); + o.setExternalContextProvider(function () { + var e; + return Y( + { + application_id: t.applicationId, + session: { + id: + null === (e = a.findTrackedSession()) || + void 0 === e + ? void 0 + : e.id, + }, + }, + f.findView(), + { view: { name: null } } + ); + }), + o.setTelemetryContextProvider(function () { + var e, n; + return { + application: { id: t.applicationId }, + session: { + id: + null === (e = a.findTrackedSession()) || + void 0 === e + ? void 0 + : e.id, + }, + view: { + id: + null === (n = f.findView()) || void 0 === n + ? void 0 + : n.view.id, + }, + action: { id: p.findActionId() }, + }; + }), + Vt() ? Ho(i) : Bo(t, i, o.telemetryEventObservable); + var a = Vt() ? Do() : $o(t, i), + s = Pe(), + u = Go(location), + c = Yo(i, t, location, a, u, s, e), + f = c.viewContexts, + l = c.foregroundContexts, + d = c.urlContexts, + p = c.actionContexts, + h = c.addAction; + Bi(i, a), Gi(i); + var v = wo(i, t, location, s, u, l, n, r), + y = v.addTiming, + m = v.startView, + g = Di(i, l).addError; + Rr(i, t, a), sn(i, t); + var b = Rn(t.applicationId, a, f, p, d); + return { + addAction: h, + addError: g, + addTiming: y, + startView: m, + lifeCycle: i, + viewContexts: f, + session: a, + getInternalContext: b.get, + }; + } + function Xo(t) { + var e, + n = Ot(t); + if (Vt()) { + var r = Bt(); + n.monitoringMessageObservable.subscribe(function (t) { + return r.send("internal_log", t); + }), + n.telemetryEventObservable.subscribe(function (t) { + return r.send("internal_telemetry", t); + }); + } else if (t.internalMonitoringEndpointBuilder) { + var i = Le( + t, + t.internalMonitoringEndpointBuilder, + null === (e = t.replica) || void 0 === e + ? void 0 + : e.internalMonitoringEndpointBuilder + ); + n.monitoringMessageObservable.subscribe(function (t) { + return i.add(t); + }); + } + return n; + } + function Yo(t, e, n, r, i, o, a) { + var s = dr(t), + u = Wo(t, i, n), + c = Cn(), + f = pi(t, o, e, c), + l = f.addAction, + d = f.actionContexts; + return ( + kn(e, t, r, s, u, d, a), + { + viewContexts: s, + foregroundContexts: c, + urlContexts: u, + addAction: l, + actionContexts: d, + stop: function () { + s.stop(), c.stop(); + }, + } + ); + } + var Qo = { + FullSnapshot: 2, + IncrementalSnapshot: 3, + Meta: 4, + Focus: 6, + ViewEnd: 7, + VisualViewport: 8, + }, + ta = { + IGNORE: "ignore", + HIDDEN: "hidden", + ALLOW: ye.ALLOW, + MASK: ye.MASK, + MASK_USER_INPUT: ye.MASK_USER_INPUT, + }, + ea = "data-dd-privacy", + na = "allow", + ra = "mask", + ia = "mask-user-input", + oa = "hidden", + aa = "dd-privacy-allow", + sa = "dd-privacy-mask", + ua = "dd-privacy-mask-user-input", + ca = "dd-privacy-hidden", + fa = "***", + la = + "data:image/gif;base64,R0lGODlhAQABAIAAAMLCwgAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==", + da = { + INPUT: !0, + OUTPUT: !0, + TEXTAREA: !0, + SELECT: !0, + OPTION: !0, + DATALIST: !0, + OPTGROUP: !0, + }, + pa = 1e5, + ha = "x"; + function va(t, e) { + var n = t.parentNode ? va(t.parentNode, e) : e, + r = ma(t); + return ya(r, n); + } + function ya(t, e) { + switch (e) { + case ta.HIDDEN: + case ta.IGNORE: + return e; + } + switch (t) { + case ta.ALLOW: + case ta.MASK: + case ta.MASK_USER_INPUT: + case ta.HIDDEN: + case ta.IGNORE: + return t; + default: + return e; + } + } + function ma(t) { + if (ba(t)) { + var e = t.getAttribute(ea); + if ("BASE" === t.tagName) return ta.ALLOW; + if ("INPUT" === t.tagName) { + var n = t; + if ( + "password" === n.type || + "email" === n.type || + "tel" === n.type + ) + return ta.MASK; + if ("hidden" === n.type) return ta.MASK; + var r = n.getAttribute("autocomplete"); + if (r && 0 === r.indexOf("cc-")) return ta.MASK; + } + return e === oa || t.classList.contains(ca) + ? ta.HIDDEN + : e === ra || t.classList.contains(sa) + ? ta.MASK + : e === ia || t.classList.contains(ua) + ? ta.MASK_USER_INPUT + : e === na || t.classList.contains(aa) + ? ta.ALLOW + : Da(t) + ? ta.IGNORE + : void 0; + } + } + function ga(t, e) { + switch (e) { + case ta.MASK: + case ta.HIDDEN: + case ta.IGNORE: + return !0; + case ta.MASK_USER_INPUT: + return _a(t) ? wa(t.parentNode) : wa(t); + default: + return !1; + } + } + function ba(t) { + return t.nodeType === t.ELEMENT_NODE; + } + function _a(t) { + return t.nodeType === t.TEXT_NODE; + } + function wa(t) { + if (!t || t.nodeType !== t.ELEMENT_NODE) return !1; + var e = t; + if ("INPUT" === e.tagName) + switch (e.type) { + case "button": + case "color": + case "reset": + case "submit": + return !1; + } + return !!da[e.tagName]; + } + var xa = function (t) { + return t.replace(/\S/g, ha); + }; + function ka(t, e, n) { + var r, + i = + null === (r = t.parentElement) || void 0 === r + ? void 0 + : r.tagName, + o = t.textContent || ""; + if (!e || o.trim()) { + var a = n, + s = "STYLE" === i || void 0, + u = "SCRIPT" === i; + if (u) o = fa; + else if (a === ta.HIDDEN) o = fa; + else if (ga(t, a) && !s) + if ( + "DATALIST" === i || + "SELECT" === i || + "OPTGROUP" === i + ) { + if (!o.trim()) return; + } else o = "OPTION" === i ? fa : xa(o); + return o; + } + } + var Sa = new WeakMap(); + function Ea(t) { + return Sa.has(t); + } + function Oa(t) { + var e = t; + while (e) { + if (!Ea(e)) return !1; + e = e.parentNode; + } + return !0; + } + function Ta(t) { + return Sa.get(t); + } + function Aa(t, e) { + Sa.set(t, e); + } + function Ca(t, e) { + var n = t.tagName, + r = t.value; + if (ga(t, e)) { + var i = t.type; + if ( + "INPUT" === n && + ("button" === i || "submit" === i || "reset" === i) + ) + return r; + if (!r || "OPTION" === n) return; + return fa; + } + return "OPTION" === n || "SELECT" === n + ? t.value + : "INPUT" === n || "TEXTAREA" === n + ? r + : void 0; + } + function ja(t) { + return Boolean(t.changedTouches); + } + function Ia(t, e) { + Array.prototype.forEach.call(t, e); + } + function Ma(t, e) { + return La(t, { document: t, parentNodePrivacyLevel: e }); + } + function La(t, e) { + var n = Pa(t, e); + if (!n) return null; + var r = Ta(t) || Ha(), + i = n; + return ( + (i.id = r), + Aa(t, r), + e.serializedNodeIds && e.serializedNodeIds.add(r), + i + ); + } + function Pa(t, e) { + switch (t.nodeType) { + case t.DOCUMENT_NODE: + return Na(t, e); + case t.DOCUMENT_TYPE_NODE: + return Ra(t); + case t.ELEMENT_NODE: + return $a(t, e); + case t.TEXT_NODE: + return Fa(t, e); + case t.CDATA_SECTION_NODE: + return za(); + } + } + function Na(t, e) { + return { type: 0, childNodes: Ua(t, e) }; + } + function Ra(t) { + return { + type: 1, + name: t.name, + publicId: t.publicId, + systemId: t.systemId, + }; + } + function $a(t, e) { + var n, + r = Wa(t.tagName), + i = Ka(t) || void 0, + o = ya(ma(t), e.parentNodePrivacyLevel); + if (o === ta.HIDDEN) { + var a = t.getBoundingClientRect(), + s = a.width, + u = a.height; + return { + type: 2, + tagName: r, + attributes: + ((n = { + rr_width: "".concat(s, "px"), + rr_height: "".concat(u, "px"), + }), + (n[ea] = oa), + n), + childNodes: [], + isSVG: i, + }; + } + if (o !== ta.IGNORE) { + var c = Xa(t, o), + f = []; + if (t.childNodes.length) { + var l = void 0; + (l = + e.parentNodePrivacyLevel === o && + e.ignoreWhiteSpace === ("head" === r) + ? e + : x({}, e, { + parentNodePrivacyLevel: o, + ignoreWhiteSpace: "head" === r, + })), + (f = Ua(t, l)); + } + return { + type: 2, + tagName: r, + attributes: c, + childNodes: f, + isSVG: i, + }; + } + } + function Da(t) { + if ("SCRIPT" === t.nodeName) return !0; + if ("LINK" === t.nodeName) { + var e = i("rel"); + return ( + ("preload" === e && "script" === i("as")) || + "shortcut icon" === e || + "icon" === e + ); + } + if ("META" === t.nodeName) { + var n = i("name"), + r = ((e = i("rel")), i("property")); + return ( + /^msapplication-tile(image|color)$/.test(n) || + "application-name" === n || + "icon" === e || + "apple-touch-icon" === e || + "shortcut icon" === e || + "keywords" === n || + "description" === n || + /^(og|twitter|fb):/.test(r) || + /^(og|twitter):/.test(n) || + "pinterest" === n || + "robots" === n || + "googlebot" === n || + "bingbot" === n || + t.hasAttribute("http-equiv") || + "author" === n || + "generator" === n || + "framework" === n || + "publisher" === n || + "progid" === n || + /^article:/.test(r) || + /^product:/.test(r) || + "google-site-verification" === n || + "yandex-verification" === n || + "csrf-token" === n || + "p:domain_verify" === n || + "verify-v1" === n || + "verification" === n || + "shopify-checkout-api-token" === n + ); + } + function i(e) { + return (t.getAttribute(e) || "").toLowerCase(); + } + return !1; + } + function Fa(t, e) { + var n, + r = + null === (n = t.parentElement) || void 0 === n + ? void 0 + : n.tagName, + i = ka( + t, + e.ignoreWhiteSpace || !1, + e.parentNodePrivacyLevel + ); + if (i) + return { + type: 3, + textContent: i, + isStyle: "STYLE" === r || void 0, + }; + } + function za() { + return { type: 4, textContent: "" }; + } + function Ua(t, e) { + var n = []; + return ( + Ia(t.childNodes, function (t) { + var r = La(t, e); + r && n.push(r); + }), + n + ); + } + function Ba(t, e, n) { + if (e === ta.HIDDEN) return null; + var r = t.getAttribute(n); + if (e === ta.MASK) { + var i = t.tagName; + switch (n) { + case "title": + case "alt": + return fa; + } + if ( + ("IMG" === i || "SOURCE" === i) && + ("src" === n || "srcset" === n) + ) + return la; + if ("A" === i && "href" === n) return fa; + if (r && 0 === n.indexOf("data-") && n !== ea) return fa; + } + return r && + "string" === typeof r && + r.length > pa && + "data:" === r.slice(0, 5) + ? "data:truncated" + : r; + } + var Va = 1; + function Ha() { + return Va++; + } + var qa = /[^a-z1-6-_]/; + function Wa(t) { + var e = t.toLowerCase().trim(); + return qa.test(e) ? "div" : e; + } + function Ga(t) { + try { + var e = t.rules || t.cssRules; + return e ? Array.from(e).map(Za).join("") : null; + } catch (n) { + return null; + } + } + function Za(t) { + return Ja(t) ? Ga(t.styleSheet) || "" : t.cssText; + } + function Ja(t) { + return "styleSheet" in t; + } + function Ka(t) { + return "svg" === t.tagName || t instanceof SVGElement; + } + function Xa(t, e) { + if (e === ta.HIDDEN) return {}; + for ( + var n = {}, r = Wa(t.tagName), i = t.ownerDocument, o = 0; + o < t.attributes.length; + o += 1 + ) { + var a = t.attributes.item(o), + s = a.name, + u = Ba(t, e, s); + null !== u && (n[s] = u); + } + if ( + t.value && + ("textarea" === r || + "select" === r || + "option" === r || + "input" === r) + ) { + var c = Ca(t, e); + void 0 !== c && (n.value = c); + } + if ("option" === r && e === ta.ALLOW) { + var f = t; + f.selected && (n.selected = f.selected); + } + if ("link" === r) { + var l = Array.from(i.styleSheets).find(function (e) { + return e.href === t.href; + }), + d = Ga(l); + d && l && (delete n.rel, delete n.href, (n._cssText = d)); + } + if ( + "style" === r && + t.sheet && + !(t.innerText || t.textContent || "").trim().length + ) { + d = Ga(t.sheet); + d && (n._cssText = d); + } + var p = t; + if ( + ("input" !== r || + ("radio" !== p.type && "checkbox" !== p.type) || + (e === ta.ALLOW + ? (n.checked = !!p.checked) + : ga(p, e) && (n.checked = fa)), + "audio" === r || "video" === r) + ) { + var h = t; + n.rr_mediaState = h.paused ? "paused" : "played"; + } + return ( + t.scrollLeft && + (n.rr_scrollLeft = Math.round(t.scrollLeft)), + t.scrollTop && (n.rr_scrollTop = Math.round(t.scrollTop)), + n + ); + } + var Ya = { + Mutation: 0, + MouseMove: 1, + MouseInteraction: 2, + Scroll: 3, + ViewportResize: 4, + Input: 5, + TouchMove: 6, + MediaInteraction: 7, + StyleSheetRule: 8, + }, + Qa = { + MouseUp: 0, + MouseDown: 1, + Click: 2, + ContextMenu: 3, + DblClick: 4, + Focus: 5, + Blur: 6, + TouchStart: 7, + TouchEnd: 9, + }, + ts = { Play: 0, Pause: 1 }, + es = 100; + function ns(t) { + var e = T, + n = []; + function r() { + e(), t(n), (n = []); + } + return { + addMutations: function (t) { + 0 === n.length && (e = Q(r, { timeout: es })), + n.push.apply(n, t); + }, + flush: r, + stop: function () { + e(); + }, + }; + } + function rs(t, e, n) { + var r = Ne(); + if (!r) return { stop: T }; + var i = ns(function (t) { + os(t.concat(o.takeRecords()), e, n); + }), + o = new r(Tt(i.addMutations)); + return ( + o.observe(document, { + attributeOldValue: !0, + attributes: !0, + characterData: !0, + characterDataOldValue: !0, + childList: !0, + subtree: !0, + }), + t.onFlush(i.flush), + { + stop: function () { + o.disconnect(), i.stop(); + }, + } + ); + } + var is = (function () { + function t() {} + return ( + (t.prototype.flush = function () { + var t; + null === (t = this.flushListener) || + void 0 === t || + t.call(this); + }), + (t.prototype.onFlush = function (t) { + this.flushListener = t; + }), + t + ); + })(); + function os(t, e, n) { + var r = t.filter(function (t) { + return ( + document.contains(t.target) && + Oa(t.target) && + va(t.target, n) !== ta.HIDDEN + ); + }), + i = as( + r.filter(function (t) { + return "childList" === t.type; + }), + n + ), + o = i.adds, + a = i.removes, + s = i.hasBeenSerialized, + u = ss( + r.filter(function (t) { + return "characterData" === t.type && !s(t.target); + }), + n + ), + c = us( + r.filter(function (t) { + return "attributes" === t.type && !s(t.target); + }), + n + ); + (u.length || c.length || a.length || o.length) && + e({ adds: o, removes: a, texts: u, attributes: c }); + } + function as(t, e) { + for ( + var n = new Set(), + r = new Map(), + i = function (t) { + Ia(t.addedNodes, function (t) { + n.add(t); + }), + Ia(t.removedNodes, function (e) { + n.has(e) || r.set(e, t.target), n.delete(e); + }); + }, + o = 0, + a = t; + o < a.length; + o++ + ) { + var s = a[o]; + i(s); + } + var u = Array.from(n); + cs(u); + for ( + var c = new Set(), f = [], l = 0, d = u; + l < d.length; + l++ + ) { + var p = d[l]; + if (!m(p)) { + var h = va(p.parentNode, e); + if (h !== ta.HIDDEN && h !== ta.IGNORE) { + var v = La(p, { + document: document, + serializedNodeIds: c, + parentNodePrivacyLevel: h, + }); + v && + f.push({ + nextId: g(p), + parentId: Ta(p.parentNode), + node: v, + }); + } + } + } + var y = []; + return ( + r.forEach(function (t, e) { + Ea(e) && y.push({ parentId: Ta(t), id: Ta(e) }); + }), + { adds: f, removes: y, hasBeenSerialized: m } + ); + function m(t) { + return Ea(t) && c.has(Ta(t)); + } + function g(t) { + var e = t.nextSibling; + while (e) { + if (Ea(e)) return Ta(e); + e = e.nextSibling; + } + return null; + } + } + function ss(t, e) { + for ( + var n, + r = [], + i = new Set(), + o = t.filter(function (t) { + return !i.has(t.target) && (i.add(t.target), !0); + }), + a = 0, + s = o; + a < s.length; + a++ + ) { + var u = s[a], + c = u.target.textContent; + if (c !== u.oldValue) { + var f = va(u.target.parentNode, e); + f !== ta.HIDDEN && + f !== ta.IGNORE && + r.push({ + id: Ta(u.target), + value: + null !== (n = ka(u.target, !1, f)) && + void 0 !== n + ? n + : null, + }); + } + } + return r; + } + function us(t, e) { + for ( + var n = [], + r = new Map(), + i = t.filter(function (t) { + var e = r.get(t.target); + return ( + !(null === e || void 0 === e + ? void 0 + : e.has(t.attributeName)) && + (e + ? e.add(t.attributeName) + : r.set( + t.target, + new Set([t.attributeName]) + ), + !0) + ); + }), + o = new Map(), + a = 0, + s = i; + a < s.length; + a++ + ) { + var u = s[a], + c = u.target.getAttribute(u.attributeName); + if (c !== u.oldValue) { + var f = va(u.target, e), + l = Ba(u.target, f, u.attributeName), + d = void 0; + if ("value" === u.attributeName) { + var p = Ca(u.target, f); + if (void 0 === p) continue; + d = p; + } else d = "string" === typeof l ? l : null; + var h = o.get(u.target); + h || + ((h = { id: Ta(u.target), attributes: {} }), + n.push(h), + o.set(u.target, h)), + (h.attributes[u.attributeName] = d); + } + } + return n; + } + function cs(t) { + t.sort(function (t, e) { + var n = t.compareDocumentPosition(e); + return n & Node.DOCUMENT_POSITION_CONTAINED_BY + ? -1 + : n & Node.DOCUMENT_POSITION_CONTAINS || + n & Node.DOCUMENT_POSITION_FOLLOWING + ? 1 + : n & Node.DOCUMENT_POSITION_PRECEDING + ? -1 + : 0; + }); + } + var fs = 25; + function ls() { + var t = window.visualViewport; + return ( + Math.abs(t.pageTop - t.offsetTop - window.scrollY) > fs || + Math.abs(t.pageLeft - t.offsetLeft - window.scrollX) > fs + ); + } + var ds, + ps = function (t, e) { + var n = window.visualViewport, + r = { + layoutViewportX: t, + layoutViewportY: e, + visualViewportX: t, + visualViewportY: e, + }; + return n + ? (ls() + ? ((r.layoutViewportX = Math.round( + t + n.offsetLeft + )), + (r.layoutViewportY = Math.round( + e + n.offsetTop + ))) + : ((r.visualViewportX = Math.round( + t - n.offsetLeft + )), + (r.visualViewportY = Math.round( + e - n.offsetTop + ))), + r) + : r; + }, + hs = function () { + var t = window.visualViewport; + return { + scale: t.scale, + offsetLeft: t.offsetLeft, + offsetTop: t.offsetTop, + pageLeft: t.pageLeft, + pageTop: t.pageTop, + height: t.height, + width: t.width, + }; + }; + function vs() { + var t = window.visualViewport; + return t ? t.width * t.scale : window.innerWidth || 0; + } + function ys() { + var t = window.visualViewport; + return t ? t.height * t.scale : window.innerHeight || 0; + } + function ms() { + var t = window.visualViewport; + return t + ? t.pageLeft - t.offsetLeft + : void 0 !== window.scrollX + ? window.scrollX + : window.pageXOffset || 0; + } + function gs() { + var t = window.visualViewport; + return t + ? t.pageTop - t.offsetTop + : void 0 !== window.scrollY + ? window.scrollY + : window.pageYOffset || 0; + } + var bs = 50, + _s = 100, + ws = 200; + function xs(t) { + var e = ks( + t.mutationController, + t.mutationCb, + t.defaultPrivacyLevel + ), + n = Ss(t.mousemoveCb), + r = Os(t.mouseInteractionCb, t.defaultPrivacyLevel), + i = Ts(t.scrollCb, t.defaultPrivacyLevel), + o = As(t.viewportResizeCb), + a = Cs(t.inputCb, t.defaultPrivacyLevel), + s = Is(t.mediaInteractionCb, t.defaultPrivacyLevel), + u = js(t.styleSheetRuleCb), + c = Ms(t.focusCb), + f = Ls(t.visualViewportResizeCb); + return function () { + e(), n(), r(), i(), o(), a(), s(), u(), c(), f(); + }; + } + function ks(t, e, n) { + return rs(t, e, n).stop; + } + function Ss(t) { + var e = w( + Tt(function (e) { + var n = e.target; + if (Ea(n)) { + var r = ja(e) ? e.changedTouches[0] : e, + i = r.clientX, + o = r.clientY, + a = { id: Ta(n), timeOffset: 0, x: i, y: o }; + if (window.visualViewport) { + var s = ps(i, o), + u = s.visualViewportX, + c = s.visualViewportY; + (a.x = u), (a.y = c); + } + t([a], ja(e) ? Ya.TouchMove : Ya.MouseMove); + } + }), + bs, + { trailing: !1 } + ).throttled; + return W(document, ["mousemove", "touchmove"], e, { + capture: !0, + passive: !0, + }).stop; + } + var Es = + ((ds = {}), + (ds["mouseup"] = Qa.MouseUp), + (ds["mousedown"] = Qa.MouseDown), + (ds["click"] = Qa.Click), + (ds["contextmenu"] = Qa.ContextMenu), + (ds["dblclick"] = Qa.DblClick), + (ds["focus"] = Qa.Focus), + (ds["blur"] = Qa.Blur), + (ds["touchstart"] = Qa.TouchStart), + (ds["touchend"] = Qa.TouchEnd), + ds); + function Os(t, e) { + var n = function (n) { + var r = n.target; + if (va(r, e) !== ta.HIDDEN && Ea(r)) { + var i = ja(n) ? n.changedTouches[0] : n, + o = i.clientX, + a = i.clientY, + s = { id: Ta(r), type: Es[n.type], x: o, y: a }; + if (window.visualViewport) { + var u = ps(o, a), + c = u.visualViewportX, + f = u.visualViewportY; + (s.x = c), (s.y = f); + } + t(s); + } + }; + return W(document, Object.keys(Es), n, { + capture: !0, + passive: !0, + }).stop; + } + function Ts(t, e) { + var n = w( + Tt(function (n) { + var r = n.target; + if (r && va(r, e) !== ta.HIDDEN && Ea(r)) { + var i = Ta(r); + r === document + ? t({ id: i, x: ms(), y: gs() }) + : t({ id: i, x: r.scrollLeft, y: r.scrollTop }); + } + }), + _s + ).throttled; + return q(document, "scroll", n, { capture: !0, passive: !0 }) + .stop; + } + function As(t) { + var e = w( + Tt(function () { + var e = ys(), + n = vs(); + t({ height: Number(e), width: Number(n) }); + }), + 200 + ).throttled; + return q(window, "resize", e, { capture: !0, passive: !0 }) + .stop; + } + function Cs(t, e) { + var n = new WeakMap(); + function r(t) { + var n = va(t, e); + if (n !== ta.HIDDEN) { + var r, + o = t.type; + if ("radio" === o || "checkbox" === o) { + if (ga(t, n)) return; + r = { isChecked: t.checked }; + } else { + var a = Ca(t, n); + if (void 0 === a) return; + r = { text: a }; + } + i(t, r); + var s = t.name; + "radio" === o && + s && + t.checked && + Ia( + document.querySelectorAll( + 'input[type="radio"][name="'.concat(s, '"]') + ), + function (e) { + e !== t && i(e, { isChecked: !1 }); + } + ); + } + } + function i(e, r) { + if (Ea(e)) { + var i = n.get(e); + (i && + i.text === r.text && + i.isChecked === r.isChecked) || + (n.set(e, r), t(x({ id: Ta(e) }, r))); + } + } + var o = W( + document, + ["input", "change"], + function (t) { + (t.target instanceof HTMLInputElement || + t.target instanceof HTMLTextAreaElement || + t.target instanceof HTMLSelectElement) && + r(t.target); + }, + { capture: !0, passive: !0 } + ).stop, + a = [ + vr(HTMLInputElement.prototype, "value", r), + vr(HTMLInputElement.prototype, "checked", r), + vr(HTMLSelectElement.prototype, "value", r), + vr(HTMLTextAreaElement.prototype, "value", r), + vr(HTMLSelectElement.prototype, "selectedIndex", r), + ]; + return function () { + a.forEach(function (t) { + return t.stop(); + }), + o(); + }; + } + function js(t) { + var e = hr(CSSStyleSheet.prototype, "insertRule", { + before: function (e, n) { + Ea(this.ownerNode) && + t({ + id: Ta(this.ownerNode), + adds: [{ rule: e, index: n }], + }); + }, + }).stop, + n = hr(CSSStyleSheet.prototype, "deleteRule", { + before: function (e) { + Ea(this.ownerNode) && + t({ + id: Ta(this.ownerNode), + removes: [{ index: e }], + }); + }, + }).stop; + return function () { + e(), n(); + }; + } + function Is(t, e) { + var n = function (n) { + var r = n.target; + r && + va(r, e) !== ta.HIDDEN && + Ea(r) && + t({ + id: Ta(r), + type: "play" === n.type ? ts.Play : ts.Pause, + }); + }; + return W(document, ["play", "pause"], n, { + capture: !0, + passive: !0, + }).stop; + } + function Ms(t) { + return W(window, ["focus", "blur"], function () { + t({ has_focus: document.hasFocus() }); + }).stop; + } + function Ls(t) { + if (!window.visualViewport) return T; + var e = w( + Tt(function () { + t(hs()); + }), + ws, + { trailing: !1 } + ), + n = e.throttled, + r = e.cancel, + i = W(window.visualViewport, ["resize", "scroll"], n, { + capture: !0, + passive: !0, + }).stop; + return function () { + i(), r(); + }; + } + function Ps(t) { + var e = t.emit; + if (!e) throw new Error("emit function is required"); + var n = new is(), + r = function (r) { + void 0 === r && (r = pt()), + n.flush(), + e({ + data: { + height: ys(), + href: window.location.href, + width: vs(), + }, + type: Qo.Meta, + timestamp: r, + }), + e({ + data: { has_focus: document.hasFocus() }, + type: Qo.Focus, + timestamp: r, + }), + e({ + data: { + node: Ma(document, t.defaultPrivacyLevel), + initialOffset: { left: ms(), top: gs() }, + }, + type: Qo.FullSnapshot, + timestamp: r, + }), + window.visualViewport && + e({ + data: hs(), + type: Qo.VisualViewport, + timestamp: r, + }); + }; + r(); + var i = xs({ + mutationController: n, + defaultPrivacyLevel: t.defaultPrivacyLevel, + inputCb: function (t) { + return e(Ns(Ya.Input, t)); + }, + mediaInteractionCb: function (t) { + return e(Ns(Ya.MediaInteraction, t)); + }, + mouseInteractionCb: function (t) { + return e(Ns(Ya.MouseInteraction, t)); + }, + mousemoveCb: function (t, n) { + return e(Ns(n, { positions: t })); + }, + mutationCb: function (t) { + return e(Ns(Ya.Mutation, t)); + }, + scrollCb: function (t) { + return e(Ns(Ya.Scroll, t)); + }, + styleSheetRuleCb: function (t) { + return e(Ns(Ya.StyleSheetRule, t)); + }, + viewportResizeCb: function (t) { + return e(Ns(Ya.ViewportResize, t)); + }, + focusCb: function (t) { + return e({ data: t, type: Qo.Focus, timestamp: pt() }); + }, + visualViewportResizeCb: function (t) { + e({ + data: t, + type: Qo.VisualViewport, + timestamp: pt(), + }); + }, + }); + return { + stop: i, + takeFullSnapshot: r, + flushMutations: function () { + return n.flush(); + }, + }; + } + function Ns(t, e) { + return { + data: x({ source: t }, e), + type: Qo.IncrementalSnapshot, + timestamp: pt(), + }; + } + var Rs = 6e4; + function $s(t, e, n, r, i) { + var o = new FormData(); + o.append( + "segment", + new Blob([e], { type: "application/octet-stream" }), + "".concat(n.session.id, "-").concat(n.start) + ), + Ds(n, function (t, e) { + return o.append(t, e); + }), + o.append("raw_segment_size", r.toString()); + var a = new je(t, Rs); + a.send(o, e.byteLength, i); + } + function Ds(t, e, n) { + void 0 === n && (n = ""), + $(t).forEach(function (t) { + var r = t[0], + i = t[1]; + "object" === typeof i && null !== i + ? Ds(i, e, "".concat(n).concat(r, ".")) + : e("".concat(n).concat(r), String(i)); + }); + } + var Fs, + zs = 10; + function Us(t) { + return Ws(t).segments_count; + } + function Bs(t) { + Ws(t).segments_count += 1; + } + function Vs(t) { + Ws(t).records_count += 1; + } + function Hs(t, e) { + Ws(t).segments_total_raw_size += e; + } + function qs(t) { + return null === Fs || void 0 === Fs ? void 0 : Fs.get(t); + } + function Ws(t) { + var e; + return ( + Fs || (Fs = new Map()), + Fs.has(t) + ? (e = Fs.get(t)) + : ((e = { + records_count: 0, + segments_count: 0, + segments_total_raw_size: 0, + }), + Fs.set(t, e), + Fs.size > zs && Gs()), + e + ); + } + function Gs() { + if (Fs) + if (Fs.keys) Fs.delete(Fs.keys().next().value); + else { + var t = !0; + Fs.forEach(function (e, n) { + t && (Fs.delete(n), (t = !1)); + }); + } + } + var Zs, + Js = 0, + Ks = (function () { + function t(t, e, n, r, i, o) { + var a = this; + (this.worker = t), + (this.isFlushed = !1), + (this.id = Js++); + var s = e.view.id; + (this.metadata = x( + { + start: r.timestamp, + end: r.timestamp, + creation_reason: n, + records_count: 1, + has_full_snapshot: r.type === Qo.FullSnapshot, + index_in_view: Us(s), + }, + e + )), + Bs(s), + Vs(s); + var u = Tt(function (e) { + var n = e.data; + "errored" !== n.type && + "initialized" !== n.type && + (n.id === a.id + ? (Hs(s, n.additionalRawSize), + "flushed" === n.type + ? (o(n.result, n.rawSize), + t.removeEventListener("message", u)) + : i(n.compressedSize)) + : n.id > a.id && + (t.removeEventListener("message", u), + Ct( + "Segment did not receive a 'flush' response before being replaced." + ))); + }); + t.addEventListener("message", u), + this.worker.postMessage({ + data: '{"records":['.concat(JSON.stringify(r)), + id: this.id, + action: "write", + }); + } + return ( + (t.prototype.addRecord = function (t) { + var e; + (this.metadata.end = t.timestamp), + (this.metadata.records_count += 1), + Vs(this.metadata.view.id), + (e = this.metadata).has_full_snapshot || + (e.has_full_snapshot = + t.type === Qo.FullSnapshot), + this.worker.postMessage({ + data: ",".concat(JSON.stringify(t)), + id: this.id, + action: "write", + }); + }), + (t.prototype.flush = function (t) { + this.worker.postMessage({ + data: "],".concat( + JSON.stringify(this.metadata).slice(1), + "\n" + ), + id: this.id, + action: "flush", + }), + (this.isFlushed = !0), + (this.flushReason = t); + }), + t + ); + })(), + Xs = 3e4, + Ys = Rs; + function Qs(t, e, n, r, i, o) { + return tu( + t, + function () { + return eu(e, n, r); + }, + i, + o + ); + } + function tu(t, e, n, r, i) { + void 0 === i && (i = window); + var o = { status: 0, nextSegmentCreationReason: "init" }, + a = t.subscribe(2, function () { + c("view_change"); + }).unsubscribe, + s = t.subscribe(9, function () { + c("before_unload"); + }).unsubscribe, + u = q( + i, + "visibilitychange", + function () { + "hidden" === document.visibilityState && + c("visibility_hidden"); + }, + { capture: !0 } + ).stop; + function c(t) { + 1 === o.status && + (o.segment.flush(t || "sdk_stopped"), + clearTimeout(o.expirationTimeoutId)), + (o = t + ? { status: 0, nextSegmentCreationReason: t } + : { status: 2 }); + } + function f(t, i) { + var a = e(); + if (a) { + var s = new Ks( + r, + a, + t, + i, + function (t) { + !s.isFlushed && t > Ys && c("max_size"); + }, + function (t, e) { + n(t, s.metadata, e, s.flushReason); + } + ); + o = { + status: 1, + segment: s, + expirationTimeoutId: setTimeout( + Tt(function () { + c("max_duration"); + }), + Xs + ), + }; + } + } + return { + addRecord: function (t) { + switch (o.status) { + case 0: + f(o.nextSegmentCreationReason, t); + break; + case 1: + o.segment.addRecord(t); + break; + } + }, + stop: function () { + c(), a(), s(), u(); + }, + }; + } + function eu(t, e, n) { + var r = e.findTrackedSession(), + i = n.findView(); + if (r && i) + return { + application: { id: t }, + session: { id: r.id }, + view: { id: i.view.id }, + }; + } + function nu() { + return ( + Zs || + (Zs = URL.createObjectURL( + new Blob(["(".concat(ru, ")(self)")]) + )), + new Worker(Zs) + ); + } + function ru() { + function t(t) { + return function () { + try { + return t.apply(this, arguments); + } catch (e) { + try { + self.postMessage({ type: "errored", error: e }); + } catch (n) { + self.postMessage({ + type: "errored", + error: "".concat(e), + }); + } + } + }; + } + function e() { + var t = 4, + e = 0, + n = 1, + r = 2; + function i(t) { + var e = t.length; + while (--e >= 0) t[e] = 0; + } + var o = 0, + a = 1, + s = 2, + u = 3, + c = 258, + f = 29, + l = 256, + d = l + 1 + f, + p = 30, + h = 19, + v = 2 * d + 1, + y = 15, + m = 16, + g = 7, + b = 256, + _ = 16, + w = 17, + x = 18, + k = new Uint8Array([ + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, + 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, + ]), + S = new Uint8Array([ + 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, + 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, + ]), + E = new Uint8Array([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, + 3, 7, + ]), + O = new Uint8Array([ + 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, + 2, 14, 1, 15, + ]), + T = 512, + A = new Array(2 * (d + 2)); + i(A); + var C = new Array(2 * p); + i(C); + var j = new Array(T); + i(j); + var I = new Array(c - u + 1); + i(I); + var M = new Array(f); + i(M); + var L, + P, + N, + R = new Array(p); + function $(t, e, n, r, i) { + (this.static_tree = t), + (this.extra_bits = e), + (this.extra_base = n), + (this.elems = r), + (this.max_length = i), + (this.has_stree = t && t.length); + } + function D(t, e) { + (this.dyn_tree = t), + (this.max_code = 0), + (this.stat_desc = e); + } + i(R); + var F = function (t) { + return t < 256 ? j[t] : j[256 + (t >>> 7)]; + }, + z = function (t, e) { + (t.pending_buf[t.pending++] = 255 & e), + (t.pending_buf[t.pending++] = (e >>> 8) & 255); + }, + U = function (t, e, n) { + t.bi_valid > m - n + ? ((t.bi_buf |= (e << t.bi_valid) & 65535), + z(t, t.bi_buf), + (t.bi_buf = e >> (m - t.bi_valid)), + (t.bi_valid += n - m)) + : ((t.bi_buf |= (e << t.bi_valid) & 65535), + (t.bi_valid += n)); + }, + B = function (t, e, n) { + U(t, n[2 * e], n[2 * e + 1]); + }, + V = function (t, e) { + var n = 0; + do { + (n |= 1 & t), (t >>>= 1), (n <<= 1); + } while (--e > 0); + return n >>> 1; + }, + H = function (t) { + 16 === t.bi_valid + ? (z(t, t.bi_buf), + (t.bi_buf = 0), + (t.bi_valid = 0)) + : t.bi_valid >= 8 && + ((t.pending_buf[t.pending++] = + 255 & t.bi_buf), + (t.bi_buf >>= 8), + (t.bi_valid -= 8)); + }, + q = function (t, e) { + var n, + r, + i, + o, + a, + s, + u = e.dyn_tree, + c = e.max_code, + f = e.stat_desc.static_tree, + l = e.stat_desc.has_stree, + d = e.stat_desc.extra_bits, + p = e.stat_desc.extra_base, + h = e.stat_desc.max_length, + m = 0; + for (o = 0; o <= y; o++) t.bl_count[o] = 0; + for ( + u[2 * t.heap[t.heap_max] + 1] = 0, + n = t.heap_max + 1; + n < v; + n++ + ) + (r = t.heap[n]), + (o = u[2 * u[2 * r + 1] + 1] + 1), + o > h && ((o = h), m++), + (u[2 * r + 1] = o), + r > c || + (t.bl_count[o]++, + (a = 0), + r >= p && (a = d[r - p]), + (s = u[2 * r]), + (t.opt_len += s * (o + a)), + l && + (t.static_len += + s * (f[2 * r + 1] + a))); + if (0 !== m) { + do { + o = h - 1; + while (0 === t.bl_count[o]) o--; + t.bl_count[o]--, + (t.bl_count[o + 1] += 2), + t.bl_count[h]--, + (m -= 2); + } while (m > 0); + for (o = h; 0 !== o; o--) { + r = t.bl_count[o]; + while (0 !== r) + (i = t.heap[--n]), + i > c || + (u[2 * i + 1] !== o && + ((t.opt_len += + (o - u[2 * i + 1]) * + u[2 * i]), + (u[2 * i + 1] = o)), + r--); + } + } + }, + W = function (t, e, n) { + var r, + i, + o = new Array(y + 1), + a = 0; + for (r = 1; r <= y; r++) + o[r] = a = (a + n[r - 1]) << 1; + for (i = 0; i <= e; i++) { + var s = t[2 * i + 1]; + 0 !== s && (t[2 * i] = V(o[s]++, s)); + } + }, + G = function () { + var t, + e, + n, + r, + i, + o = new Array(y + 1); + for (n = 0, r = 0; r < f - 1; r++) + for (M[r] = n, t = 0; t < 1 << k[r]; t++) + I[n++] = r; + for (I[n - 1] = r, i = 0, r = 0; r < 16; r++) + for (R[r] = i, t = 0; t < 1 << S[r]; t++) + j[i++] = r; + for (i >>= 7; r < p; r++) + for ( + R[r] = i << 7, t = 0; + t < 1 << (S[r] - 7); + t++ + ) + j[256 + i++] = r; + for (e = 0; e <= y; e++) o[e] = 0; + t = 0; + while (t <= 143) (A[2 * t + 1] = 8), t++, o[8]++; + while (t <= 255) (A[2 * t + 1] = 9), t++, o[9]++; + while (t <= 279) (A[2 * t + 1] = 7), t++, o[7]++; + while (t <= 287) (A[2 * t + 1] = 8), t++, o[8]++; + for (W(A, d + 1, o), t = 0; t < p; t++) + (C[2 * t + 1] = 5), (C[2 * t] = V(t, 5)); + (L = new $(A, k, l + 1, d, y)), + (P = new $(C, S, 0, p, y)), + (N = new $(new Array(0), E, 0, h, g)); + }, + Z = function (t) { + var e; + for (e = 0; e < d; e++) t.dyn_ltree[2 * e] = 0; + for (e = 0; e < p; e++) t.dyn_dtree[2 * e] = 0; + for (e = 0; e < h; e++) t.bl_tree[2 * e] = 0; + (t.dyn_ltree[2 * b] = 1), + (t.opt_len = t.static_len = 0), + (t.last_lit = t.matches = 0); + }, + J = function (t) { + t.bi_valid > 8 + ? z(t, t.bi_buf) + : t.bi_valid > 0 && + (t.pending_buf[t.pending++] = t.bi_buf), + (t.bi_buf = 0), + (t.bi_valid = 0); + }, + K = function (t, e, n, r) { + J(t), + r && (z(t, n), z(t, ~n)), + t.pending_buf.set( + t.window.subarray(e, e + n), + t.pending + ), + (t.pending += n); + }, + X = function (t, e, n, r) { + var i = 2 * e, + o = 2 * n; + return ( + t[i] < t[o] || (t[i] === t[o] && r[e] <= r[n]) + ); + }, + Y = function (t, e, n) { + var r = t.heap[n], + i = n << 1; + while (i <= t.heap_len) { + if ( + (i < t.heap_len && + X( + e, + t.heap[i + 1], + t.heap[i], + t.depth + ) && + i++, + X(e, r, t.heap[i], t.depth)) + ) + break; + (t.heap[n] = t.heap[i]), (n = i), (i <<= 1); + } + t.heap[n] = r; + }, + Q = function (t, e, n) { + var r, + i, + o, + a, + s = 0; + if (0 !== t.last_lit) + do { + (r = + (t.pending_buf[t.d_buf + 2 * s] << 8) | + t.pending_buf[t.d_buf + 2 * s + 1]), + (i = t.pending_buf[t.l_buf + s]), + s++, + 0 === r + ? B(t, i, e) + : ((o = I[i]), + B(t, o + l + 1, e), + (a = k[o]), + 0 !== a && + ((i -= M[o]), U(t, i, a)), + r--, + (o = F(r)), + B(t, o, n), + (a = S[o]), + 0 !== a && + ((r -= R[o]), U(t, r, a))); + } while (s < t.last_lit); + B(t, b, e); + }, + tt = function (t, e) { + var n, + r, + i, + o = e.dyn_tree, + a = e.stat_desc.static_tree, + s = e.stat_desc.has_stree, + u = e.stat_desc.elems, + c = -1; + for ( + t.heap_len = 0, t.heap_max = v, n = 0; + n < u; + n++ + ) + 0 !== o[2 * n] + ? ((t.heap[++t.heap_len] = c = n), + (t.depth[n] = 0)) + : (o[2 * n + 1] = 0); + while (t.heap_len < 2) + (i = t.heap[++t.heap_len] = c < 2 ? ++c : 0), + (o[2 * i] = 1), + (t.depth[i] = 0), + t.opt_len--, + s && (t.static_len -= a[2 * i + 1]); + for ( + e.max_code = c, n = t.heap_len >> 1; + n >= 1; + n-- + ) + Y(t, o, n); + i = u; + do { + (n = t.heap[1]), + (t.heap[1] = t.heap[t.heap_len--]), + Y(t, o, 1), + (r = t.heap[1]), + (t.heap[--t.heap_max] = n), + (t.heap[--t.heap_max] = r), + (o[2 * i] = o[2 * n] + o[2 * r]), + (t.depth[i] = + (t.depth[n] >= t.depth[r] + ? t.depth[n] + : t.depth[r]) + 1), + (o[2 * n + 1] = o[2 * r + 1] = i), + (t.heap[1] = i++), + Y(t, o, 1); + } while (t.heap_len >= 2); + (t.heap[--t.heap_max] = t.heap[1]), + q(t, e), + W(o, c, t.bl_count); + }, + et = function (t, e, n) { + var r, + i, + o = -1, + a = e[1], + s = 0, + u = 7, + c = 4; + for ( + 0 === a && ((u = 138), (c = 3)), + e[2 * (n + 1) + 1] = 65535, + r = 0; + r <= n; + r++ + ) + (i = a), + (a = e[2 * (r + 1) + 1]), + (++s < u && i === a) || + (s < c + ? (t.bl_tree[2 * i] += s) + : 0 !== i + ? (i !== o && t.bl_tree[2 * i]++, + t.bl_tree[2 * _]++) + : s <= 10 + ? t.bl_tree[2 * w]++ + : t.bl_tree[2 * x]++, + (s = 0), + (o = i), + 0 === a + ? ((u = 138), (c = 3)) + : i === a + ? ((u = 6), (c = 3)) + : ((u = 7), (c = 4))); + }, + nt = function (t, e, n) { + var r, + i, + o = -1, + a = e[1], + s = 0, + u = 7, + c = 4; + for ( + 0 === a && ((u = 138), (c = 3)), r = 0; + r <= n; + r++ + ) + if ( + ((i = a), + (a = e[2 * (r + 1) + 1]), + !(++s < u && i === a)) + ) { + if (s < c) + do { + B(t, i, t.bl_tree); + } while (0 !== --s); + else + 0 !== i + ? (i !== o && + (B(t, i, t.bl_tree), s--), + B(t, _, t.bl_tree), + U(t, s - 3, 2)) + : s <= 10 + ? (B(t, w, t.bl_tree), + U(t, s - 3, 3)) + : (B(t, x, t.bl_tree), + U(t, s - 11, 7)); + (s = 0), + (o = i), + 0 === a + ? ((u = 138), (c = 3)) + : i === a + ? ((u = 6), (c = 3)) + : ((u = 7), (c = 4)); + } + }, + rt = function (t) { + var e; + for ( + et(t, t.dyn_ltree, t.l_desc.max_code), + et(t, t.dyn_dtree, t.d_desc.max_code), + tt(t, t.bl_desc), + e = h - 1; + e >= 3; + e-- + ) + if (0 !== t.bl_tree[2 * O[e] + 1]) break; + return (t.opt_len += 3 * (e + 1) + 5 + 5 + 4), e; + }, + it = function (t, e, n, r) { + var i; + for ( + U(t, e - 257, 5), + U(t, n - 1, 5), + U(t, r - 4, 4), + i = 0; + i < r; + i++ + ) + U(t, t.bl_tree[2 * O[i] + 1], 3); + nt(t, t.dyn_ltree, e - 1), + nt(t, t.dyn_dtree, n - 1); + }, + ot = function (t) { + var r, + i = 4093624447; + for (r = 0; r <= 31; r++, i >>>= 1) + if (1 & i && 0 !== t.dyn_ltree[2 * r]) return e; + if ( + 0 !== t.dyn_ltree[18] || + 0 !== t.dyn_ltree[20] || + 0 !== t.dyn_ltree[26] + ) + return n; + for (r = 32; r < l; r++) + if (0 !== t.dyn_ltree[2 * r]) return n; + return e; + }, + at = !1, + st = function (t) { + at || (G(), (at = !0)), + (t.l_desc = new D(t.dyn_ltree, L)), + (t.d_desc = new D(t.dyn_dtree, P)), + (t.bl_desc = new D(t.bl_tree, N)), + (t.bi_buf = 0), + (t.bi_valid = 0), + Z(t); + }, + ut = function (t, e, n, r) { + U(t, (o << 1) + (r ? 1 : 0), 3), K(t, e, n, !0); + }, + ct = function (t) { + U(t, a << 1, 3), B(t, b, A), H(t); + }, + ft = function (e, n, i, o) { + var u, + c, + f = 0; + e.level > 0 + ? (e.strm.data_type === r && + (e.strm.data_type = ot(e)), + tt(e, e.l_desc), + tt(e, e.d_desc), + (f = rt(e)), + (u = (e.opt_len + 3 + 7) >>> 3), + (c = (e.static_len + 3 + 7) >>> 3), + c <= u && (u = c)) + : (u = c = i + 5), + i + 4 <= u && -1 !== n + ? ut(e, n, i, o) + : e.strategy === t || c === u + ? (U(e, (a << 1) + (o ? 1 : 0), 3), + Q(e, A, C)) + : (U(e, (s << 1) + (o ? 1 : 0), 3), + it( + e, + e.l_desc.max_code + 1, + e.d_desc.max_code + 1, + f + 1 + ), + Q(e, e.dyn_ltree, e.dyn_dtree)), + Z(e), + o && J(e); + }, + lt = function (t, e, n) { + return ( + (t.pending_buf[t.d_buf + 2 * t.last_lit] = + (e >>> 8) & 255), + (t.pending_buf[t.d_buf + 2 * t.last_lit + 1] = + 255 & e), + (t.pending_buf[t.l_buf + t.last_lit] = 255 & n), + t.last_lit++, + 0 === e + ? t.dyn_ltree[2 * n]++ + : (t.matches++, + e--, + t.dyn_ltree[2 * (I[n] + l + 1)]++, + t.dyn_dtree[2 * F(e)]++), + t.last_lit === t.lit_bufsize - 1 + ); + }, + dt = st, + pt = ut, + ht = ft, + vt = lt, + yt = ct, + mt = { + _tr_init: dt, + _tr_stored_block: pt, + _tr_flush_block: ht, + _tr_tally: vt, + _tr_align: yt, + }, + gt = function (t, e, n, r) { + var i = (65535 & t) | 0, + o = ((t >>> 16) & 65535) | 0, + a = 0; + while (0 !== n) { + (a = n > 2e3 ? 2e3 : n), (n -= a); + do { + (i = (i + e[r++]) | 0), (o = (o + i) | 0); + } while (--a); + (i %= 65521), (o %= 65521); + } + return i | (o << 16) | 0; + }, + bt = gt, + _t = function () { + for (var t, e = [], n = 0; n < 256; n++) { + t = n; + for (var r = 0; r < 8; r++) + t = + 1 & t + ? 3988292384 ^ (t >>> 1) + : t >>> 1; + e[n] = t; + } + return e; + }, + wt = new Uint32Array(_t()), + xt = function (t, e, n, r) { + var i = wt, + o = r + n; + t ^= -1; + for (var a = r; a < o; a++) + t = (t >>> 8) ^ i[255 & (t ^ e[a])]; + return -1 ^ t; + }, + kt = xt, + St = { + 2: "need dictionary", + 1: "stream end", + 0: "", + "-1": "file error", + "-2": "stream error", + "-3": "data error", + "-4": "insufficient memory", + "-5": "buffer error", + "-6": "incompatible version", + }, + Et = { + Z_NO_FLUSH: 0, + Z_PARTIAL_FLUSH: 1, + Z_SYNC_FLUSH: 2, + Z_FULL_FLUSH: 3, + Z_FINISH: 4, + Z_BLOCK: 5, + Z_TREES: 6, + Z_OK: 0, + Z_STREAM_END: 1, + Z_NEED_DICT: 2, + Z_ERRNO: -1, + Z_STREAM_ERROR: -2, + Z_DATA_ERROR: -3, + Z_MEM_ERROR: -4, + Z_BUF_ERROR: -5, + Z_NO_COMPRESSION: 0, + Z_BEST_SPEED: 1, + Z_BEST_COMPRESSION: 9, + Z_DEFAULT_COMPRESSION: -1, + Z_FILTERED: 1, + Z_HUFFMAN_ONLY: 2, + Z_RLE: 3, + Z_FIXED: 4, + Z_DEFAULT_STRATEGY: 0, + Z_BINARY: 0, + Z_TEXT: 1, + Z_UNKNOWN: 2, + Z_DEFLATED: 8, + }, + Ot = mt._tr_init, + Tt = mt._tr_stored_block, + At = mt._tr_flush_block, + Ct = mt._tr_tally, + jt = mt._tr_align, + It = Et.Z_NO_FLUSH, + Mt = Et.Z_PARTIAL_FLUSH, + Lt = Et.Z_FULL_FLUSH, + Pt = Et.Z_FINISH, + Nt = Et.Z_BLOCK, + Rt = Et.Z_OK, + $t = Et.Z_STREAM_END, + Dt = Et.Z_STREAM_ERROR, + Ft = Et.Z_DATA_ERROR, + zt = Et.Z_BUF_ERROR, + Ut = Et.Z_DEFAULT_COMPRESSION, + Bt = Et.Z_FILTERED, + Vt = Et.Z_HUFFMAN_ONLY, + Ht = Et.Z_RLE, + qt = Et.Z_FIXED, + Wt = Et.Z_DEFAULT_STRATEGY, + Gt = Et.Z_UNKNOWN, + Zt = Et.Z_DEFLATED, + Jt = 9, + Kt = 15, + Xt = 8, + Yt = 29, + Qt = 256, + te = Qt + 1 + Yt, + ee = 30, + ne = 19, + re = 2 * te + 1, + ie = 15, + oe = 3, + ae = 258, + se = ae + oe + 1, + ue = 32, + ce = 42, + fe = 69, + le = 73, + de = 91, + pe = 103, + he = 113, + ve = 666, + ye = 1, + me = 2, + ge = 3, + be = 4, + _e = 3, + we = function (t, e) { + return (t.msg = St[e]), e; + }, + xe = function (t) { + return (t << 1) - (t > 4 ? 9 : 0); + }, + ke = function (t) { + var e = t.length; + while (--e >= 0) t[e] = 0; + }, + Se = function (t, e, n) { + return ((e << t.hash_shift) ^ n) & t.hash_mask; + }, + Ee = Se, + Oe = function (t) { + var e = t.state, + n = e.pending; + n > t.avail_out && (n = t.avail_out), + 0 !== n && + (t.output.set( + e.pending_buf.subarray( + e.pending_out, + e.pending_out + n + ), + t.next_out + ), + (t.next_out += n), + (e.pending_out += n), + (t.total_out += n), + (t.avail_out -= n), + (e.pending -= n), + 0 === e.pending && (e.pending_out = 0)); + }, + Te = function (t, e) { + At( + t, + t.block_start >= 0 ? t.block_start : -1, + t.strstart - t.block_start, + e + ), + (t.block_start = t.strstart), + Oe(t.strm); + }, + Ae = function (t, e) { + t.pending_buf[t.pending++] = e; + }, + Ce = function (t, e) { + (t.pending_buf[t.pending++] = (e >>> 8) & 255), + (t.pending_buf[t.pending++] = 255 & e); + }, + je = function (t, e, n, r) { + var i = t.avail_in; + return ( + i > r && (i = r), + 0 === i + ? 0 + : ((t.avail_in -= i), + e.set( + t.input.subarray( + t.next_in, + t.next_in + i + ), + n + ), + 1 === t.state.wrap + ? (t.adler = bt(t.adler, e, i, n)) + : 2 === t.state.wrap && + (t.adler = kt(t.adler, e, i, n)), + (t.next_in += i), + (t.total_in += i), + i) + ); + }, + Ie = function (t, e) { + var n, + r, + i = t.max_chain_length, + o = t.strstart, + a = t.prev_length, + s = t.nice_match, + u = + t.strstart > t.w_size - se + ? t.strstart - (t.w_size - se) + : 0, + c = t.window, + f = t.w_mask, + l = t.prev, + d = t.strstart + ae, + p = c[o + a - 1], + h = c[o + a]; + t.prev_length >= t.good_match && (i >>= 2), + s > t.lookahead && (s = t.lookahead); + do { + if ( + ((n = e), + c[n + a] === h && + c[n + a - 1] === p && + c[n] === c[o] && + c[++n] === c[o + 1]) + ) { + (o += 2), n++; + do {} while ( + c[++o] === c[++n] && + c[++o] === c[++n] && + c[++o] === c[++n] && + c[++o] === c[++n] && + c[++o] === c[++n] && + c[++o] === c[++n] && + c[++o] === c[++n] && + c[++o] === c[++n] && + o < d + ); + if ( + ((r = ae - (d - o)), + (o = d - ae), + r > a) + ) { + if ( + ((t.match_start = e), + (a = r), + r >= s) + ) + break; + (p = c[o + a - 1]), (h = c[o + a]); + } + } + } while ((e = l[e & f]) > u && 0 !== --i); + return a <= t.lookahead ? a : t.lookahead; + }, + Me = function (t) { + var e, + n, + r, + i, + o, + a = t.w_size; + do { + if ( + ((i = + t.window_size - + t.lookahead - + t.strstart), + t.strstart >= a + (a - se)) + ) { + t.window.set( + t.window.subarray(a, a + a), + 0 + ), + (t.match_start -= a), + (t.strstart -= a), + (t.block_start -= a), + (n = t.hash_size), + (e = n); + do { + (r = t.head[--e]), + (t.head[e] = r >= a ? r - a : 0); + } while (--n); + (n = a), (e = n); + do { + (r = t.prev[--e]), + (t.prev[e] = r >= a ? r - a : 0); + } while (--n); + i += a; + } + if (0 === t.strm.avail_in) break; + if ( + ((n = je( + t.strm, + t.window, + t.strstart + t.lookahead, + i + )), + (t.lookahead += n), + t.lookahead + t.insert >= oe) + ) { + (o = t.strstart - t.insert), + (t.ins_h = t.window[o]), + (t.ins_h = Ee( + t, + t.ins_h, + t.window[o + 1] + )); + while (t.insert) + if ( + ((t.ins_h = Ee( + t, + t.ins_h, + t.window[o + oe - 1] + )), + (t.prev[o & t.w_mask] = + t.head[t.ins_h]), + (t.head[t.ins_h] = o), + o++, + t.insert--, + t.lookahead + t.insert < oe) + ) + break; + } + } while (t.lookahead < se && 0 !== t.strm.avail_in); + }, + Le = function (t, e) { + var n = 65535; + for ( + n > t.pending_buf_size - 5 && + (n = t.pending_buf_size - 5); + ; + + ) { + if (t.lookahead <= 1) { + if ((Me(t), 0 === t.lookahead && e === It)) + return ye; + if (0 === t.lookahead) break; + } + (t.strstart += t.lookahead), (t.lookahead = 0); + var r = t.block_start + n; + if ( + (0 === t.strstart || t.strstart >= r) && + ((t.lookahead = t.strstart - r), + (t.strstart = r), + Te(t, !1), + 0 === t.strm.avail_out) + ) + return ye; + if ( + t.strstart - t.block_start >= + t.w_size - se && + (Te(t, !1), 0 === t.strm.avail_out) + ) + return ye; + } + return ( + (t.insert = 0), + e === Pt + ? (Te(t, !0), + 0 === t.strm.avail_out ? ge : be) + : (t.strstart > t.block_start && + (Te(t, !1), t.strm.avail_out), + ye) + ); + }, + Pe = function (t, e) { + for (var n, r; ; ) { + if (t.lookahead < se) { + if ((Me(t), t.lookahead < se && e === It)) + return ye; + if (0 === t.lookahead) break; + } + if ( + ((n = 0), + t.lookahead >= oe && + ((t.ins_h = Ee( + t, + t.ins_h, + t.window[t.strstart + oe - 1] + )), + (n = t.prev[t.strstart & t.w_mask] = + t.head[t.ins_h]), + (t.head[t.ins_h] = t.strstart)), + 0 !== n && + t.strstart - n <= t.w_size - se && + (t.match_length = Ie(t, n)), + t.match_length >= oe) + ) + if ( + ((r = Ct( + t, + t.strstart - t.match_start, + t.match_length - oe + )), + (t.lookahead -= t.match_length), + t.match_length <= t.max_lazy_match && + t.lookahead >= oe) + ) { + t.match_length--; + do { + t.strstart++, + (t.ins_h = Ee( + t, + t.ins_h, + t.window[ + t.strstart + oe - 1 + ] + )), + (n = t.prev[ + t.strstart & t.w_mask + ] = + t.head[t.ins_h]), + (t.head[t.ins_h] = t.strstart); + } while (0 !== --t.match_length); + t.strstart++; + } else + (t.strstart += t.match_length), + (t.match_length = 0), + (t.ins_h = t.window[t.strstart]), + (t.ins_h = Ee( + t, + t.ins_h, + t.window[t.strstart + 1] + )); + else + (r = Ct(t, 0, t.window[t.strstart])), + t.lookahead--, + t.strstart++; + if (r && (Te(t, !1), 0 === t.strm.avail_out)) + return ye; + } + return ( + (t.insert = + t.strstart < oe - 1 ? t.strstart : oe - 1), + e === Pt + ? (Te(t, !0), + 0 === t.strm.avail_out ? ge : be) + : t.last_lit && + (Te(t, !1), 0 === t.strm.avail_out) + ? ye + : me + ); + }, + Ne = function (t, e) { + for (var n, r, i; ; ) { + if (t.lookahead < se) { + if ((Me(t), t.lookahead < se && e === It)) + return ye; + if (0 === t.lookahead) break; + } + if ( + ((n = 0), + t.lookahead >= oe && + ((t.ins_h = Ee( + t, + t.ins_h, + t.window[t.strstart + oe - 1] + )), + (n = t.prev[t.strstart & t.w_mask] = + t.head[t.ins_h]), + (t.head[t.ins_h] = t.strstart)), + (t.prev_length = t.match_length), + (t.prev_match = t.match_start), + (t.match_length = oe - 1), + 0 !== n && + t.prev_length < t.max_lazy_match && + t.strstart - n <= t.w_size - se && + ((t.match_length = Ie(t, n)), + t.match_length <= 5 && + (t.strategy === Bt || + (t.match_length === oe && + t.strstart - t.match_start > + 4096)) && + (t.match_length = oe - 1)), + t.prev_length >= oe && + t.match_length <= t.prev_length) + ) { + (i = t.strstart + t.lookahead - oe), + (r = Ct( + t, + t.strstart - 1 - t.prev_match, + t.prev_length - oe + )), + (t.lookahead -= t.prev_length - 1), + (t.prev_length -= 2); + do { + ++t.strstart <= i && + ((t.ins_h = Ee( + t, + t.ins_h, + t.window[t.strstart + oe - 1] + )), + (n = t.prev[t.strstart & t.w_mask] = + t.head[t.ins_h]), + (t.head[t.ins_h] = t.strstart)); + } while (0 !== --t.prev_length); + if ( + ((t.match_available = 0), + (t.match_length = oe - 1), + t.strstart++, + r && + (Te(t, !1), 0 === t.strm.avail_out)) + ) + return ye; + } else if (t.match_available) { + if ( + ((r = Ct( + t, + 0, + t.window[t.strstart - 1] + )), + r && Te(t, !1), + t.strstart++, + t.lookahead--, + 0 === t.strm.avail_out) + ) + return ye; + } else + (t.match_available = 1), + t.strstart++, + t.lookahead--; + } + return ( + t.match_available && + ((r = Ct(t, 0, t.window[t.strstart - 1])), + (t.match_available = 0)), + (t.insert = + t.strstart < oe - 1 ? t.strstart : oe - 1), + e === Pt + ? (Te(t, !0), + 0 === t.strm.avail_out ? ge : be) + : t.last_lit && + (Te(t, !1), 0 === t.strm.avail_out) + ? ye + : me + ); + }, + Re = function (t, e) { + for (var n, r, i, o, a = t.window; ; ) { + if (t.lookahead <= ae) { + if ((Me(t), t.lookahead <= ae && e === It)) + return ye; + if (0 === t.lookahead) break; + } + if ( + ((t.match_length = 0), + t.lookahead >= oe && + t.strstart > 0 && + ((i = t.strstart - 1), + (r = a[i]), + r === a[++i] && + r === a[++i] && + r === a[++i])) + ) { + o = t.strstart + ae; + do {} while ( + r === a[++i] && + r === a[++i] && + r === a[++i] && + r === a[++i] && + r === a[++i] && + r === a[++i] && + r === a[++i] && + r === a[++i] && + i < o + ); + (t.match_length = ae - (o - i)), + t.match_length > t.lookahead && + (t.match_length = t.lookahead); + } + if ( + (t.match_length >= oe + ? ((n = Ct(t, 1, t.match_length - oe)), + (t.lookahead -= t.match_length), + (t.strstart += t.match_length), + (t.match_length = 0)) + : ((n = Ct(t, 0, t.window[t.strstart])), + t.lookahead--, + t.strstart++), + n && (Te(t, !1), 0 === t.strm.avail_out)) + ) + return ye; + } + return ( + (t.insert = 0), + e === Pt + ? (Te(t, !0), + 0 === t.strm.avail_out ? ge : be) + : t.last_lit && + (Te(t, !1), 0 === t.strm.avail_out) + ? ye + : me + ); + }, + $e = function (t, e) { + for (var n; ; ) { + if ( + 0 === t.lookahead && + (Me(t), 0 === t.lookahead) + ) { + if (e === It) return ye; + break; + } + if ( + ((t.match_length = 0), + (n = Ct(t, 0, t.window[t.strstart])), + t.lookahead--, + t.strstart++, + n && (Te(t, !1), 0 === t.strm.avail_out)) + ) + return ye; + } + return ( + (t.insert = 0), + e === Pt + ? (Te(t, !0), + 0 === t.strm.avail_out ? ge : be) + : t.last_lit && + (Te(t, !1), 0 === t.strm.avail_out) + ? ye + : me + ); + }; + function De(t, e, n, r, i) { + (this.good_length = t), + (this.max_lazy = e), + (this.nice_length = n), + (this.max_chain = r), + (this.func = i); + } + var Fe = [ + new De(0, 0, 0, 0, Le), + new De(4, 4, 8, 4, Pe), + new De(4, 5, 16, 8, Pe), + new De(4, 6, 32, 32, Pe), + new De(4, 4, 16, 16, Ne), + new De(8, 16, 32, 32, Ne), + new De(8, 16, 128, 128, Ne), + new De(8, 32, 128, 256, Ne), + new De(32, 128, 258, 1024, Ne), + new De(32, 258, 258, 4096, Ne), + ], + ze = function (t) { + (t.window_size = 2 * t.w_size), + ke(t.head), + (t.max_lazy_match = Fe[t.level].max_lazy), + (t.good_match = Fe[t.level].good_length), + (t.nice_match = Fe[t.level].nice_length), + (t.max_chain_length = Fe[t.level].max_chain), + (t.strstart = 0), + (t.block_start = 0), + (t.lookahead = 0), + (t.insert = 0), + (t.match_length = t.prev_length = oe - 1), + (t.match_available = 0), + (t.ins_h = 0); + }; + function Ue() { + (this.strm = null), + (this.status = 0), + (this.pending_buf = null), + (this.pending_buf_size = 0), + (this.pending_out = 0), + (this.pending = 0), + (this.wrap = 0), + (this.gzhead = null), + (this.gzindex = 0), + (this.method = Zt), + (this.last_flush = -1), + (this.w_size = 0), + (this.w_bits = 0), + (this.w_mask = 0), + (this.window = null), + (this.window_size = 0), + (this.prev = null), + (this.head = null), + (this.ins_h = 0), + (this.hash_size = 0), + (this.hash_bits = 0), + (this.hash_mask = 0), + (this.hash_shift = 0), + (this.block_start = 0), + (this.match_length = 0), + (this.prev_match = 0), + (this.match_available = 0), + (this.strstart = 0), + (this.match_start = 0), + (this.lookahead = 0), + (this.prev_length = 0), + (this.max_chain_length = 0), + (this.max_lazy_match = 0), + (this.level = 0), + (this.strategy = 0), + (this.good_match = 0), + (this.nice_match = 0), + (this.dyn_ltree = new Uint16Array(2 * re)), + (this.dyn_dtree = new Uint16Array( + 2 * (2 * ee + 1) + )), + (this.bl_tree = new Uint16Array(2 * (2 * ne + 1))), + ke(this.dyn_ltree), + ke(this.dyn_dtree), + ke(this.bl_tree), + (this.l_desc = null), + (this.d_desc = null), + (this.bl_desc = null), + (this.bl_count = new Uint16Array(ie + 1)), + (this.heap = new Uint16Array(2 * te + 1)), + ke(this.heap), + (this.heap_len = 0), + (this.heap_max = 0), + (this.depth = new Uint16Array(2 * te + 1)), + ke(this.depth), + (this.l_buf = 0), + (this.lit_bufsize = 0), + (this.last_lit = 0), + (this.d_buf = 0), + (this.opt_len = 0), + (this.static_len = 0), + (this.matches = 0), + (this.insert = 0), + (this.bi_buf = 0), + (this.bi_valid = 0); + } + var Be = function (t) { + if (!t || !t.state) return we(t, Dt); + (t.total_in = t.total_out = 0), (t.data_type = Gt); + var e = t.state; + return ( + (e.pending = 0), + (e.pending_out = 0), + e.wrap < 0 && (e.wrap = -e.wrap), + (e.status = e.wrap ? ce : he), + (t.adler = 2 === e.wrap ? 0 : 1), + (e.last_flush = It), + Ot(e), + Rt + ); + }, + Ve = function (t) { + var e = Be(t); + return e === Rt && ze(t.state), e; + }, + He = function (t, e) { + return t && t.state + ? 2 !== t.state.wrap + ? Dt + : ((t.state.gzhead = e), Rt) + : Dt; + }, + qe = function (t, e, n, r, i, o) { + if (!t) return Dt; + var a = 1; + if ( + (e === Ut && (e = 6), + r < 0 + ? ((a = 0), (r = -r)) + : r > 15 && ((a = 2), (r -= 16)), + i < 1 || + i > Jt || + n !== Zt || + r < 8 || + r > 15 || + e < 0 || + e > 9 || + o < 0 || + o > qt) + ) + return we(t, Dt); + 8 === r && (r = 9); + var s = new Ue(); + return ( + (t.state = s), + (s.strm = t), + (s.wrap = a), + (s.gzhead = null), + (s.w_bits = r), + (s.w_size = 1 << s.w_bits), + (s.w_mask = s.w_size - 1), + (s.hash_bits = i + 7), + (s.hash_size = 1 << s.hash_bits), + (s.hash_mask = s.hash_size - 1), + (s.hash_shift = ~~( + (s.hash_bits + oe - 1) / + oe + )), + (s.window = new Uint8Array(2 * s.w_size)), + (s.head = new Uint16Array(s.hash_size)), + (s.prev = new Uint16Array(s.w_size)), + (s.lit_bufsize = 1 << (i + 6)), + (s.pending_buf_size = 4 * s.lit_bufsize), + (s.pending_buf = new Uint8Array( + s.pending_buf_size + )), + (s.d_buf = 1 * s.lit_bufsize), + (s.l_buf = 3 * s.lit_bufsize), + (s.level = e), + (s.strategy = o), + (s.method = n), + Ve(t) + ); + }, + We = function (t, e) { + return qe(t, e, Zt, Kt, Xt, Wt); + }, + Ge = function (t, e) { + var n, r; + if (!t || !t.state || e > Nt || e < 0) + return t ? we(t, Dt) : Dt; + var i = t.state; + if ( + !t.output || + (!t.input && 0 !== t.avail_in) || + (i.status === ve && e !== Pt) + ) + return we(t, 0 === t.avail_out ? zt : Dt); + i.strm = t; + var o = i.last_flush; + if (((i.last_flush = e), i.status === ce)) + if (2 === i.wrap) + (t.adler = 0), + Ae(i, 31), + Ae(i, 139), + Ae(i, 8), + i.gzhead + ? (Ae( + i, + (i.gzhead.text ? 1 : 0) + + (i.gzhead.hcrc ? 2 : 0) + + (i.gzhead.extra ? 4 : 0) + + (i.gzhead.name ? 8 : 0) + + (i.gzhead.comment + ? 16 + : 0) + ), + Ae(i, 255 & i.gzhead.time), + Ae(i, (i.gzhead.time >> 8) & 255), + Ae( + i, + (i.gzhead.time >> 16) & 255 + ), + Ae( + i, + (i.gzhead.time >> 24) & 255 + ), + Ae( + i, + 9 === i.level + ? 2 + : i.strategy >= Vt || + i.level < 2 + ? 4 + : 0 + ), + Ae(i, 255 & i.gzhead.os), + i.gzhead.extra && + i.gzhead.extra.length && + (Ae( + i, + 255 & + i.gzhead.extra.length + ), + Ae( + i, + (i.gzhead.extra.length >> + 8) & + 255 + )), + i.gzhead.hcrc && + (t.adler = kt( + t.adler, + i.pending_buf, + i.pending, + 0 + )), + (i.gzindex = 0), + (i.status = fe)) + : (Ae(i, 0), + Ae(i, 0), + Ae(i, 0), + Ae(i, 0), + Ae(i, 0), + Ae( + i, + 9 === i.level + ? 2 + : i.strategy >= Vt || + i.level < 2 + ? 4 + : 0 + ), + Ae(i, _e), + (i.status = he)); + else { + var a = (Zt + ((i.w_bits - 8) << 4)) << 8, + s = -1; + (s = + i.strategy >= Vt || i.level < 2 + ? 0 + : i.level < 6 + ? 1 + : 6 === i.level + ? 2 + : 3), + (a |= s << 6), + 0 !== i.strstart && (a |= ue), + (a += 31 - (a % 31)), + (i.status = he), + Ce(i, a), + 0 !== i.strstart && + (Ce(i, t.adler >>> 16), + Ce(i, 65535 & t.adler)), + (t.adler = 1); + } + if (i.status === fe) + if (i.gzhead.extra) { + n = i.pending; + while ( + i.gzindex < + (65535 & i.gzhead.extra.length) + ) { + if ( + i.pending === i.pending_buf_size && + (i.gzhead.hcrc && + i.pending > n && + (t.adler = kt( + t.adler, + i.pending_buf, + i.pending - n, + n + )), + Oe(t), + (n = i.pending), + i.pending === i.pending_buf_size) + ) + break; + Ae(i, 255 & i.gzhead.extra[i.gzindex]), + i.gzindex++; + } + i.gzhead.hcrc && + i.pending > n && + (t.adler = kt( + t.adler, + i.pending_buf, + i.pending - n, + n + )), + i.gzindex === i.gzhead.extra.length && + ((i.gzindex = 0), (i.status = le)); + } else i.status = le; + if (i.status === le) + if (i.gzhead.name) { + n = i.pending; + do { + if ( + i.pending === i.pending_buf_size && + (i.gzhead.hcrc && + i.pending > n && + (t.adler = kt( + t.adler, + i.pending_buf, + i.pending - n, + n + )), + Oe(t), + (n = i.pending), + i.pending === i.pending_buf_size) + ) { + r = 1; + break; + } + (r = + i.gzindex < i.gzhead.name.length + ? 255 & + i.gzhead.name.charCodeAt( + i.gzindex++ + ) + : 0), + Ae(i, r); + } while (0 !== r); + i.gzhead.hcrc && + i.pending > n && + (t.adler = kt( + t.adler, + i.pending_buf, + i.pending - n, + n + )), + 0 === r && + ((i.gzindex = 0), (i.status = de)); + } else i.status = de; + if (i.status === de) + if (i.gzhead.comment) { + n = i.pending; + do { + if ( + i.pending === i.pending_buf_size && + (i.gzhead.hcrc && + i.pending > n && + (t.adler = kt( + t.adler, + i.pending_buf, + i.pending - n, + n + )), + Oe(t), + (n = i.pending), + i.pending === i.pending_buf_size) + ) { + r = 1; + break; + } + (r = + i.gzindex < i.gzhead.comment.length + ? 255 & + i.gzhead.comment.charCodeAt( + i.gzindex++ + ) + : 0), + Ae(i, r); + } while (0 !== r); + i.gzhead.hcrc && + i.pending > n && + (t.adler = kt( + t.adler, + i.pending_buf, + i.pending - n, + n + )), + 0 === r && (i.status = pe); + } else i.status = pe; + if ( + (i.status === pe && + (i.gzhead.hcrc + ? (i.pending + 2 > i.pending_buf_size && + Oe(t), + i.pending + 2 <= i.pending_buf_size && + (Ae(i, 255 & t.adler), + Ae(i, (t.adler >> 8) & 255), + (t.adler = 0), + (i.status = he))) + : (i.status = he)), + 0 !== i.pending) + ) { + if ((Oe(t), 0 === t.avail_out)) + return (i.last_flush = -1), Rt; + } else if ( + 0 === t.avail_in && + xe(e) <= xe(o) && + e !== Pt + ) + return we(t, zt); + if (i.status === ve && 0 !== t.avail_in) + return we(t, zt); + if ( + 0 !== t.avail_in || + 0 !== i.lookahead || + (e !== It && i.status !== ve) + ) { + var u = + i.strategy === Vt + ? $e(i, e) + : i.strategy === Ht + ? Re(i, e) + : Fe[i.level].func(i, e); + if ( + ((u !== ge && u !== be) || (i.status = ve), + u === ye || u === ge) + ) + return ( + 0 === t.avail_out && + (i.last_flush = -1), + Rt + ); + if ( + u === me && + (e === Mt + ? jt(i) + : e !== Nt && + (Tt(i, 0, 0, !1), + e === Lt && + (ke(i.head), + 0 === i.lookahead && + ((i.strstart = 0), + (i.block_start = 0), + (i.insert = 0)))), + Oe(t), + 0 === t.avail_out) + ) + return (i.last_flush = -1), Rt; + } + return e !== Pt + ? Rt + : i.wrap <= 0 + ? $t + : (2 === i.wrap + ? (Ae(i, 255 & t.adler), + Ae(i, (t.adler >> 8) & 255), + Ae(i, (t.adler >> 16) & 255), + Ae(i, (t.adler >> 24) & 255), + Ae(i, 255 & t.total_in), + Ae(i, (t.total_in >> 8) & 255), + Ae(i, (t.total_in >> 16) & 255), + Ae(i, (t.total_in >> 24) & 255)) + : (Ce(i, t.adler >>> 16), + Ce(i, 65535 & t.adler)), + Oe(t), + i.wrap > 0 && (i.wrap = -i.wrap), + 0 !== i.pending ? Rt : $t); + }, + Ze = function (t) { + if (!t || !t.state) return Dt; + var e = t.state.status; + return e !== ce && + e !== fe && + e !== le && + e !== de && + e !== pe && + e !== he && + e !== ve + ? we(t, Dt) + : ((t.state = null), e === he ? we(t, Ft) : Rt); + }, + Je = function (t, e) { + var n = e.length; + if (!t || !t.state) return Dt; + var r = t.state, + i = r.wrap; + if ( + 2 === i || + (1 === i && r.status !== ce) || + r.lookahead + ) + return Dt; + if ( + (1 === i && (t.adler = bt(t.adler, e, n, 0)), + (r.wrap = 0), + n >= r.w_size) + ) { + 0 === i && + (ke(r.head), + (r.strstart = 0), + (r.block_start = 0), + (r.insert = 0)); + var o = new Uint8Array(r.w_size); + o.set(e.subarray(n - r.w_size, n), 0), + (e = o), + (n = r.w_size); + } + var a = t.avail_in, + s = t.next_in, + u = t.input; + (t.avail_in = n), + (t.next_in = 0), + (t.input = e), + Me(r); + while (r.lookahead >= oe) { + var c = r.strstart, + f = r.lookahead - (oe - 1); + do { + (r.ins_h = Ee( + r, + r.ins_h, + r.window[c + oe - 1] + )), + (r.prev[c & r.w_mask] = + r.head[r.ins_h]), + (r.head[r.ins_h] = c), + c++; + } while (--f); + (r.strstart = c), (r.lookahead = oe - 1), Me(r); + } + return ( + (r.strstart += r.lookahead), + (r.block_start = r.strstart), + (r.insert = r.lookahead), + (r.lookahead = 0), + (r.match_length = r.prev_length = oe - 1), + (r.match_available = 0), + (t.next_in = s), + (t.input = u), + (t.avail_in = a), + (r.wrap = i), + Rt + ); + }, + Ke = We, + Xe = qe, + Ye = Ve, + Qe = Be, + tn = He, + en = Ge, + nn = Ze, + rn = Je, + on = "pako deflate (from Nodeca project)", + an = { + deflateInit: Ke, + deflateInit2: Xe, + deflateReset: Ye, + deflateResetKeep: Qe, + deflateSetHeader: tn, + deflate: en, + deflateEnd: nn, + deflateSetDictionary: rn, + deflateInfo: on, + }; + function sn(t) { + for (var e = 0, n = 0, r = t.length; n < r; n++) + e += t[n].length; + for ( + var i = new Uint8Array(e), + o = 0, + a = 0, + s = t.length; + o < s; + o++ + ) { + var u = t[o]; + i.set(u, a), (a += u.length); + } + return i; + } + for (var un = new Uint8Array(256), cn = 0; cn < 256; cn++) + un[cn] = + cn >= 252 + ? 6 + : cn >= 248 + ? 5 + : cn >= 240 + ? 4 + : cn >= 224 + ? 3 + : cn >= 192 + ? 2 + : 1; + function fn() { + (this.input = null), + (this.next_in = 0), + (this.avail_in = 0), + (this.total_in = 0), + (this.output = null), + (this.next_out = 0), + (this.avail_out = 0), + (this.total_out = 0), + (this.msg = ""), + (this.state = null), + (this.data_type = 2), + (this.adler = 0); + } + un[254] = un[254] = 1; + var ln = fn, + dn = Object.prototype.toString, + pn = Et.Z_NO_FLUSH, + hn = Et.Z_SYNC_FLUSH, + vn = Et.Z_FULL_FLUSH, + yn = Et.Z_FINISH, + mn = Et.Z_OK, + gn = Et.Z_STREAM_END, + bn = Et.Z_DEFAULT_COMPRESSION, + _n = Et.Z_DEFAULT_STRATEGY, + wn = Et.Z_DEFLATED; + function xn() { + this.options = { + level: bn, + method: wn, + chunkSize: 16384, + windowBits: 15, + memLevel: 8, + strategy: _n, + }; + var t = this.options; + t.raw && t.windowBits > 0 + ? (t.windowBits = -t.windowBits) + : t.gzip && + t.windowBits > 0 && + t.windowBits < 16 && + (t.windowBits += 16), + (this.err = 0), + (this.msg = ""), + (this.ended = !1), + (this.chunks = []), + (this.strm = new ln()), + (this.strm.avail_out = 0); + var e = an.deflateInit2( + this.strm, + t.level, + t.method, + t.windowBits, + t.memLevel, + t.strategy + ); + if (e !== mn) throw new Error(St[e]); + if ( + (t.header && + an.deflateSetHeader(this.strm, t.header), + t.dictionary) + ) { + var n; + if ( + ((n = + "[object ArrayBuffer]" === + dn.call(t.dictionary) + ? new Uint8Array(t.dictionary) + : t.dictionary), + (e = an.deflateSetDictionary(this.strm, n)), + e !== mn) + ) + throw new Error(St[e]); + this._dict_set = !0; + } + } + function kn(t) { + if ( + "function" === typeof TextEncoder && + TextEncoder.prototype.encode + ) + return new TextEncoder().encode(t); + var e, + n, + r, + i, + o, + a = t.length, + s = 0; + for (i = 0; i < a; i++) + (n = t.charCodeAt(i)), + 55296 === (64512 & n) && + i + 1 < a && + ((r = t.charCodeAt(i + 1)), + 56320 === (64512 & r) && + ((n = + 65536 + + ((n - 55296) << 10) + + (r - 56320)), + i++)), + (s += + n < 128 + ? 1 + : n < 2048 + ? 2 + : n < 65536 + ? 3 + : 4); + for (e = new Uint8Array(s), o = 0, i = 0; o < s; i++) + (n = t.charCodeAt(i)), + 55296 === (64512 & n) && + i + 1 < a && + ((r = t.charCodeAt(i + 1)), + 56320 === (64512 & r) && + ((n = + 65536 + + ((n - 55296) << 10) + + (r - 56320)), + i++)), + n < 128 + ? (e[o++] = n) + : n < 2048 + ? ((e[o++] = 192 | (n >>> 6)), + (e[o++] = 128 | (63 & n))) + : n < 65536 + ? ((e[o++] = 224 | (n >>> 12)), + (e[o++] = 128 | ((n >>> 6) & 63)), + (e[o++] = 128 | (63 & n))) + : ((e[o++] = 240 | (n >>> 18)), + (e[o++] = 128 | ((n >>> 12) & 63)), + (e[o++] = 128 | ((n >>> 6) & 63)), + (e[o++] = 128 | (63 & n))); + return e; + } + return ( + (xn.prototype.push = function (t, e) { + var n, + r, + i = this.strm, + o = this.options.chunkSize; + if (this.ended) return !1; + for ( + r = e === ~~e ? e : !0 === e ? yn : pn, + "[object ArrayBuffer]" === dn.call(t) + ? (i.input = new Uint8Array(t)) + : (i.input = t), + i.next_in = 0, + i.avail_in = i.input.length; + ; + + ) + if ( + (0 === i.avail_out && + ((i.output = new Uint8Array(o)), + (i.next_out = 0), + (i.avail_out = o)), + (r === hn || r === vn) && i.avail_out <= 6) + ) + this.onData( + i.output.subarray(0, i.next_out) + ), + (i.avail_out = 0); + else { + if (((n = an.deflate(i, r)), n === gn)) + return ( + i.next_out > 0 && + this.onData( + i.output.subarray( + 0, + i.next_out + ) + ), + (n = an.deflateEnd(this.strm)), + this.onEnd(n), + (this.ended = !0), + n === mn + ); + if (0 !== i.avail_out) { + if (r > 0 && i.next_out > 0) + this.onData( + i.output.subarray(0, i.next_out) + ), + (i.avail_out = 0); + else if (0 === i.avail_in) break; + } else this.onData(i.output); + } + return !0; + }), + (xn.prototype.onData = function (t) { + this.chunks.push(t); + }), + (xn.prototype.onEnd = function (t) { + t === mn && (this.result = sn(this.chunks)), + (this.chunks = []), + (this.err = t), + (this.msg = this.strm.msg); + }), + { Deflate: xn, constants: Et, string2buf: kn } + ); + } + t(function () { + var n = e(), + r = n.Deflate, + i = n.constants, + o = n.string2buf, + a = new r(), + s = 0; + function u(t) { + var e = o(t); + return ( + a.push(e, i.Z_SYNC_FLUSH), (s += e.length), e.length + ); + } + self.addEventListener( + "message", + t(function (t) { + var e = t.data; + switch (e.action) { + case "init": + self.postMessage({ type: "initialized" }); + break; + case "write": + var n = u(e.data); + self.postMessage({ + type: "wrote", + id: e.id, + compressedSize: a.chunks.reduce( + function (t, e) { + return t + e.length; + }, + 0 + ), + additionalRawSize: n, + }); + break; + case "flush": + n = e.data ? u(e.data) : 0; + a.push("", i.Z_FINISH), + self.postMessage({ + type: "flushed", + id: e.id, + result: a.result, + additionalRawSize: n, + rawSize: s, + }), + (a = new r()), + (s = 0); + break; + } + }) + ); + })(); + } + var iu = { status: 0 }; + function ou(t, e) { + switch ((void 0 === e && (e = nu), iu.status)) { + case 0: + (iu = { status: 1, callbacks: [t] }), au(e); + break; + case 1: + iu.callbacks.push(t); + break; + case 2: + t(); + break; + case 3: + t(iu.worker); + break; + } + } + function au(t) { + void 0 === t && (t = nu); + try { + var e = t(); + return ( + e.addEventListener("error", Tt(uu)), + e.addEventListener( + "message", + Tt(function (t) { + var n = t.data; + "errored" === n.type + ? uu(n.error) + : "initialized" === n.type && su(e); + }) + ), + e.postMessage({ action: "init" }), + e + ); + } catch (n) { + uu(n); + } + } + function su(t) { + 1 === iu.status && + (iu.callbacks.forEach(function (e) { + return e(t); + }), + (iu = { status: 3, worker: t })); + } + function uu(t) { + 1 === iu.status + ? (r.error( + "Session Replay recording failed to start: an error occurred while creating the Worker:", + t + ), + t instanceof Event || + (t instanceof Error && + j(t.message, "Content Security Policy")) + ? r.error( + "Please make sure CSP is correctly configured https://docs.datadoghq.com/real_user_monitoring/faq/content_security_policy" + ) + : jt(t), + iu.callbacks.forEach(function (t) { + return t(); + }), + (iu = { status: 2 })) + : jt(t); + } + function cu(t, e, n, r, i) { + var o = Qs( + t, + e.applicationId, + n, + r, + function (t, n, r, i) { + return $s( + e.sessionReplayEndpointBuilder, + t, + n, + r, + i + ); + }, + i + ), + a = o.addRecord, + s = o.stop, + u = Ps({ + emit: a, + defaultPrivacyLevel: e.defaultPrivacyLevel, + }), + c = u.stop, + f = u.takeFullSnapshot, + l = u.flushMutations, + d = t.subscribe(4, function () { + l(), a({ timestamp: pt(), type: Qo.ViewEnd }); + }).unsubscribe, + p = t.subscribe(2, function (t) { + f(t.startClocks.timeStamp); + }).unsubscribe; + return { + stop: function () { + d(), p(), c(), s(); + }, + }; + } + function fu(t, e) { + if ((void 0 === e && (e = ou), Vt())) + return { + start: T, + stop: T, + getReplayStats: function () {}, + onRumStart: T, + isRecording: function () { + return !1; + }, + }; + var n = { status: 0 }, + r = function () { + n = { status: 1 }; + }, + i = function () { + n = { status: 0 }; + }; + return { + start: function () { + return r(); + }, + stop: function () { + return i(); + }, + getReplayStats: qs, + onRumStart: function (o, a, s, u) { + o.subscribe(7, function () { + (2 !== n.status && 3 !== n.status) || + (i(), (n = { status: 1 })); + }), + o.subscribe(8, function () { + 1 === n.status && r(); + }), + (r = function () { + var r = s.findTrackedSession(); + r && r.hasReplayPlan + ? 2 !== n.status && + 3 !== n.status && + ((n = { status: 2 }), + G("interactive", function () { + 2 === n.status && + e(function (e) { + if (2 === n.status) + if (e) { + var r = t( + o, + a, + s, + u, + e + ).stop; + n = { + status: 3, + stopRecording: r, + }; + } else n = { status: 0 }; + }); + })) + : (n = { status: 1 }); + }), + (i = function () { + 0 !== n.status && + (3 === n.status && n.stopRecording(), + (n = { status: 0 })); + }), + 1 === n.status && r(); + }, + isRecording: function () { + return 3 === n.status; + }, + }; + } + n.d(e, "a", function () { + return du; + }); + var lu = fu(cu), + du = Oe(Ko, lu); + Dt(z(), "DD_RUM", du); + }, + "0d58": function (t, e, n) { + var r = n("ce10"), + i = n("e11e"); + t.exports = + Object.keys || + function (t) { + return r(t, i); + }; + }, + "0f89": function (t, e, n) { + var r = n("6f8a"); + t.exports = function (t) { + if (!r(t)) throw TypeError(t + " is not an object!"); + return t; + }; + }, + "103a": function (t, e, n) { + var r = n("da3c").document; + t.exports = r && r.documentElement; + }, + "111f": function (t, e, n) { + "use strict"; + var r = n("d13f"); + t.exports = function (t) { + r(r.S, t, { + of: function () { + var t = arguments.length, + e = new Array(t); + while (t--) e[t] = arguments[t]; + return new this(e); + }, + }); + }; + }, + 1169: function (t, e, n) { + var r = n("2d95"); + t.exports = + Array.isArray || + function (t) { + return "Array" == r(t); + }; + }, + "11c1": function (t, e, n) { + var r = n("c437"), + i = n("c64e"), + o = i; + (o.v1 = r), (o.v4 = i), (t.exports = o); + }, + "11e9": function (t, e, n) { + var r = n("52a7"), + i = n("4630"), + o = n("6821"), + a = n("6a99"), + s = n("69a8"), + u = n("c69a"), + c = Object.getOwnPropertyDescriptor; + e.f = n("9e1e") + ? c + : function (t, e) { + if (((t = o(t)), (e = a(e, !0)), u)) + try { + return c(t, e); + } catch (n) {} + if (s(t, e)) return i(!r.f.call(t, e), t[e]); + }; + }, + "12fd": function (t, e, n) { + var r = n("6f8a"), + i = n("da3c").document, + o = r(i) && r(i.createElement); + t.exports = function (t) { + return o ? i.createElement(t) : {}; + }; + }, + "12fd9": function (t, e) {}, + "1331e": function (t, e, n) { + "use strict"; + Object.defineProperty(e, "__esModule", { value: !0 }), + (e.default = void 0); + var r = n("78ef"), + i = (0, r.regex)("integer", /^-?[0-9]*$/); + e.default = i; + }, + 1495: function (t, e, n) { + var r = n("86cc"), + i = n("cb7c"), + o = n("0d58"); + t.exports = n("9e1e") + ? Object.defineProperties + : function (t, e) { + i(t); + var n, + a = o(e), + s = a.length, + u = 0; + while (s > u) r.f(t, (n = a[u++]), e[n]); + return t; + }; + }, + 1938: function (t, e, n) { + var r = n("d13f"); + r(r.S, "Array", { isArray: n("b5aa") }); + }, + "196c": function (t, e) { + t.exports = function (t, e, n) { + var r = void 0 === n; + switch (e.length) { + case 0: + return r ? t() : t.call(n); + case 1: + return r ? t(e[0]) : t.call(n, e[0]); + case 2: + return r ? t(e[0], e[1]) : t.call(n, e[0], e[1]); + case 3: + return r + ? t(e[0], e[1], e[2]) + : t.call(n, e[0], e[1], e[2]); + case 4: + return r + ? t(e[0], e[1], e[2], e[3]) + : t.call(n, e[0], e[1], e[2], e[3]); + } + return t.apply(n, e); + }; + }, + 1991: function (t, e, n) { + var r, + i, + o, + a = n("9b43"), + s = n("31f4"), + u = n("fab2"), + c = n("230e"), + f = n("7726"), + l = f.process, + d = f.setImmediate, + p = f.clearImmediate, + h = f.MessageChannel, + v = f.Dispatch, + y = 0, + m = {}, + g = "onreadystatechange", + b = function () { + var t = +this; + if (m.hasOwnProperty(t)) { + var e = m[t]; + delete m[t], e(); + } + }, + _ = function (t) { + b.call(t.data); + }; + (d && p) || + ((d = function (t) { + var e = [], + n = 1; + while (arguments.length > n) e.push(arguments[n++]); + return ( + (m[++y] = function () { + s("function" == typeof t ? t : Function(t), e); + }), + r(y), + y + ); + }), + (p = function (t) { + delete m[t]; + }), + "process" == n("2d95")(l) + ? (r = function (t) { + l.nextTick(a(b, t, 1)); + }) + : v && v.now + ? (r = function (t) { + v.now(a(b, t, 1)); + }) + : h + ? ((i = new h()), + (o = i.port2), + (i.port1.onmessage = _), + (r = a(o.postMessage, o, 1))) + : f.addEventListener && + "function" == typeof postMessage && + !f.importScripts + ? ((r = function (t) { + f.postMessage(t + "", "*"); + }), + f.addEventListener("message", _, !1)) + : (r = + g in c("script") + ? function (t) { + u.appendChild(c("script"))[g] = + function () { + u.removeChild(this), b.call(t); + }; + } + : function (t) { + setTimeout(a(b, t, 1), 0); + })), + (t.exports = { set: d, clear: p }); + }, + "1b55": function (t, e, n) { + var r = n("7772")("wks"), + i = n("7b00"), + o = n("da3c").Symbol, + a = "function" == typeof o, + s = (t.exports = function (t) { + return ( + r[t] || + (r[t] = (a && o[t]) || (a ? o : i)("Symbol." + t)) + ); + }); + s.store = r; + }, + "1b8f": function (t, e, n) { + var r = n("a812"), + i = Math.max, + o = Math.min; + t.exports = function (t, e) { + return (t = r(t)), t < 0 ? i(t + e, 0) : o(t, e); + }; + }, + "1be4": function (t, e, n) { + "use strict"; + var r = n("da3c"), + i = n("a7d3"), + o = n("3adc"), + a = n("7d95"), + s = n("1b55")("species"); + t.exports = function (t) { + var e = "function" == typeof i[t] ? i[t] : r[t]; + a && + e && + !e[s] && + o.f(e, s, { + configurable: !0, + get: function () { + return this; + }, + }); + }; + }, + "1dce": function (t, e, n) { + "use strict"; + Object.defineProperty(e, "__esModule", { value: !0 }), + (e.Vuelidate = I), + Object.defineProperty(e, "withParams", { + enumerable: !0, + get: function () { + return i.withParams; + }, + }), + (e.default = e.validationMixin = void 0); + var r = n("fbf4"), + i = n("0234"); + function o(t) { + return u(t) || s(t) || a(); + } + function a() { + throw new TypeError( + "Invalid attempt to spread non-iterable instance" + ); + } + function s(t) { + if ( + Symbol.iterator in Object(t) || + "[object Arguments]" === Object.prototype.toString.call(t) + ) + return Array.from(t); + } + function u(t) { + if (Array.isArray(t)) { + for (var e = 0, n = new Array(t.length); e < t.length; e++) + n[e] = t[e]; + return n; + } + } + function c(t) { + for (var e = 1; e < arguments.length; e++) { + var n = null != arguments[e] ? arguments[e] : {}, + r = Object.keys(n); + "function" === typeof Object.getOwnPropertySymbols && + (r = r.concat( + Object.getOwnPropertySymbols(n).filter(function ( + t + ) { + return Object.getOwnPropertyDescriptor(n, t) + .enumerable; + }) + )), + r.forEach(function (e) { + f(t, e, n[e]); + }); + } + return t; + } + function f(t, e, n) { + return ( + e in t + ? Object.defineProperty(t, e, { + value: n, + enumerable: !0, + configurable: !0, + writable: !0, + }) + : (t[e] = n), + t + ); + } + function l(t) { + return ( + (l = + "function" === typeof Symbol && + "symbol" === typeof Symbol.iterator + ? function (t) { + return typeof t; + } + : function (t) { + return t && + "function" === typeof Symbol && + t.constructor === Symbol && + t !== Symbol.prototype + ? "symbol" + : typeof t; + }), + l(t) + ); + } + var d = function () { + return null; + }, + p = function (t, e, n) { + return t.reduce(function (t, r) { + return (t[n ? n(r) : r] = e(r)), t; + }, {}); + }; + function h(t) { + return "function" === typeof t; + } + function v(t) { + return null !== t && ("object" === l(t) || h(t)); + } + function y(t) { + return v(t) && h(t.then); + } + var m = function (t, e, n, r) { + if ("function" === typeof n) return n.call(t, e, r); + n = Array.isArray(n) ? n : n.split("."); + for (var i = 0; i < n.length; i++) { + if (!e || "object" !== l(e)) return r; + e = e[n[i]]; + } + return "undefined" === typeof e ? r : e; + }, + g = "__isVuelidateAsyncVm"; + function b(t, e) { + var n = new t({ data: { p: !0, v: !1 } }); + return ( + e.then( + function (t) { + (n.p = !1), (n.v = t); + }, + function (t) { + throw ((n.p = !1), (n.v = !1), t); + } + ), + (n[g] = !0), + n + ); + } + var _ = { + $invalid: function () { + var t = this, + e = this.proxy; + return ( + this.nestedKeys.some(function (e) { + return t.refProxy(e).$invalid; + }) || + this.ruleKeys.some(function (t) { + return !e[t]; + }) + ); + }, + $dirty: function () { + var t = this; + return ( + !!this.dirty || + (0 !== this.nestedKeys.length && + this.nestedKeys.every(function (e) { + return t.refProxy(e).$dirty; + })) + ); + }, + $anyDirty: function () { + var t = this; + return ( + !!this.dirty || + (0 !== this.nestedKeys.length && + this.nestedKeys.some(function (e) { + return t.refProxy(e).$anyDirty; + })) + ); + }, + $error: function () { + return this.$dirty && !this.$pending && this.$invalid; + }, + $anyError: function () { + return this.$anyDirty && !this.$pending && this.$invalid; + }, + $pending: function () { + var t = this; + return ( + this.ruleKeys.some(function (e) { + return t.getRef(e).$pending; + }) || + this.nestedKeys.some(function (e) { + return t.refProxy(e).$pending; + }) + ); + }, + $params: function () { + var t = this, + e = this.validations; + return c( + {}, + p(this.nestedKeys, function (t) { + return (e[t] && e[t].$params) || null; + }), + p(this.ruleKeys, function (e) { + return t.getRef(e).$params; + }) + ); + }, + }; + function w(t) { + this.dirty = t; + var e = this.proxy, + n = t ? "$touch" : "$reset"; + this.nestedKeys.forEach(function (t) { + e[t][n](); + }); + } + var x = { + $touch: function () { + w.call(this, !0); + }, + $reset: function () { + w.call(this, !1); + }, + $flattenParams: function () { + var t = this.proxy, + e = []; + for (var n in this.$params) + if (this.isNested(n)) { + for ( + var r = t[n].$flattenParams(), i = 0; + i < r.length; + i++ + ) + r[i].path.unshift(n); + e = e.concat(r); + } else + e.push({ + path: [], + name: n, + params: this.$params[n], + }); + return e; + }, + }, + k = Object.keys(_), + S = Object.keys(x), + E = null, + O = function (t) { + if (E) return E; + var e = t.extend({ + computed: { + refs: function () { + var t = this._vval; + (this._vval = this.children), + (0, r.patchChildren)(t, this._vval); + var e = {}; + return ( + this._vval.forEach(function (t) { + e[t.key] = t.vm; + }), + e + ); + }, + }, + beforeCreate: function () { + this._vval = null; + }, + beforeDestroy: function () { + this._vval && + ((0, r.patchChildren)(this._vval), + (this._vval = null)); + }, + methods: { + getModel: function () { + return this.lazyModel + ? this.lazyModel(this.prop) + : this.model; + }, + getModelKey: function (t) { + var e = this.getModel(); + if (e) return e[t]; + }, + hasIter: function () { + return !1; + }, + }, + }), + n = e.extend({ + data: function () { + return { + rule: null, + lazyModel: null, + model: null, + lazyParentModel: null, + rootModel: null, + }; + }, + methods: { + runRule: function (e) { + var n = this.getModel(); + (0, i.pushParams)(); + var r = this.rule.call( + this.rootModel, + n, + e + ), + o = y(r) ? b(t, r) : r, + a = (0, i.popParams)(), + s = + a && a.$sub + ? a.$sub.length > 1 + ? a + : a.$sub[0] + : null; + return { output: o, params: s }; + }, + }, + computed: { + run: function () { + var t = this, + e = this.lazyParentModel(), + n = Array.isArray(e) && e.__ob__; + if (n) { + var r = e.__ob__.dep; + r.depend(); + var i = r.constructor.target; + if (!this._indirectWatcher) { + var o = i.constructor; + this._indirectWatcher = new o( + this, + function () { + return t.runRule(e); + }, + null, + { lazy: !0 } + ); + } + var a = this.getModel(); + if ( + !this._indirectWatcher.dirty && + this._lastModel === a + ) + return ( + this._indirectWatcher.depend(), + i.value + ); + (this._lastModel = a), + this._indirectWatcher.evaluate(), + this._indirectWatcher.depend(); + } else + this._indirectWatcher && + (this._indirectWatcher.teardown(), + (this._indirectWatcher = null)); + return this._indirectWatcher + ? this._indirectWatcher.value + : this.runRule(e); + }, + $params: function () { + return this.run.params; + }, + proxy: function () { + var t = this.run.output; + return t[g] ? !!t.v : !!t; + }, + $pending: function () { + var t = this.run.output; + return !!t[g] && t.p; + }, + }, + destroyed: function () { + this._indirectWatcher && + (this._indirectWatcher.teardown(), + (this._indirectWatcher = null)); + }, + }), + a = e.extend({ + data: function () { + return { + dirty: !1, + validations: null, + lazyModel: null, + model: null, + prop: null, + lazyParentModel: null, + rootModel: null, + }; + }, + methods: c({}, x, { + refProxy: function (t) { + return this.getRef(t).proxy; + }, + getRef: function (t) { + return this.refs[t]; + }, + isNested: function (t) { + return ( + "function" !== + typeof this.validations[t] + ); + }, + }), + computed: c({}, _, { + nestedKeys: function () { + return this.keys.filter(this.isNested); + }, + ruleKeys: function () { + var t = this; + return this.keys.filter(function (e) { + return !t.isNested(e); + }); + }, + keys: function () { + return Object.keys(this.validations).filter( + function (t) { + return "$params" !== t; + } + ); + }, + proxy: function () { + var t = this, + e = p(this.keys, function (e) { + return { + enumerable: !0, + configurable: !0, + get: function () { + return t.refProxy(e); + }, + }; + }), + n = p(k, function (e) { + return { + enumerable: !0, + configurable: !0, + get: function () { + return t[e]; + }, + }; + }), + r = p(S, function (e) { + return { + enumerable: !1, + configurable: !0, + get: function () { + return t[e]; + }, + }; + }), + i = this.hasIter() + ? { + $iter: { + enumerable: !0, + value: Object.defineProperties( + {}, + c({}, e) + ), + }, + } + : {}; + return Object.defineProperties( + {}, + c( + {}, + e, + i, + { + $model: { + enumerable: !0, + get: function () { + var e = + t.lazyParentModel(); + return null != e + ? e[t.prop] + : null; + }, + set: function (e) { + var n = + t.lazyParentModel(); + null != n && + ((n[t.prop] = e), + t.$touch()); + }, + }, + }, + n, + r + ) + ); + }, + children: function () { + var t = this; + return o( + this.nestedKeys.map(function (e) { + return f(t, e); + }) + ) + .concat( + o( + this.ruleKeys.map(function (e) { + return l(t, e); + }) + ) + ) + .filter(Boolean); + }, + }), + }), + s = a.extend({ + methods: { + isNested: function (t) { + return ( + "undefined" !== + typeof this.validations[t]() + ); + }, + getRef: function (t) { + var e = this; + return { + get proxy() { + return e.validations[t]() || !1; + }, + }; + }, + }, + }), + u = a.extend({ + computed: { + keys: function () { + var t = this.getModel(); + return v(t) ? Object.keys(t) : []; + }, + tracker: function () { + var t = this, + e = this.validations.$trackBy; + return e + ? function (n) { + return "".concat( + m( + t.rootModel, + t.getModelKey(n), + e + ) + ); + } + : function (t) { + return "".concat(t); + }; + }, + getModelLazy: function () { + var t = this; + return function () { + return t.getModel(); + }; + }, + children: function () { + var t = this, + e = this.validations, + n = this.getModel(), + i = c({}, e); + delete i["$trackBy"]; + var o = {}; + return this.keys + .map(function (e) { + var s = t.tracker(e); + return o.hasOwnProperty(s) + ? null + : ((o[s] = !0), + (0, r.h)(a, s, { + validations: i, + prop: e, + lazyParentModel: + t.getModelLazy, + model: n[e], + rootModel: t.rootModel, + })); + }) + .filter(Boolean); + }, + }, + methods: { + isNested: function () { + return !0; + }, + getRef: function (t) { + return this.refs[this.tracker(t)]; + }, + hasIter: function () { + return !0; + }, + }, + }), + f = function (t, e) { + if ("$each" === e) + return (0, r.h)(u, e, { + validations: t.validations[e], + lazyParentModel: t.lazyParentModel, + prop: e, + lazyModel: t.getModel, + rootModel: t.rootModel, + }); + var n = t.validations[e]; + if (Array.isArray(n)) { + var i = t.rootModel, + o = p( + n, + function (t) { + return function () { + return m(i, i.$v, t); + }; + }, + function (t) { + return Array.isArray(t) + ? t.join(".") + : t; + } + ); + return (0, r.h)(s, e, { + validations: o, + lazyParentModel: d, + prop: e, + lazyModel: d, + rootModel: i, + }); + } + return (0, r.h)(a, e, { + validations: n, + lazyParentModel: t.getModel, + prop: e, + lazyModel: t.getModelKey, + rootModel: t.rootModel, + }); + }, + l = function (t, e) { + return (0, r.h)(n, e, { + rule: t.validations[e], + lazyParentModel: t.lazyParentModel, + lazyModel: t.getModel, + rootModel: t.rootModel, + }); + }; + return (E = { VBase: e, Validation: a }), E; + }, + T = null; + function A(t) { + if (T) return T; + var e = t.constructor; + while (e.super) e = e.super; + return (T = e), e; + } + var C = function (t, e) { + var n = A(t), + i = O(n), + o = i.Validation, + a = i.VBase, + s = new a({ + computed: { + children: function () { + var n = + "function" === typeof e ? e.call(t) : e; + return [ + (0, r.h)(o, "$v", { + validations: n, + lazyParentModel: d, + prop: "$v", + model: t, + rootModel: t, + }), + ]; + }, + }, + }); + return s; + }, + j = { + data: function () { + var t = this.$options.validations; + return t && (this._vuelidate = C(this, t)), {}; + }, + beforeCreate: function () { + var t = this.$options, + e = t.validations; + e && + (t.computed || (t.computed = {}), + t.computed.$v || + (t.computed.$v = function () { + return this._vuelidate + ? this._vuelidate.refs.$v.proxy + : null; + })); + }, + beforeDestroy: function () { + this._vuelidate && + (this._vuelidate.$destroy(), + (this._vuelidate = null)); + }, + }; + function I(t) { + t.mixin(j); + } + e.validationMixin = j; + var M = I; + e.default = M; + }, + "1ea0": function (t, e, n) { + "use strict"; + var r = n("f2fe"), + i = n("6f8a"), + o = n("196c"), + a = [].slice, + s = {}, + u = function (t, e, n) { + if (!(e in s)) { + for (var r = [], i = 0; i < e; i++) + r[i] = "a[" + i + "]"; + s[e] = Function( + "F,a", + "return new F(" + r.join(",") + ")" + ); + } + return s[e](t, n); + }; + t.exports = + Function.bind || + function (t) { + var e = r(this), + n = a.call(arguments, 1), + s = function () { + var r = n.concat(a.call(arguments)); + return this instanceof s + ? u(e, r.length, r) + : o(e, r, t); + }; + return i(e.prototype) && (s.prototype = e.prototype), s; + }; + }, + "1fa8": function (t, e, n) { + var r = n("cb7c"); + t.exports = function (t, e, n, i) { + try { + return i ? e(r(n)[0], n[1]) : e(n); + } catch (a) { + var o = t["return"]; + throw (void 0 !== o && r(o.call(t)), a); + } + }; + }, + "1fca": function (t, e, n) { + var r = n("6f8a"); + t.exports = function (t, e) { + if (!r(t) || t._t !== e) + throw TypeError( + "Incompatible receiver, " + e + " required!" + ); + return t; + }; + }, + "214f": function (t, e, n) { + "use strict"; + n("b0c5"); + var r = n("2aba"), + i = n("32e9"), + o = n("79e5"), + a = n("be13"), + s = n("2b4c"), + u = n("520a"), + c = s("species"), + f = !o(function () { + var t = /./; + return ( + (t.exec = function () { + var t = []; + return (t.groups = { a: "7" }), t; + }), + "7" !== "".replace(t, "$") + ); + }), + l = (function () { + var t = /(?:)/, + e = t.exec; + t.exec = function () { + return e.apply(this, arguments); + }; + var n = "ab".split(t); + return 2 === n.length && "a" === n[0] && "b" === n[1]; + })(); + t.exports = function (t, e, n) { + var d = s(t), + p = !o(function () { + var e = {}; + return ( + (e[d] = function () { + return 7; + }), + 7 != ""[t](e) + ); + }), + h = p + ? !o(function () { + var e = !1, + n = /a/; + return ( + (n.exec = function () { + return (e = !0), null; + }), + "split" === t && + ((n.constructor = {}), + (n.constructor[c] = function () { + return n; + })), + n[d](""), + !e + ); + }) + : void 0; + if ( + !p || + !h || + ("replace" === t && !f) || + ("split" === t && !l) + ) { + var v = /./[d], + y = n(a, d, ""[t], function (t, e, n, r, i) { + return e.exec === u + ? p && !i + ? { done: !0, value: v.call(e, n, r) } + : { done: !0, value: t.call(n, e, r) } + : { done: !1 }; + }), + m = y[0], + g = y[1]; + r(String.prototype, t, m), + i( + RegExp.prototype, + d, + 2 == e + ? function (t, e) { + return g.call(t, this, e); + } + : function (t) { + return g.call(t, this); + } + ); + } + }; + }, + "230e": function (t, e, n) { + var r = n("d3f4"), + i = n("7726").document, + o = r(i) && r(i.createElement); + t.exports = function (t) { + return o ? i.createElement(t) : {}; + }; + }, + 2312: function (t, e, n) { + t.exports = n("8ce0"); + }, + 2366: function (t, e) { + for (var n = [], r = 0; r < 256; ++r) + n[r] = (r + 256).toString(16).substr(1); + function i(t, e) { + var r = e || 0, + i = n; + return [ + i[t[r++]], + i[t[r++]], + i[t[r++]], + i[t[r++]], + "-", + i[t[r++]], + i[t[r++]], + "-", + i[t[r++]], + i[t[r++]], + "-", + i[t[r++]], + i[t[r++]], + "-", + i[t[r++]], + i[t[r++]], + i[t[r++]], + i[t[r++]], + i[t[r++]], + i[t[r++]], + ].join(""); + } + t.exports = i; + }, + 2397: function (t, e, n) { + var r = n("5ca1"), + i = n("2aeb"), + o = n("d8e8"), + a = n("cb7c"), + s = n("d3f4"), + u = n("79e5"), + c = n("f0c1"), + f = (n("7726").Reflect || {}).construct, + l = u(function () { + function t() {} + return !(f(function () {}, [], t) instanceof t); + }), + d = !u(function () { + f(function () {}); + }); + r(r.S + r.F * (l || d), "Reflect", { + construct: function (t, e) { + o(t), a(e); + var n = arguments.length < 3 ? t : o(arguments[2]); + if (d && !l) return f(t, e, n); + if (t == n) { + switch (e.length) { + case 0: + return new t(); + case 1: + return new t(e[0]); + case 2: + return new t(e[0], e[1]); + case 3: + return new t(e[0], e[1], e[2]); + case 4: + return new t(e[0], e[1], e[2], e[3]); + } + var r = [null]; + return r.push.apply(r, e), new (c.apply(t, r))(); + } + var u = n.prototype, + p = i(s(u) ? u : Object.prototype), + h = Function.apply.call(t, p, e); + return s(h) ? h : p; + }, + }); + }, + "23c6": function (t, e, n) { + var r = n("2d95"), + i = n("2b4c")("toStringTag"), + o = + "Arguments" == + r( + (function () { + return arguments; + })() + ), + a = function (t, e) { + try { + return t[e]; + } catch (n) {} + }; + t.exports = function (t) { + var e, n, s; + return void 0 === t + ? "Undefined" + : null === t + ? "Null" + : "string" == typeof (n = a((e = Object(t)), i)) + ? n + : o + ? r(e) + : "Object" == (s = r(e)) && "function" == typeof e.callee + ? "Arguments" + : s; + }; + }, + 2418: function (t, e, n) { + var r = n("6a9b"), + i = n("a5ab"), + o = n("1b8f"); + t.exports = function (t) { + return function (e, n, a) { + var s, + u = r(e), + c = i(u.length), + f = o(a, c); + if (t && n != n) { + while (c > f) if (((s = u[f++]), s != s)) return !0; + } else + for (; c > f; f++) + if ((t || f in u) && u[f] === n) return t || f || 0; + return !t && -1; + }; + }; + }, + "245b": function (t, e) { + t.exports = function (t, e) { + return { value: e, done: !!t }; + }; + }, + 2498: function (t, e, n) { + var r = n("d13f"), + i = n("7108"), + o = n("f2fe"), + a = n("0f89"), + s = n("6f8a"), + u = n("d782"), + c = n("1ea0"), + f = (n("da3c").Reflect || {}).construct, + l = u(function () { + function t() {} + return !(f(function () {}, [], t) instanceof t); + }), + d = !u(function () { + f(function () {}); + }); + r(r.S + r.F * (l || d), "Reflect", { + construct: function (t, e) { + o(t), a(e); + var n = arguments.length < 3 ? t : o(arguments[2]); + if (d && !l) return f(t, e, n); + if (t == n) { + switch (e.length) { + case 0: + return new t(); + case 1: + return new t(e[0]); + case 2: + return new t(e[0], e[1]); + case 3: + return new t(e[0], e[1], e[2]); + case 4: + return new t(e[0], e[1], e[2], e[3]); + } + var r = [null]; + return r.push.apply(r, e), new (c.apply(t, r))(); + } + var u = n.prototype, + p = i(s(u) ? u : Object.prototype), + h = Function.apply.call(t, p, e); + return s(h) ? h : p; + }, + }); + }, + "261e": function (t, e, n) { + n("012f")("Map"); + }, + 2621: function (t, e) { + e.f = Object.getOwnPropertySymbols; + }, + 2695: function (t, e, n) { + var r = n("43c8"), + i = n("6a9b"), + o = n("2418")(!1), + a = n("5d8f")("IE_PROTO"); + t.exports = function (t, e) { + var n, + s = i(t), + u = 0, + c = []; + for (n in s) n != a && r(s, n) && c.push(n); + while (e.length > u) + r(s, (n = e[u++])) && (~o(c, n) || c.push(n)); + return c; + }; + }, + "27ee": function (t, e, n) { + var r = n("23c6"), + i = n("2b4c")("iterator"), + o = n("84f2"); + t.exports = n("8378").getIteratorMethod = function (t) { + if (void 0 != t) return t[i] || t["@@iterator"] || o[r(t)]; + }; + }, + 2877: function (t, e, n) { + "use strict"; + function r(t, e, n, r, i, o, a, s) { + var u, + c = "function" === typeof t ? t.options : t; + if ( + (e && + ((c.render = e), + (c.staticRenderFns = n), + (c._compiled = !0)), + r && (c.functional = !0), + o && (c._scopeId = "data-v-" + o), + a + ? ((u = function (t) { + (t = + t || + (this.$vnode && this.$vnode.ssrContext) || + (this.parent && + this.parent.$vnode && + this.parent.$vnode.ssrContext)), + t || + "undefined" === + typeof __VUE_SSR_CONTEXT__ || + (t = __VUE_SSR_CONTEXT__), + i && i.call(this, t), + t && + t._registeredComponents && + t._registeredComponents.add(a); + }), + (c._ssrRegister = u)) + : i && + (u = s + ? function () { + i.call( + this, + (c.functional ? this.parent : this) + .$root.$options.shadowRoot + ); + } + : i), + u) + ) + if (c.functional) { + c._injectStyles = u; + var f = c.render; + c.render = function (t, e) { + return u.call(e), f(t, e); + }; + } else { + var l = c.beforeCreate; + c.beforeCreate = l ? [].concat(l, u) : [u]; + } + return { exports: t, options: c }; + } + n.d(e, "a", function () { + return r; + }); + }, + "28a5": function (t, e, n) { + "use strict"; + var r = n("aae3"), + i = n("cb7c"), + o = n("ebd6"), + a = n("0390"), + s = n("9def"), + u = n("5f1b"), + c = n("520a"), + f = n("79e5"), + l = Math.min, + d = [].push, + p = "split", + h = "length", + v = "lastIndex", + y = 4294967295, + m = !f(function () { + RegExp(y, "y"); + }); + n("214f")("split", 2, function (t, e, n, f) { + var g; + return ( + (g = + "c" == "abbc"[p](/(b)*/)[1] || + 4 != "test"[p](/(?:)/, -1)[h] || + 2 != "ab"[p](/(?:ab)*/)[h] || + 4 != "."[p](/(.?)(.?)/)[h] || + "."[p](/()()/)[h] > 1 || + ""[p](/.?/)[h] + ? function (t, e) { + var i = String(this); + if (void 0 === t && 0 === e) return []; + if (!r(t)) return n.call(i, t, e); + var o, + a, + s, + u = [], + f = + (t.ignoreCase ? "i" : "") + + (t.multiline ? "m" : "") + + (t.unicode ? "u" : "") + + (t.sticky ? "y" : ""), + l = 0, + p = void 0 === e ? y : e >>> 0, + m = new RegExp(t.source, f + "g"); + while ((o = c.call(m, i))) { + if ( + ((a = m[v]), + a > l && + (u.push(i.slice(l, o.index)), + o[h] > 1 && + o.index < i[h] && + d.apply(u, o.slice(1)), + (s = o[0][h]), + (l = a), + u[h] >= p)) + ) + break; + m[v] === o.index && m[v]++; + } + return ( + l === i[h] + ? (!s && m.test("")) || u.push("") + : u.push(i.slice(l)), + u[h] > p ? u.slice(0, p) : u + ); + } + : "0"[p](void 0, 0)[h] + ? function (t, e) { + return void 0 === t && 0 === e + ? [] + : n.call(this, t, e); + } + : n), + [ + function (n, r) { + var i = t(this), + o = void 0 == n ? void 0 : n[e]; + return void 0 !== o + ? o.call(n, i, r) + : g.call(String(i), n, r); + }, + function (t, e) { + var r = f(g, t, this, e, g !== n); + if (r.done) return r.value; + var c = i(t), + d = String(this), + p = o(c, RegExp), + h = c.unicode, + v = + (c.ignoreCase ? "i" : "") + + (c.multiline ? "m" : "") + + (c.unicode ? "u" : "") + + (m ? "y" : "g"), + b = new p(m ? c : "^(?:" + c.source + ")", v), + _ = void 0 === e ? y : e >>> 0; + if (0 === _) return []; + if (0 === d.length) + return null === u(b, d) ? [d] : []; + var w = 0, + x = 0, + k = []; + while (x < d.length) { + b.lastIndex = m ? x : 0; + var S, + E = u(b, m ? d : d.slice(x)); + if ( + null === E || + (S = l( + s(b.lastIndex + (m ? 0 : x)), + d.length + )) === w + ) + x = a(d, x, h); + else { + if ((k.push(d.slice(w, x)), k.length === _)) + return k; + for (var O = 1; O <= E.length - 1; O++) + if ((k.push(E[O]), k.length === _)) + return k; + x = w = S; + } + } + return k.push(d.slice(w)), k; + }, + ] + ); + }); + }, + "2a12": function (t, e, n) { + "use strict"; + Object.defineProperty(e, "__esModule", { value: !0 }), + (e.default = void 0); + var r = n("78ef"), + i = function (t) { + return (0, r.withParams)( + { type: "maxLength", max: t }, + function (e) { + return !(0, r.req)(e) || (0, r.len)(e) <= t; + } + ); + }; + e.default = i; + }, + "2a4e": function (t, e, n) { + var r = n("a812"), + i = n("e5fa"); + t.exports = function (t) { + return function (e, n) { + var o, + a, + s = String(i(e)), + u = r(n), + c = s.length; + return u < 0 || u >= c + ? t + ? "" + : void 0 + : ((o = s.charCodeAt(u)), + o < 55296 || + o > 56319 || + u + 1 === c || + (a = s.charCodeAt(u + 1)) < 56320 || + a > 57343 + ? t + ? s.charAt(u) + : o + : t + ? s.slice(u, u + 2) + : a - 56320 + ((o - 55296) << 10) + 65536); + }; + }; + }, + "2aba": function (t, e, n) { + var r = n("7726"), + i = n("32e9"), + o = n("69a8"), + a = n("ca5a")("src"), + s = n("fa5b"), + u = "toString", + c = ("" + s).split(u); + (n("8378").inspectSource = function (t) { + return s.call(t); + }), + (t.exports = function (t, e, n, s) { + var u = "function" == typeof n; + u && (o(n, "name") || i(n, "name", e)), + t[e] !== n && + (u && + (o(n, a) || + i( + n, + a, + t[e] ? "" + t[e] : c.join(String(e)) + )), + t === r + ? (t[e] = n) + : s + ? t[e] + ? (t[e] = n) + : i(t, e, n) + : (delete t[e], i(t, e, n))); + })(Function.prototype, u, function () { + return ( + ("function" == typeof this && this[a]) || s.call(this) + ); + }); + }, + "2aeb": function (t, e, n) { + var r = n("cb7c"), + i = n("1495"), + o = n("e11e"), + a = n("613b")("IE_PROTO"), + s = function () {}, + u = "prototype", + c = function () { + var t, + e = n("230e")("iframe"), + r = o.length, + i = "<", + a = ">"; + (e.style.display = "none"), + n("fab2").appendChild(e), + (e.src = "javascript:"), + (t = e.contentWindow.document), + t.open(), + t.write( + i + + "script" + + a + + "document.F=Object" + + i + + "/script" + + a + ), + t.close(), + (c = t.F); + while (r--) delete c[u][o[r]]; + return c(); + }; + t.exports = + Object.create || + function (t, e) { + var n; + return ( + null !== t + ? ((s[u] = r(t)), + (n = new s()), + (s[u] = null), + (n[a] = t)) + : (n = c()), + void 0 === e ? n : i(n, e) + ); + }; + }, + "2b0e": function (t, e, n) { + "use strict"; + (function (t) { + /*! + * Vue.js v2.6.14 + * (c) 2014-2021 Evan You + * Released under the MIT License. + */ + var n = Object.freeze({}); + function r(t) { + return void 0 === t || null === t; + } + function i(t) { + return void 0 !== t && null !== t; + } + function o(t) { + return !0 === t; + } + function a(t) { + return !1 === t; + } + function s(t) { + return ( + "string" === typeof t || + "number" === typeof t || + "symbol" === typeof t || + "boolean" === typeof t + ); + } + function u(t) { + return null !== t && "object" === typeof t; + } + var c = Object.prototype.toString; + function f(t) { + return "[object Object]" === c.call(t); + } + function l(t) { + return "[object RegExp]" === c.call(t); + } + function d(t) { + var e = parseFloat(String(t)); + return e >= 0 && Math.floor(e) === e && isFinite(t); + } + function p(t) { + return ( + i(t) && + "function" === typeof t.then && + "function" === typeof t.catch + ); + } + function h(t) { + return null == t + ? "" + : Array.isArray(t) || (f(t) && t.toString === c) + ? JSON.stringify(t, null, 2) + : String(t); + } + function v(t) { + var e = parseFloat(t); + return isNaN(e) ? t : e; + } + function y(t, e) { + for ( + var n = Object.create(null), r = t.split(","), i = 0; + i < r.length; + i++ + ) + n[r[i]] = !0; + return e + ? function (t) { + return n[t.toLowerCase()]; + } + : function (t) { + return n[t]; + }; + } + y("slot,component", !0); + var m = y("key,ref,slot,slot-scope,is"); + function g(t, e) { + if (t.length) { + var n = t.indexOf(e); + if (n > -1) return t.splice(n, 1); + } + } + var b = Object.prototype.hasOwnProperty; + function _(t, e) { + return b.call(t, e); + } + function w(t) { + var e = Object.create(null); + return function (n) { + var r = e[n]; + return r || (e[n] = t(n)); + }; + } + var x = /-(\w)/g, + k = w(function (t) { + return t.replace(x, function (t, e) { + return e ? e.toUpperCase() : ""; + }); + }), + S = w(function (t) { + return t.charAt(0).toUpperCase() + t.slice(1); + }), + E = /\B([A-Z])/g, + O = w(function (t) { + return t.replace(E, "-$1").toLowerCase(); + }); + function T(t, e) { + function n(n) { + var r = arguments.length; + return r + ? r > 1 + ? t.apply(e, arguments) + : t.call(e, n) + : t.call(e); + } + return (n._length = t.length), n; + } + function A(t, e) { + return t.bind(e); + } + var C = Function.prototype.bind ? A : T; + function j(t, e) { + e = e || 0; + var n = t.length - e, + r = new Array(n); + while (n--) r[n] = t[n + e]; + return r; + } + function I(t, e) { + for (var n in e) t[n] = e[n]; + return t; + } + function M(t) { + for (var e = {}, n = 0; n < t.length; n++) + t[n] && I(e, t[n]); + return e; + } + function L(t, e, n) {} + var P = function (t, e, n) { + return !1; + }, + N = function (t) { + return t; + }; + function R(t, e) { + if (t === e) return !0; + var n = u(t), + r = u(e); + if (!n || !r) return !n && !r && String(t) === String(e); + try { + var i = Array.isArray(t), + o = Array.isArray(e); + if (i && o) + return ( + t.length === e.length && + t.every(function (t, n) { + return R(t, e[n]); + }) + ); + if (t instanceof Date && e instanceof Date) + return t.getTime() === e.getTime(); + if (i || o) return !1; + var a = Object.keys(t), + s = Object.keys(e); + return ( + a.length === s.length && + a.every(function (n) { + return R(t[n], e[n]); + }) + ); + } catch (c) { + return !1; + } + } + function $(t, e) { + for (var n = 0; n < t.length; n++) if (R(t[n], e)) return n; + return -1; + } + function D(t) { + var e = !1; + return function () { + e || ((e = !0), t.apply(this, arguments)); + }; + } + var F = "data-server-rendered", + z = ["component", "directive", "filter"], + U = [ + "beforeCreate", + "created", + "beforeMount", + "mounted", + "beforeUpdate", + "updated", + "beforeDestroy", + "destroyed", + "activated", + "deactivated", + "errorCaptured", + "serverPrefetch", + ], + B = { + optionMergeStrategies: Object.create(null), + silent: !1, + productionTip: !1, + devtools: !1, + performance: !1, + errorHandler: null, + warnHandler: null, + ignoredElements: [], + keyCodes: Object.create(null), + isReservedTag: P, + isReservedAttr: P, + isUnknownElement: P, + getTagNamespace: L, + parsePlatformTagName: N, + mustUseProp: P, + async: !0, + _lifecycleHooks: U, + }, + V = + /a-zA-Z\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD/; + function H(t) { + var e = (t + "").charCodeAt(0); + return 36 === e || 95 === e; + } + function q(t, e, n, r) { + Object.defineProperty(t, e, { + value: n, + enumerable: !!r, + writable: !0, + configurable: !0, + }); + } + var W = new RegExp("[^" + V.source + ".$_\\d]"); + function G(t) { + if (!W.test(t)) { + var e = t.split("."); + return function (t) { + for (var n = 0; n < e.length; n++) { + if (!t) return; + t = t[e[n]]; + } + return t; + }; + } + } + var Z, + J = "__proto__" in {}, + K = "undefined" !== typeof window, + X = + "undefined" !== typeof WXEnvironment && + !!WXEnvironment.platform, + Y = X && WXEnvironment.platform.toLowerCase(), + Q = K && window.navigator.userAgent.toLowerCase(), + tt = Q && /msie|trident/.test(Q), + et = Q && Q.indexOf("msie 9.0") > 0, + nt = Q && Q.indexOf("edge/") > 0, + rt = + (Q && Q.indexOf("android"), + (Q && /iphone|ipad|ipod|ios/.test(Q)) || "ios" === Y), + it = + (Q && /chrome\/\d+/.test(Q), + Q && /phantomjs/.test(Q), + Q && Q.match(/firefox\/(\d+)/)), + ot = {}.watch, + at = !1; + if (K) + try { + var st = {}; + Object.defineProperty(st, "passive", { + get: function () { + at = !0; + }, + }), + window.addEventListener("test-passive", null, st); + } catch (Sa) {} + var ut = function () { + return ( + void 0 === Z && + (Z = + !K && + !X && + "undefined" !== typeof t && + t["process"] && + "server" === t["process"].env.VUE_ENV), + Z + ); + }, + ct = K && window.__VUE_DEVTOOLS_GLOBAL_HOOK__; + function ft(t) { + return ( + "function" === typeof t && + /native code/.test(t.toString()) + ); + } + var lt, + dt = + "undefined" !== typeof Symbol && + ft(Symbol) && + "undefined" !== typeof Reflect && + ft(Reflect.ownKeys); + lt = + "undefined" !== typeof Set && ft(Set) + ? Set + : (function () { + function t() { + this.set = Object.create(null); + } + return ( + (t.prototype.has = function (t) { + return !0 === this.set[t]; + }), + (t.prototype.add = function (t) { + this.set[t] = !0; + }), + (t.prototype.clear = function () { + this.set = Object.create(null); + }), + t + ); + })(); + var pt = L, + ht = 0, + vt = function () { + (this.id = ht++), (this.subs = []); + }; + (vt.prototype.addSub = function (t) { + this.subs.push(t); + }), + (vt.prototype.removeSub = function (t) { + g(this.subs, t); + }), + (vt.prototype.depend = function () { + vt.target && vt.target.addDep(this); + }), + (vt.prototype.notify = function () { + var t = this.subs.slice(); + for (var e = 0, n = t.length; e < n; e++) t[e].update(); + }), + (vt.target = null); + var yt = []; + function mt(t) { + yt.push(t), (vt.target = t); + } + function gt() { + yt.pop(), (vt.target = yt[yt.length - 1]); + } + var bt = function (t, e, n, r, i, o, a, s) { + (this.tag = t), + (this.data = e), + (this.children = n), + (this.text = r), + (this.elm = i), + (this.ns = void 0), + (this.context = o), + (this.fnContext = void 0), + (this.fnOptions = void 0), + (this.fnScopeId = void 0), + (this.key = e && e.key), + (this.componentOptions = a), + (this.componentInstance = void 0), + (this.parent = void 0), + (this.raw = !1), + (this.isStatic = !1), + (this.isRootInsert = !0), + (this.isComment = !1), + (this.isCloned = !1), + (this.isOnce = !1), + (this.asyncFactory = s), + (this.asyncMeta = void 0), + (this.isAsyncPlaceholder = !1); + }, + _t = { child: { configurable: !0 } }; + (_t.child.get = function () { + return this.componentInstance; + }), + Object.defineProperties(bt.prototype, _t); + var wt = function (t) { + void 0 === t && (t = ""); + var e = new bt(); + return (e.text = t), (e.isComment = !0), e; + }; + function xt(t) { + return new bt(void 0, void 0, void 0, String(t)); + } + function kt(t) { + var e = new bt( + t.tag, + t.data, + t.children && t.children.slice(), + t.text, + t.elm, + t.context, + t.componentOptions, + t.asyncFactory + ); + return ( + (e.ns = t.ns), + (e.isStatic = t.isStatic), + (e.key = t.key), + (e.isComment = t.isComment), + (e.fnContext = t.fnContext), + (e.fnOptions = t.fnOptions), + (e.fnScopeId = t.fnScopeId), + (e.asyncMeta = t.asyncMeta), + (e.isCloned = !0), + e + ); + } + var St = Array.prototype, + Et = Object.create(St), + Ot = [ + "push", + "pop", + "shift", + "unshift", + "splice", + "sort", + "reverse", + ]; + Ot.forEach(function (t) { + var e = St[t]; + q(Et, t, function () { + var n = [], + r = arguments.length; + while (r--) n[r] = arguments[r]; + var i, + o = e.apply(this, n), + a = this.__ob__; + switch (t) { + case "push": + case "unshift": + i = n; + break; + case "splice": + i = n.slice(2); + break; + } + return i && a.observeArray(i), a.dep.notify(), o; + }); + }); + var Tt = Object.getOwnPropertyNames(Et), + At = !0; + function Ct(t) { + At = t; + } + var jt = function (t) { + (this.value = t), + (this.dep = new vt()), + (this.vmCount = 0), + q(t, "__ob__", this), + Array.isArray(t) + ? (J ? It(t, Et) : Mt(t, Et, Tt), + this.observeArray(t)) + : this.walk(t); + }; + function It(t, e) { + t.__proto__ = e; + } + function Mt(t, e, n) { + for (var r = 0, i = n.length; r < i; r++) { + var o = n[r]; + q(t, o, e[o]); + } + } + function Lt(t, e) { + var n; + if (u(t) && !(t instanceof bt)) + return ( + _(t, "__ob__") && t.__ob__ instanceof jt + ? (n = t.__ob__) + : At && + !ut() && + (Array.isArray(t) || f(t)) && + Object.isExtensible(t) && + !t._isVue && + (n = new jt(t)), + e && n && n.vmCount++, + n + ); + } + function Pt(t, e, n, r, i) { + var o = new vt(), + a = Object.getOwnPropertyDescriptor(t, e); + if (!a || !1 !== a.configurable) { + var s = a && a.get, + u = a && a.set; + (s && !u) || 2 !== arguments.length || (n = t[e]); + var c = !i && Lt(n); + Object.defineProperty(t, e, { + enumerable: !0, + configurable: !0, + get: function () { + var e = s ? s.call(t) : n; + return ( + vt.target && + (o.depend(), + c && + (c.dep.depend(), + Array.isArray(e) && $t(e))), + e + ); + }, + set: function (e) { + var r = s ? s.call(t) : n; + e === r || + (e !== e && r !== r) || + (s && !u) || + (u ? u.call(t, e) : (n = e), + (c = !i && Lt(e)), + o.notify()); + }, + }); + } + } + function Nt(t, e, n) { + if (Array.isArray(t) && d(e)) + return ( + (t.length = Math.max(t.length, e)), + t.splice(e, 1, n), + n + ); + if (e in t && !(e in Object.prototype)) + return (t[e] = n), n; + var r = t.__ob__; + return t._isVue || (r && r.vmCount) + ? n + : r + ? (Pt(r.value, e, n), r.dep.notify(), n) + : ((t[e] = n), n); + } + function Rt(t, e) { + if (Array.isArray(t) && d(e)) t.splice(e, 1); + else { + var n = t.__ob__; + t._isVue || + (n && n.vmCount) || + (_(t, e) && (delete t[e], n && n.dep.notify())); + } + } + function $t(t) { + for (var e = void 0, n = 0, r = t.length; n < r; n++) + (e = t[n]), + e && e.__ob__ && e.__ob__.dep.depend(), + Array.isArray(e) && $t(e); + } + (jt.prototype.walk = function (t) { + for (var e = Object.keys(t), n = 0; n < e.length; n++) + Pt(t, e[n]); + }), + (jt.prototype.observeArray = function (t) { + for (var e = 0, n = t.length; e < n; e++) Lt(t[e]); + }); + var Dt = B.optionMergeStrategies; + function Ft(t, e) { + if (!e) return t; + for ( + var n, + r, + i, + o = dt ? Reflect.ownKeys(e) : Object.keys(e), + a = 0; + a < o.length; + a++ + ) + (n = o[a]), + "__ob__" !== n && + ((r = t[n]), + (i = e[n]), + _(t, n) + ? r !== i && f(r) && f(i) && Ft(r, i) + : Nt(t, n, i)); + return t; + } + function zt(t, e, n) { + return n + ? function () { + var r = + "function" === typeof e + ? e.call(n, n) + : e, + i = + "function" === typeof t + ? t.call(n, n) + : t; + return r ? Ft(r, i) : i; + } + : e + ? t + ? function () { + return Ft( + "function" === typeof e + ? e.call(this, this) + : e, + "function" === typeof t + ? t.call(this, this) + : t + ); + } + : e + : t; + } + function Ut(t, e) { + var n = e + ? t + ? t.concat(e) + : Array.isArray(e) + ? e + : [e] + : t; + return n ? Bt(n) : n; + } + function Bt(t) { + for (var e = [], n = 0; n < t.length; n++) + -1 === e.indexOf(t[n]) && e.push(t[n]); + return e; + } + function Vt(t, e, n, r) { + var i = Object.create(t || null); + return e ? I(i, e) : i; + } + (Dt.data = function (t, e, n) { + return n + ? zt(t, e, n) + : e && "function" !== typeof e + ? t + : zt(t, e); + }), + U.forEach(function (t) { + Dt[t] = Ut; + }), + z.forEach(function (t) { + Dt[t + "s"] = Vt; + }), + (Dt.watch = function (t, e, n, r) { + if ( + (t === ot && (t = void 0), + e === ot && (e = void 0), + !e) + ) + return Object.create(t || null); + if (!t) return e; + var i = {}; + for (var o in (I(i, t), e)) { + var a = i[o], + s = e[o]; + a && !Array.isArray(a) && (a = [a]), + (i[o] = a + ? a.concat(s) + : Array.isArray(s) + ? s + : [s]); + } + return i; + }), + (Dt.props = + Dt.methods = + Dt.inject = + Dt.computed = + function (t, e, n, r) { + if (!t) return e; + var i = Object.create(null); + return I(i, t), e && I(i, e), i; + }), + (Dt.provide = zt); + var Ht = function (t, e) { + return void 0 === e ? t : e; + }; + function qt(t, e) { + var n = t.props; + if (n) { + var r, + i, + o, + a = {}; + if (Array.isArray(n)) { + r = n.length; + while (r--) + (i = n[r]), + "string" === typeof i && + ((o = k(i)), (a[o] = { type: null })); + } else if (f(n)) + for (var s in n) + (i = n[s]), + (o = k(s)), + (a[o] = f(i) ? i : { type: i }); + else 0; + t.props = a; + } + } + function Wt(t, e) { + var n = t.inject; + if (n) { + var r = (t.inject = {}); + if (Array.isArray(n)) + for (var i = 0; i < n.length; i++) + r[n[i]] = { from: n[i] }; + else if (f(n)) + for (var o in n) { + var a = n[o]; + r[o] = f(a) ? I({ from: o }, a) : { from: a }; + } + else 0; + } + } + function Gt(t) { + var e = t.directives; + if (e) + for (var n in e) { + var r = e[n]; + "function" === typeof r && + (e[n] = { bind: r, update: r }); + } + } + function Zt(t, e, n) { + if ( + ("function" === typeof e && (e = e.options), + qt(e, n), + Wt(e, n), + Gt(e), + !e._base && + (e.extends && (t = Zt(t, e.extends, n)), e.mixins)) + ) + for (var r = 0, i = e.mixins.length; r < i; r++) + t = Zt(t, e.mixins[r], n); + var o, + a = {}; + for (o in t) s(o); + for (o in e) _(t, o) || s(o); + function s(r) { + var i = Dt[r] || Ht; + a[r] = i(t[r], e[r], n, r); + } + return a; + } + function Jt(t, e, n, r) { + if ("string" === typeof n) { + var i = t[e]; + if (_(i, n)) return i[n]; + var o = k(n); + if (_(i, o)) return i[o]; + var a = S(o); + if (_(i, a)) return i[a]; + var s = i[n] || i[o] || i[a]; + return s; + } + } + function Kt(t, e, n, r) { + var i = e[t], + o = !_(n, t), + a = n[t], + s = ee(Boolean, i.type); + if (s > -1) + if (o && !_(i, "default")) a = !1; + else if ("" === a || a === O(t)) { + var u = ee(String, i.type); + (u < 0 || s < u) && (a = !0); + } + if (void 0 === a) { + a = Xt(r, i, t); + var c = At; + Ct(!0), Lt(a), Ct(c); + } + return a; + } + function Xt(t, e, n) { + if (_(e, "default")) { + var r = e.default; + return t && + t.$options.propsData && + void 0 === t.$options.propsData[n] && + void 0 !== t._props[n] + ? t._props[n] + : "function" === typeof r && + "Function" !== Qt(e.type) + ? r.call(t) + : r; + } + } + var Yt = /^\s*function (\w+)/; + function Qt(t) { + var e = t && t.toString().match(Yt); + return e ? e[1] : ""; + } + function te(t, e) { + return Qt(t) === Qt(e); + } + function ee(t, e) { + if (!Array.isArray(e)) return te(e, t) ? 0 : -1; + for (var n = 0, r = e.length; n < r; n++) + if (te(e[n], t)) return n; + return -1; + } + function ne(t, e, n) { + mt(); + try { + if (e) { + var r = e; + while ((r = r.$parent)) { + var i = r.$options.errorCaptured; + if (i) + for (var o = 0; o < i.length; o++) + try { + var a = + !1 === i[o].call(r, t, e, n); + if (a) return; + } catch (Sa) { + ie(Sa, r, "errorCaptured hook"); + } + } + } + ie(t, e, n); + } finally { + gt(); + } + } + function re(t, e, n, r, i) { + var o; + try { + (o = n ? t.apply(e, n) : t.call(e)), + o && + !o._isVue && + p(o) && + !o._handled && + (o.catch(function (t) { + return ne(t, r, i + " (Promise/async)"); + }), + (o._handled = !0)); + } catch (Sa) { + ne(Sa, r, i); + } + return o; + } + function ie(t, e, n) { + if (B.errorHandler) + try { + return B.errorHandler.call(null, t, e, n); + } catch (Sa) { + Sa !== t && oe(Sa, null, "config.errorHandler"); + } + oe(t, e, n); + } + function oe(t, e, n) { + if ((!K && !X) || "undefined" === typeof console) throw t; + console.error(t); + } + var ae, + se = !1, + ue = [], + ce = !1; + function fe() { + ce = !1; + var t = ue.slice(0); + ue.length = 0; + for (var e = 0; e < t.length; e++) t[e](); + } + if ("undefined" !== typeof Promise && ft(Promise)) { + var le = Promise.resolve(); + (ae = function () { + le.then(fe), rt && setTimeout(L); + }), + (se = !0); + } else if ( + tt || + "undefined" === typeof MutationObserver || + (!ft(MutationObserver) && + "[object MutationObserverConstructor]" !== + MutationObserver.toString()) + ) + ae = + "undefined" !== typeof setImmediate && ft(setImmediate) + ? function () { + setImmediate(fe); + } + : function () { + setTimeout(fe, 0); + }; + else { + var de = 1, + pe = new MutationObserver(fe), + he = document.createTextNode(String(de)); + pe.observe(he, { characterData: !0 }), + (ae = function () { + (de = (de + 1) % 2), (he.data = String(de)); + }), + (se = !0); + } + function ve(t, e) { + var n; + if ( + (ue.push(function () { + if (t) + try { + t.call(e); + } catch (Sa) { + ne(Sa, e, "nextTick"); + } + else n && n(e); + }), + ce || ((ce = !0), ae()), + !t && "undefined" !== typeof Promise) + ) + return new Promise(function (t) { + n = t; + }); + } + var ye = new lt(); + function me(t) { + ge(t, ye), ye.clear(); + } + function ge(t, e) { + var n, + r, + i = Array.isArray(t); + if ( + !( + (!i && !u(t)) || + Object.isFrozen(t) || + t instanceof bt + ) + ) { + if (t.__ob__) { + var o = t.__ob__.dep.id; + if (e.has(o)) return; + e.add(o); + } + if (i) { + n = t.length; + while (n--) ge(t[n], e); + } else { + (r = Object.keys(t)), (n = r.length); + while (n--) ge(t[r[n]], e); + } + } + } + var be = w(function (t) { + var e = "&" === t.charAt(0); + t = e ? t.slice(1) : t; + var n = "~" === t.charAt(0); + t = n ? t.slice(1) : t; + var r = "!" === t.charAt(0); + return ( + (t = r ? t.slice(1) : t), + { name: t, once: n, capture: r, passive: e } + ); + }); + function _e(t, e) { + function n() { + var t = arguments, + r = n.fns; + if (!Array.isArray(r)) + return re(r, null, arguments, e, "v-on handler"); + for (var i = r.slice(), o = 0; o < i.length; o++) + re(i[o], null, t, e, "v-on handler"); + } + return (n.fns = t), n; + } + function we(t, e, n, i, a, s) { + var u, c, f, l; + for (u in t) + (c = t[u]), + (f = e[u]), + (l = be(u)), + r(c) || + (r(f) + ? (r(c.fns) && (c = t[u] = _e(c, s)), + o(l.once) && + (c = t[u] = a(l.name, c, l.capture)), + n( + l.name, + c, + l.capture, + l.passive, + l.params + )) + : c !== f && ((f.fns = c), (t[u] = f))); + for (u in e) + r(t[u]) && ((l = be(u)), i(l.name, e[u], l.capture)); + } + function xe(t, e, n) { + var a; + t instanceof bt && (t = t.data.hook || (t.data.hook = {})); + var s = t[e]; + function u() { + n.apply(this, arguments), g(a.fns, u); + } + r(s) + ? (a = _e([u])) + : i(s.fns) && o(s.merged) + ? ((a = s), a.fns.push(u)) + : (a = _e([s, u])), + (a.merged = !0), + (t[e] = a); + } + function ke(t, e, n) { + var o = e.options.props; + if (!r(o)) { + var a = {}, + s = t.attrs, + u = t.props; + if (i(s) || i(u)) + for (var c in o) { + var f = O(c); + Se(a, u, c, f, !0) || Se(a, s, c, f, !1); + } + return a; + } + } + function Se(t, e, n, r, o) { + if (i(e)) { + if (_(e, n)) return (t[n] = e[n]), o || delete e[n], !0; + if (_(e, r)) return (t[n] = e[r]), o || delete e[r], !0; + } + return !1; + } + function Ee(t) { + for (var e = 0; e < t.length; e++) + if (Array.isArray(t[e])) + return Array.prototype.concat.apply([], t); + return t; + } + function Oe(t) { + return s(t) ? [xt(t)] : Array.isArray(t) ? Ae(t) : void 0; + } + function Te(t) { + return i(t) && i(t.text) && a(t.isComment); + } + function Ae(t, e) { + var n, + a, + u, + c, + f = []; + for (n = 0; n < t.length; n++) + (a = t[n]), + r(a) || + "boolean" === typeof a || + ((u = f.length - 1), + (c = f[u]), + Array.isArray(a) + ? a.length > 0 && + ((a = Ae(a, (e || "") + "_" + n)), + Te(a[0]) && + Te(c) && + ((f[u] = xt(c.text + a[0].text)), + a.shift()), + f.push.apply(f, a)) + : s(a) + ? Te(c) + ? (f[u] = xt(c.text + a)) + : "" !== a && f.push(xt(a)) + : Te(a) && Te(c) + ? (f[u] = xt(c.text + a.text)) + : (o(t._isVList) && + i(a.tag) && + r(a.key) && + i(e) && + (a.key = + "__vlist" + e + "_" + n + "__"), + f.push(a))); + return f; + } + function Ce(t) { + var e = t.$options.provide; + e && + (t._provided = "function" === typeof e ? e.call(t) : e); + } + function je(t) { + var e = Ie(t.$options.inject, t); + e && + (Ct(!1), + Object.keys(e).forEach(function (n) { + Pt(t, n, e[n]); + }), + Ct(!0)); + } + function Ie(t, e) { + if (t) { + for ( + var n = Object.create(null), + r = dt ? Reflect.ownKeys(t) : Object.keys(t), + i = 0; + i < r.length; + i++ + ) { + var o = r[i]; + if ("__ob__" !== o) { + var a = t[o].from, + s = e; + while (s) { + if (s._provided && _(s._provided, a)) { + n[o] = s._provided[a]; + break; + } + s = s.$parent; + } + if (!s) + if ("default" in t[o]) { + var u = t[o].default; + n[o] = + "function" === typeof u + ? u.call(e) + : u; + } else 0; + } + } + return n; + } + } + function Me(t, e) { + if (!t || !t.length) return {}; + for (var n = {}, r = 0, i = t.length; r < i; r++) { + var o = t[r], + a = o.data; + if ( + (a && + a.attrs && + a.attrs.slot && + delete a.attrs.slot, + (o.context !== e && o.fnContext !== e) || + !a || + null == a.slot) + ) + (n.default || (n.default = [])).push(o); + else { + var s = a.slot, + u = n[s] || (n[s] = []); + "template" === o.tag + ? u.push.apply(u, o.children || []) + : u.push(o); + } + } + for (var c in n) n[c].every(Le) && delete n[c]; + return n; + } + function Le(t) { + return (t.isComment && !t.asyncFactory) || " " === t.text; + } + function Pe(t) { + return t.isComment && t.asyncFactory; + } + function Ne(t, e, r) { + var i, + o = Object.keys(e).length > 0, + a = t ? !!t.$stable : !o, + s = t && t.$key; + if (t) { + if (t._normalized) return t._normalized; + if ( + a && + r && + r !== n && + s === r.$key && + !o && + !r.$hasNormal + ) + return r; + for (var u in ((i = {}), t)) + t[u] && "$" !== u[0] && (i[u] = Re(e, u, t[u])); + } else i = {}; + for (var c in e) c in i || (i[c] = $e(e, c)); + return ( + t && Object.isExtensible(t) && (t._normalized = i), + q(i, "$stable", a), + q(i, "$key", s), + q(i, "$hasNormal", o), + i + ); + } + function Re(t, e, n) { + var r = function () { + var t = arguments.length + ? n.apply(null, arguments) + : n({}); + t = + t && "object" === typeof t && !Array.isArray(t) + ? [t] + : Oe(t); + var e = t && t[0]; + return t && + (!e || (1 === t.length && e.isComment && !Pe(e))) + ? void 0 + : t; + }; + return ( + n.proxy && + Object.defineProperty(t, e, { + get: r, + enumerable: !0, + configurable: !0, + }), + r + ); + } + function $e(t, e) { + return function () { + return t[e]; + }; + } + function De(t, e) { + var n, r, o, a, s; + if (Array.isArray(t) || "string" === typeof t) + for ( + n = new Array(t.length), r = 0, o = t.length; + r < o; + r++ + ) + n[r] = e(t[r], r); + else if ("number" === typeof t) + for (n = new Array(t), r = 0; r < t; r++) + n[r] = e(r + 1, r); + else if (u(t)) + if (dt && t[Symbol.iterator]) { + n = []; + var c = t[Symbol.iterator](), + f = c.next(); + while (!f.done) + n.push(e(f.value, n.length)), (f = c.next()); + } else + for ( + a = Object.keys(t), + n = new Array(a.length), + r = 0, + o = a.length; + r < o; + r++ + ) + (s = a[r]), (n[r] = e(t[s], s, r)); + return i(n) || (n = []), (n._isVList = !0), n; + } + function Fe(t, e, n, r) { + var i, + o = this.$scopedSlots[t]; + o + ? ((n = n || {}), + r && (n = I(I({}, r), n)), + (i = o(n) || ("function" === typeof e ? e() : e))) + : (i = + this.$slots[t] || + ("function" === typeof e ? e() : e)); + var a = n && n.slot; + return a + ? this.$createElement("template", { slot: a }, i) + : i; + } + function ze(t) { + return Jt(this.$options, "filters", t, !0) || N; + } + function Ue(t, e) { + return Array.isArray(t) ? -1 === t.indexOf(e) : t !== e; + } + function Be(t, e, n, r, i) { + var o = B.keyCodes[e] || n; + return i && r && !B.keyCodes[e] + ? Ue(i, r) + : o + ? Ue(o, t) + : r + ? O(r) !== e + : void 0 === t; + } + function Ve(t, e, n, r, i) { + if (n) + if (u(n)) { + var o; + Array.isArray(n) && (n = M(n)); + var a = function (a) { + if ("class" === a || "style" === a || m(a)) + o = t; + else { + var s = t.attrs && t.attrs.type; + o = + r || B.mustUseProp(e, s, a) + ? t.domProps || (t.domProps = {}) + : t.attrs || (t.attrs = {}); + } + var u = k(a), + c = O(a); + if ( + !(u in o) && + !(c in o) && + ((o[a] = n[a]), i) + ) { + var f = t.on || (t.on = {}); + f["update:" + a] = function (t) { + n[a] = t; + }; + } + }; + for (var s in n) a(s); + } else; + return t; + } + function He(t, e) { + var n = this._staticTrees || (this._staticTrees = []), + r = n[t]; + return ( + (r && !e) || + ((r = n[t] = + this.$options.staticRenderFns[t].call( + this._renderProxy, + null, + this + )), + We(r, "__static__" + t, !1)), + r + ); + } + function qe(t, e, n) { + return We(t, "__once__" + e + (n ? "_" + n : ""), !0), t; + } + function We(t, e, n) { + if (Array.isArray(t)) + for (var r = 0; r < t.length; r++) + t[r] && + "string" !== typeof t[r] && + Ge(t[r], e + "_" + r, n); + else Ge(t, e, n); + } + function Ge(t, e, n) { + (t.isStatic = !0), (t.key = e), (t.isOnce = n); + } + function Ze(t, e) { + if (e) + if (f(e)) { + var n = (t.on = t.on ? I({}, t.on) : {}); + for (var r in e) { + var i = n[r], + o = e[r]; + n[r] = i ? [].concat(i, o) : o; + } + } else; + return t; + } + function Je(t, e, n, r) { + e = e || { $stable: !n }; + for (var i = 0; i < t.length; i++) { + var o = t[i]; + Array.isArray(o) + ? Je(o, e, n) + : o && + (o.proxy && (o.fn.proxy = !0), (e[o.key] = o.fn)); + } + return r && (e.$key = r), e; + } + function Ke(t, e) { + for (var n = 0; n < e.length; n += 2) { + var r = e[n]; + "string" === typeof r && r && (t[e[n]] = e[n + 1]); + } + return t; + } + function Xe(t, e) { + return "string" === typeof t ? e + t : t; + } + function Ye(t) { + (t._o = qe), + (t._n = v), + (t._s = h), + (t._l = De), + (t._t = Fe), + (t._q = R), + (t._i = $), + (t._m = He), + (t._f = ze), + (t._k = Be), + (t._b = Ve), + (t._v = xt), + (t._e = wt), + (t._u = Je), + (t._g = Ze), + (t._d = Ke), + (t._p = Xe); + } + function Qe(t, e, r, i, a) { + var s, + u = this, + c = a.options; + _(i, "_uid") + ? ((s = Object.create(i)), (s._original = i)) + : ((s = i), (i = i._original)); + var f = o(c._compiled), + l = !f; + (this.data = t), + (this.props = e), + (this.children = r), + (this.parent = i), + (this.listeners = t.on || n), + (this.injections = Ie(c.inject, i)), + (this.slots = function () { + return ( + u.$slots || + Ne(t.scopedSlots, (u.$slots = Me(r, i))), + u.$slots + ); + }), + Object.defineProperty(this, "scopedSlots", { + enumerable: !0, + get: function () { + return Ne(t.scopedSlots, this.slots()); + }, + }), + f && + ((this.$options = c), + (this.$slots = this.slots()), + (this.$scopedSlots = Ne( + t.scopedSlots, + this.$slots + ))), + c._scopeId + ? (this._c = function (t, e, n, r) { + var o = pn(s, t, e, n, r, l); + return ( + o && + !Array.isArray(o) && + ((o.fnScopeId = c._scopeId), + (o.fnContext = i)), + o + ); + }) + : (this._c = function (t, e, n, r) { + return pn(s, t, e, n, r, l); + }); + } + function tn(t, e, r, o, a) { + var s = t.options, + u = {}, + c = s.props; + if (i(c)) for (var f in c) u[f] = Kt(f, c, e || n); + else + i(r.attrs) && nn(u, r.attrs), + i(r.props) && nn(u, r.props); + var l = new Qe(r, u, a, o, t), + d = s.render.call(null, l._c, l); + if (d instanceof bt) return en(d, r, l.parent, s, l); + if (Array.isArray(d)) { + for ( + var p = Oe(d) || [], h = new Array(p.length), v = 0; + v < p.length; + v++ + ) + h[v] = en(p[v], r, l.parent, s, l); + return h; + } + } + function en(t, e, n, r, i) { + var o = kt(t); + return ( + (o.fnContext = n), + (o.fnOptions = r), + e.slot && ((o.data || (o.data = {})).slot = e.slot), + o + ); + } + function nn(t, e) { + for (var n in e) t[k(n)] = e[n]; + } + Ye(Qe.prototype); + var rn = { + init: function (t, e) { + if ( + t.componentInstance && + !t.componentInstance._isDestroyed && + t.data.keepAlive + ) { + var n = t; + rn.prepatch(n, n); + } else { + var r = (t.componentInstance = sn(t, In)); + r.$mount(e ? t.elm : void 0, e); + } + }, + prepatch: function (t, e) { + var n = e.componentOptions, + r = (e.componentInstance = t.componentInstance); + Rn(r, n.propsData, n.listeners, e, n.children); + }, + insert: function (t) { + var e = t.context, + n = t.componentInstance; + n._isMounted || + ((n._isMounted = !0), zn(n, "mounted")), + t.data.keepAlive && + (e._isMounted ? Qn(n) : Dn(n, !0)); + }, + destroy: function (t) { + var e = t.componentInstance; + e._isDestroyed || + (t.data.keepAlive ? Fn(e, !0) : e.$destroy()); + }, + }, + on = Object.keys(rn); + function an(t, e, n, a, s) { + if (!r(t)) { + var c = n.$options._base; + if ( + (u(t) && (t = c.extend(t)), "function" === typeof t) + ) { + var f; + if ( + r(t.cid) && + ((f = t), (t = kn(f, c)), void 0 === t) + ) + return xn(f, e, n, a, s); + (e = e || {}), + xr(t), + i(e.model) && fn(t.options, e); + var l = ke(e, t, s); + if (o(t.options.functional)) + return tn(t, l, e, n, a); + var d = e.on; + if (((e.on = e.nativeOn), o(t.options.abstract))) { + var p = e.slot; + (e = {}), p && (e.slot = p); + } + un(e); + var h = t.options.name || s, + v = new bt( + "vue-component-" + + t.cid + + (h ? "-" + h : ""), + e, + void 0, + void 0, + void 0, + n, + { + Ctor: t, + propsData: l, + listeners: d, + tag: s, + children: a, + }, + f + ); + return v; + } + } + } + function sn(t, e) { + var n = { _isComponent: !0, _parentVnode: t, parent: e }, + r = t.data.inlineTemplate; + return ( + i(r) && + ((n.render = r.render), + (n.staticRenderFns = r.staticRenderFns)), + new t.componentOptions.Ctor(n) + ); + } + function un(t) { + for ( + var e = t.hook || (t.hook = {}), n = 0; + n < on.length; + n++ + ) { + var r = on[n], + i = e[r], + o = rn[r]; + i === o || + (i && i._merged) || + (e[r] = i ? cn(o, i) : o); + } + } + function cn(t, e) { + var n = function (n, r) { + t(n, r), e(n, r); + }; + return (n._merged = !0), n; + } + function fn(t, e) { + var n = (t.model && t.model.prop) || "value", + r = (t.model && t.model.event) || "input"; + (e.attrs || (e.attrs = {}))[n] = e.model.value; + var o = e.on || (e.on = {}), + a = o[r], + s = e.model.callback; + i(a) + ? (Array.isArray(a) ? -1 === a.indexOf(s) : a !== s) && + (o[r] = [s].concat(a)) + : (o[r] = s); + } + var ln = 1, + dn = 2; + function pn(t, e, n, r, i, a) { + return ( + (Array.isArray(n) || s(n)) && + ((i = r), (r = n), (n = void 0)), + o(a) && (i = dn), + hn(t, e, n, r, i) + ); + } + function hn(t, e, n, r, o) { + if (i(n) && i(n.__ob__)) return wt(); + if ((i(n) && i(n.is) && (e = n.is), !e)) return wt(); + var a, s, u; + (Array.isArray(r) && + "function" === typeof r[0] && + ((n = n || {}), + (n.scopedSlots = { default: r[0] }), + (r.length = 0)), + o === dn ? (r = Oe(r)) : o === ln && (r = Ee(r)), + "string" === typeof e) + ? ((s = + (t.$vnode && t.$vnode.ns) || + B.getTagNamespace(e)), + (a = B.isReservedTag(e) + ? new bt( + B.parsePlatformTagName(e), + n, + r, + void 0, + void 0, + t + ) + : (n && n.pre) || + !i((u = Jt(t.$options, "components", e))) + ? new bt(e, n, r, void 0, void 0, t) + : an(u, n, t, r, e))) + : (a = an(e, n, t, r)); + return Array.isArray(a) + ? a + : i(a) + ? (i(s) && vn(a, s), i(n) && yn(n), a) + : wt(); + } + function vn(t, e, n) { + if ( + ((t.ns = e), + "foreignObject" === t.tag && ((e = void 0), (n = !0)), + i(t.children)) + ) + for (var a = 0, s = t.children.length; a < s; a++) { + var u = t.children[a]; + i(u.tag) && + (r(u.ns) || (o(n) && "svg" !== u.tag)) && + vn(u, e, n); + } + } + function yn(t) { + u(t.style) && me(t.style), u(t.class) && me(t.class); + } + function mn(t) { + (t._vnode = null), (t._staticTrees = null); + var e = t.$options, + r = (t.$vnode = e._parentVnode), + i = r && r.context; + (t.$slots = Me(e._renderChildren, i)), + (t.$scopedSlots = n), + (t._c = function (e, n, r, i) { + return pn(t, e, n, r, i, !1); + }), + (t.$createElement = function (e, n, r, i) { + return pn(t, e, n, r, i, !0); + }); + var o = r && r.data; + Pt(t, "$attrs", (o && o.attrs) || n, null, !0), + Pt(t, "$listeners", e._parentListeners || n, null, !0); + } + var gn, + bn = null; + function _n(t) { + Ye(t.prototype), + (t.prototype.$nextTick = function (t) { + return ve(t, this); + }), + (t.prototype._render = function () { + var t, + e = this, + n = e.$options, + r = n.render, + i = n._parentVnode; + i && + (e.$scopedSlots = Ne( + i.data.scopedSlots, + e.$slots, + e.$scopedSlots + )), + (e.$vnode = i); + try { + (bn = e), + (t = r.call( + e._renderProxy, + e.$createElement + )); + } catch (Sa) { + ne(Sa, e, "render"), (t = e._vnode); + } finally { + bn = null; + } + return ( + Array.isArray(t) && + 1 === t.length && + (t = t[0]), + t instanceof bt || (t = wt()), + (t.parent = i), + t + ); + }); + } + function wn(t, e) { + return ( + (t.__esModule || + (dt && "Module" === t[Symbol.toStringTag])) && + (t = t.default), + u(t) ? e.extend(t) : t + ); + } + function xn(t, e, n, r, i) { + var o = wt(); + return ( + (o.asyncFactory = t), + (o.asyncMeta = { + data: e, + context: n, + children: r, + tag: i, + }), + o + ); + } + function kn(t, e) { + if (o(t.error) && i(t.errorComp)) return t.errorComp; + if (i(t.resolved)) return t.resolved; + var n = bn; + if ( + (n && + i(t.owners) && + -1 === t.owners.indexOf(n) && + t.owners.push(n), + o(t.loading) && i(t.loadingComp)) + ) + return t.loadingComp; + if (n && !i(t.owners)) { + var a = (t.owners = [n]), + s = !0, + c = null, + f = null; + n.$on("hook:destroyed", function () { + return g(a, n); + }); + var l = function (t) { + for (var e = 0, n = a.length; e < n; e++) + a[e].$forceUpdate(); + t && + ((a.length = 0), + null !== c && (clearTimeout(c), (c = null)), + null !== f && + (clearTimeout(f), (f = null))); + }, + d = D(function (n) { + (t.resolved = wn(n, e)), + s ? (a.length = 0) : l(!0); + }), + h = D(function (e) { + i(t.errorComp) && ((t.error = !0), l(!0)); + }), + v = t(d, h); + return ( + u(v) && + (p(v) + ? r(t.resolved) && v.then(d, h) + : p(v.component) && + (v.component.then(d, h), + i(v.error) && + (t.errorComp = wn(v.error, e)), + i(v.loading) && + ((t.loadingComp = wn(v.loading, e)), + 0 === v.delay + ? (t.loading = !0) + : (c = setTimeout(function () { + (c = null), + r(t.resolved) && + r(t.error) && + ((t.loading = !0), + l(!1)); + }, v.delay || 200))), + i(v.timeout) && + (f = setTimeout(function () { + (f = null), + r(t.resolved) && h(null); + }, v.timeout)))), + (s = !1), + t.loading ? t.loadingComp : t.resolved + ); + } + } + function Sn(t) { + if (Array.isArray(t)) + for (var e = 0; e < t.length; e++) { + var n = t[e]; + if (i(n) && (i(n.componentOptions) || Pe(n))) + return n; + } + } + function En(t) { + (t._events = Object.create(null)), (t._hasHookEvent = !1); + var e = t.$options._parentListeners; + e && Cn(t, e); + } + function On(t, e) { + gn.$on(t, e); + } + function Tn(t, e) { + gn.$off(t, e); + } + function An(t, e) { + var n = gn; + return function r() { + var i = e.apply(null, arguments); + null !== i && n.$off(t, r); + }; + } + function Cn(t, e, n) { + (gn = t), we(e, n || {}, On, Tn, An, t), (gn = void 0); + } + function jn(t) { + var e = /^hook:/; + (t.prototype.$on = function (t, n) { + var r = this; + if (Array.isArray(t)) + for (var i = 0, o = t.length; i < o; i++) + r.$on(t[i], n); + else + (r._events[t] || (r._events[t] = [])).push(n), + e.test(t) && (r._hasHookEvent = !0); + return r; + }), + (t.prototype.$once = function (t, e) { + var n = this; + function r() { + n.$off(t, r), e.apply(n, arguments); + } + return (r.fn = e), n.$on(t, r), n; + }), + (t.prototype.$off = function (t, e) { + var n = this; + if (!arguments.length) + return (n._events = Object.create(null)), n; + if (Array.isArray(t)) { + for (var r = 0, i = t.length; r < i; r++) + n.$off(t[r], e); + return n; + } + var o, + a = n._events[t]; + if (!a) return n; + if (!e) return (n._events[t] = null), n; + var s = a.length; + while (s--) + if (((o = a[s]), o === e || o.fn === e)) { + a.splice(s, 1); + break; + } + return n; + }), + (t.prototype.$emit = function (t) { + var e = this, + n = e._events[t]; + if (n) { + n = n.length > 1 ? j(n) : n; + for ( + var r = j(arguments, 1), + i = 'event handler for "' + t + '"', + o = 0, + a = n.length; + o < a; + o++ + ) + re(n[o], e, r, e, i); + } + return e; + }); + } + var In = null; + function Mn(t) { + var e = In; + return ( + (In = t), + function () { + In = e; + } + ); + } + function Ln(t) { + var e = t.$options, + n = e.parent; + if (n && !e.abstract) { + while (n.$options.abstract && n.$parent) n = n.$parent; + n.$children.push(t); + } + (t.$parent = n), + (t.$root = n ? n.$root : t), + (t.$children = []), + (t.$refs = {}), + (t._watcher = null), + (t._inactive = null), + (t._directInactive = !1), + (t._isMounted = !1), + (t._isDestroyed = !1), + (t._isBeingDestroyed = !1); + } + function Pn(t) { + (t.prototype._update = function (t, e) { + var n = this, + r = n.$el, + i = n._vnode, + o = Mn(n); + (n._vnode = t), + (n.$el = i + ? n.__patch__(i, t) + : n.__patch__(n.$el, t, e, !1)), + o(), + r && (r.__vue__ = null), + n.$el && (n.$el.__vue__ = n), + n.$vnode && + n.$parent && + n.$vnode === n.$parent._vnode && + (n.$parent.$el = n.$el); + }), + (t.prototype.$forceUpdate = function () { + var t = this; + t._watcher && t._watcher.update(); + }), + (t.prototype.$destroy = function () { + var t = this; + if (!t._isBeingDestroyed) { + zn(t, "beforeDestroy"), + (t._isBeingDestroyed = !0); + var e = t.$parent; + !e || + e._isBeingDestroyed || + t.$options.abstract || + g(e.$children, t), + t._watcher && t._watcher.teardown(); + var n = t._watchers.length; + while (n--) t._watchers[n].teardown(); + t._data.__ob__ && t._data.__ob__.vmCount--, + (t._isDestroyed = !0), + t.__patch__(t._vnode, null), + zn(t, "destroyed"), + t.$off(), + t.$el && (t.$el.__vue__ = null), + t.$vnode && (t.$vnode.parent = null); + } + }); + } + function Nn(t, e, n) { + var r; + return ( + (t.$el = e), + t.$options.render || (t.$options.render = wt), + zn(t, "beforeMount"), + (r = function () { + t._update(t._render(), n); + }), + new rr( + t, + r, + L, + { + before: function () { + t._isMounted && + !t._isDestroyed && + zn(t, "beforeUpdate"); + }, + }, + !0 + ), + (n = !1), + null == t.$vnode && + ((t._isMounted = !0), zn(t, "mounted")), + t + ); + } + function Rn(t, e, r, i, o) { + var a = i.data.scopedSlots, + s = t.$scopedSlots, + u = !!( + (a && !a.$stable) || + (s !== n && !s.$stable) || + (a && t.$scopedSlots.$key !== a.$key) || + (!a && t.$scopedSlots.$key) + ), + c = !!(o || t.$options._renderChildren || u); + if ( + ((t.$options._parentVnode = i), + (t.$vnode = i), + t._vnode && (t._vnode.parent = i), + (t.$options._renderChildren = o), + (t.$attrs = i.data.attrs || n), + (t.$listeners = r || n), + e && t.$options.props) + ) { + Ct(!1); + for ( + var f = t._props, + l = t.$options._propKeys || [], + d = 0; + d < l.length; + d++ + ) { + var p = l[d], + h = t.$options.props; + f[p] = Kt(p, h, e, t); + } + Ct(!0), (t.$options.propsData = e); + } + r = r || n; + var v = t.$options._parentListeners; + (t.$options._parentListeners = r), + Cn(t, r, v), + c && ((t.$slots = Me(o, i.context)), t.$forceUpdate()); + } + function $n(t) { + while (t && (t = t.$parent)) if (t._inactive) return !0; + return !1; + } + function Dn(t, e) { + if (e) { + if (((t._directInactive = !1), $n(t))) return; + } else if (t._directInactive) return; + if (t._inactive || null === t._inactive) { + t._inactive = !1; + for (var n = 0; n < t.$children.length; n++) + Dn(t.$children[n]); + zn(t, "activated"); + } + } + function Fn(t, e) { + if ( + (!e || ((t._directInactive = !0), !$n(t))) && + !t._inactive + ) { + t._inactive = !0; + for (var n = 0; n < t.$children.length; n++) + Fn(t.$children[n]); + zn(t, "deactivated"); + } + } + function zn(t, e) { + mt(); + var n = t.$options[e], + r = e + " hook"; + if (n) + for (var i = 0, o = n.length; i < o; i++) + re(n[i], t, null, t, r); + t._hasHookEvent && t.$emit("hook:" + e), gt(); + } + var Un = [], + Bn = [], + Vn = {}, + Hn = !1, + qn = !1, + Wn = 0; + function Gn() { + (Wn = Un.length = Bn.length = 0), (Vn = {}), (Hn = qn = !1); + } + var Zn = 0, + Jn = Date.now; + if (K && !tt) { + var Kn = window.performance; + Kn && + "function" === typeof Kn.now && + Jn() > document.createEvent("Event").timeStamp && + (Jn = function () { + return Kn.now(); + }); + } + function Xn() { + var t, e; + for ( + Zn = Jn(), + qn = !0, + Un.sort(function (t, e) { + return t.id - e.id; + }), + Wn = 0; + Wn < Un.length; + Wn++ + ) + (t = Un[Wn]), + t.before && t.before(), + (e = t.id), + (Vn[e] = null), + t.run(); + var n = Bn.slice(), + r = Un.slice(); + Gn(), tr(n), Yn(r), ct && B.devtools && ct.emit("flush"); + } + function Yn(t) { + var e = t.length; + while (e--) { + var n = t[e], + r = n.vm; + r._watcher === n && + r._isMounted && + !r._isDestroyed && + zn(r, "updated"); + } + } + function Qn(t) { + (t._inactive = !1), Bn.push(t); + } + function tr(t) { + for (var e = 0; e < t.length; e++) + (t[e]._inactive = !0), Dn(t[e], !0); + } + function er(t) { + var e = t.id; + if (null == Vn[e]) { + if (((Vn[e] = !0), qn)) { + var n = Un.length - 1; + while (n > Wn && Un[n].id > t.id) n--; + Un.splice(n + 1, 0, t); + } else Un.push(t); + Hn || ((Hn = !0), ve(Xn)); + } + } + var nr = 0, + rr = function (t, e, n, r, i) { + (this.vm = t), + i && (t._watcher = this), + t._watchers.push(this), + r + ? ((this.deep = !!r.deep), + (this.user = !!r.user), + (this.lazy = !!r.lazy), + (this.sync = !!r.sync), + (this.before = r.before)) + : (this.deep = + this.user = + this.lazy = + this.sync = + !1), + (this.cb = n), + (this.id = ++nr), + (this.active = !0), + (this.dirty = this.lazy), + (this.deps = []), + (this.newDeps = []), + (this.depIds = new lt()), + (this.newDepIds = new lt()), + (this.expression = ""), + "function" === typeof e + ? (this.getter = e) + : ((this.getter = G(e)), + this.getter || (this.getter = L)), + (this.value = this.lazy ? void 0 : this.get()); + }; + (rr.prototype.get = function () { + var t; + mt(this); + var e = this.vm; + try { + t = this.getter.call(e, e); + } catch (Sa) { + if (!this.user) throw Sa; + ne( + Sa, + e, + 'getter for watcher "' + this.expression + '"' + ); + } finally { + this.deep && me(t), gt(), this.cleanupDeps(); + } + return t; + }), + (rr.prototype.addDep = function (t) { + var e = t.id; + this.newDepIds.has(e) || + (this.newDepIds.add(e), + this.newDeps.push(t), + this.depIds.has(e) || t.addSub(this)); + }), + (rr.prototype.cleanupDeps = function () { + var t = this.deps.length; + while (t--) { + var e = this.deps[t]; + this.newDepIds.has(e.id) || e.removeSub(this); + } + var n = this.depIds; + (this.depIds = this.newDepIds), + (this.newDepIds = n), + this.newDepIds.clear(), + (n = this.deps), + (this.deps = this.newDeps), + (this.newDeps = n), + (this.newDeps.length = 0); + }), + (rr.prototype.update = function () { + this.lazy + ? (this.dirty = !0) + : this.sync + ? this.run() + : er(this); + }), + (rr.prototype.run = function () { + if (this.active) { + var t = this.get(); + if (t !== this.value || u(t) || this.deep) { + var e = this.value; + if (((this.value = t), this.user)) { + var n = + 'callback for watcher "' + + this.expression + + '"'; + re(this.cb, this.vm, [t, e], this.vm, n); + } else this.cb.call(this.vm, t, e); + } + } + }), + (rr.prototype.evaluate = function () { + (this.value = this.get()), (this.dirty = !1); + }), + (rr.prototype.depend = function () { + var t = this.deps.length; + while (t--) this.deps[t].depend(); + }), + (rr.prototype.teardown = function () { + if (this.active) { + this.vm._isBeingDestroyed || + g(this.vm._watchers, this); + var t = this.deps.length; + while (t--) this.deps[t].removeSub(this); + this.active = !1; + } + }); + var ir = { enumerable: !0, configurable: !0, get: L, set: L }; + function or(t, e, n) { + (ir.get = function () { + return this[e][n]; + }), + (ir.set = function (t) { + this[e][n] = t; + }), + Object.defineProperty(t, n, ir); + } + function ar(t) { + t._watchers = []; + var e = t.$options; + e.props && sr(t, e.props), + e.methods && vr(t, e.methods), + e.data ? ur(t) : Lt((t._data = {}), !0), + e.computed && lr(t, e.computed), + e.watch && e.watch !== ot && yr(t, e.watch); + } + function sr(t, e) { + var n = t.$options.propsData || {}, + r = (t._props = {}), + i = (t.$options._propKeys = []), + o = !t.$parent; + o || Ct(!1); + var a = function (o) { + i.push(o); + var a = Kt(o, e, n, t); + Pt(r, o, a), o in t || or(t, "_props", o); + }; + for (var s in e) a(s); + Ct(!0); + } + function ur(t) { + var e = t.$options.data; + (e = t._data = + "function" === typeof e ? cr(e, t) : e || {}), + f(e) || (e = {}); + var n = Object.keys(e), + r = t.$options.props, + i = (t.$options.methods, n.length); + while (i--) { + var o = n[i]; + 0, (r && _(r, o)) || H(o) || or(t, "_data", o); + } + Lt(e, !0); + } + function cr(t, e) { + mt(); + try { + return t.call(e, e); + } catch (Sa) { + return ne(Sa, e, "data()"), {}; + } finally { + gt(); + } + } + var fr = { lazy: !0 }; + function lr(t, e) { + var n = (t._computedWatchers = Object.create(null)), + r = ut(); + for (var i in e) { + var o = e[i], + a = "function" === typeof o ? o : o.get; + 0, + r || (n[i] = new rr(t, a || L, L, fr)), + i in t || dr(t, i, o); + } + } + function dr(t, e, n) { + var r = !ut(); + "function" === typeof n + ? ((ir.get = r ? pr(e) : hr(n)), (ir.set = L)) + : ((ir.get = n.get + ? r && !1 !== n.cache + ? pr(e) + : hr(n.get) + : L), + (ir.set = n.set || L)), + Object.defineProperty(t, e, ir); + } + function pr(t) { + return function () { + var e = + this._computedWatchers && this._computedWatchers[t]; + if (e) + return ( + e.dirty && e.evaluate(), + vt.target && e.depend(), + e.value + ); + }; + } + function hr(t) { + return function () { + return t.call(this, this); + }; + } + function vr(t, e) { + t.$options.props; + for (var n in e) + t[n] = "function" !== typeof e[n] ? L : C(e[n], t); + } + function yr(t, e) { + for (var n in e) { + var r = e[n]; + if (Array.isArray(r)) + for (var i = 0; i < r.length; i++) mr(t, n, r[i]); + else mr(t, n, r); + } + } + function mr(t, e, n, r) { + return ( + f(n) && ((r = n), (n = n.handler)), + "string" === typeof n && (n = t[n]), + t.$watch(e, n, r) + ); + } + function gr(t) { + var e = { + get: function () { + return this._data; + }, + }, + n = { + get: function () { + return this._props; + }, + }; + Object.defineProperty(t.prototype, "$data", e), + Object.defineProperty(t.prototype, "$props", n), + (t.prototype.$set = Nt), + (t.prototype.$delete = Rt), + (t.prototype.$watch = function (t, e, n) { + var r = this; + if (f(e)) return mr(r, t, e, n); + (n = n || {}), (n.user = !0); + var i = new rr(r, t, e, n); + if (n.immediate) { + var o = + 'callback for immediate watcher "' + + i.expression + + '"'; + mt(), re(e, r, [i.value], r, o), gt(); + } + return function () { + i.teardown(); + }; + }); + } + var br = 0; + function _r(t) { + t.prototype._init = function (t) { + var e = this; + (e._uid = br++), + (e._isVue = !0), + t && t._isComponent + ? wr(e, t) + : (e.$options = Zt( + xr(e.constructor), + t || {}, + e + )), + (e._renderProxy = e), + (e._self = e), + Ln(e), + En(e), + mn(e), + zn(e, "beforeCreate"), + je(e), + ar(e), + Ce(e), + zn(e, "created"), + e.$options.el && e.$mount(e.$options.el); + }; + } + function wr(t, e) { + var n = (t.$options = Object.create(t.constructor.options)), + r = e._parentVnode; + (n.parent = e.parent), (n._parentVnode = r); + var i = r.componentOptions; + (n.propsData = i.propsData), + (n._parentListeners = i.listeners), + (n._renderChildren = i.children), + (n._componentTag = i.tag), + e.render && + ((n.render = e.render), + (n.staticRenderFns = e.staticRenderFns)); + } + function xr(t) { + var e = t.options; + if (t.super) { + var n = xr(t.super), + r = t.superOptions; + if (n !== r) { + t.superOptions = n; + var i = kr(t); + i && I(t.extendOptions, i), + (e = t.options = Zt(n, t.extendOptions)), + e.name && (e.components[e.name] = t); + } + } + return e; + } + function kr(t) { + var e, + n = t.options, + r = t.sealedOptions; + for (var i in n) + n[i] !== r[i] && (e || (e = {}), (e[i] = n[i])); + return e; + } + function Sr(t) { + this._init(t); + } + function Er(t) { + t.use = function (t) { + var e = + this._installedPlugins || + (this._installedPlugins = []); + if (e.indexOf(t) > -1) return this; + var n = j(arguments, 1); + return ( + n.unshift(this), + "function" === typeof t.install + ? t.install.apply(t, n) + : "function" === typeof t && t.apply(null, n), + e.push(t), + this + ); + }; + } + function Or(t) { + t.mixin = function (t) { + return (this.options = Zt(this.options, t)), this; + }; + } + function Tr(t) { + t.cid = 0; + var e = 1; + t.extend = function (t) { + t = t || {}; + var n = this, + r = n.cid, + i = t._Ctor || (t._Ctor = {}); + if (i[r]) return i[r]; + var o = t.name || n.options.name; + var a = function (t) { + this._init(t); + }; + return ( + (a.prototype = Object.create(n.prototype)), + (a.prototype.constructor = a), + (a.cid = e++), + (a.options = Zt(n.options, t)), + (a["super"] = n), + a.options.props && Ar(a), + a.options.computed && Cr(a), + (a.extend = n.extend), + (a.mixin = n.mixin), + (a.use = n.use), + z.forEach(function (t) { + a[t] = n[t]; + }), + o && (a.options.components[o] = a), + (a.superOptions = n.options), + (a.extendOptions = t), + (a.sealedOptions = I({}, a.options)), + (i[r] = a), + a + ); + }; + } + function Ar(t) { + var e = t.options.props; + for (var n in e) or(t.prototype, "_props", n); + } + function Cr(t) { + var e = t.options.computed; + for (var n in e) dr(t.prototype, n, e[n]); + } + function jr(t) { + z.forEach(function (e) { + t[e] = function (t, n) { + return n + ? ("component" === e && + f(n) && + ((n.name = n.name || t), + (n = this.options._base.extend(n))), + "directive" === e && + "function" === typeof n && + (n = { bind: n, update: n }), + (this.options[e + "s"][t] = n), + n) + : this.options[e + "s"][t]; + }; + }); + } + function Ir(t) { + return t && (t.Ctor.options.name || t.tag); + } + function Mr(t, e) { + return Array.isArray(t) + ? t.indexOf(e) > -1 + : "string" === typeof t + ? t.split(",").indexOf(e) > -1 + : !!l(t) && t.test(e); + } + function Lr(t, e) { + var n = t.cache, + r = t.keys, + i = t._vnode; + for (var o in n) { + var a = n[o]; + if (a) { + var s = a.name; + s && !e(s) && Pr(n, o, r, i); + } + } + } + function Pr(t, e, n, r) { + var i = t[e]; + !i || + (r && i.tag === r.tag) || + i.componentInstance.$destroy(), + (t[e] = null), + g(n, e); + } + _r(Sr), gr(Sr), jn(Sr), Pn(Sr), _n(Sr); + var Nr = [String, RegExp, Array], + Rr = { + name: "keep-alive", + abstract: !0, + props: { + include: Nr, + exclude: Nr, + max: [String, Number], + }, + methods: { + cacheVNode: function () { + var t = this, + e = t.cache, + n = t.keys, + r = t.vnodeToCache, + i = t.keyToCache; + if (r) { + var o = r.tag, + a = r.componentInstance, + s = r.componentOptions; + (e[i] = { + name: Ir(s), + tag: o, + componentInstance: a, + }), + n.push(i), + this.max && + n.length > parseInt(this.max) && + Pr(e, n[0], n, this._vnode), + (this.vnodeToCache = null); + } + }, + }, + created: function () { + (this.cache = Object.create(null)), + (this.keys = []); + }, + destroyed: function () { + for (var t in this.cache) + Pr(this.cache, t, this.keys); + }, + mounted: function () { + var t = this; + this.cacheVNode(), + this.$watch("include", function (e) { + Lr(t, function (t) { + return Mr(e, t); + }); + }), + this.$watch("exclude", function (e) { + Lr(t, function (t) { + return !Mr(e, t); + }); + }); + }, + updated: function () { + this.cacheVNode(); + }, + render: function () { + var t = this.$slots.default, + e = Sn(t), + n = e && e.componentOptions; + if (n) { + var r = Ir(n), + i = this, + o = i.include, + a = i.exclude; + if ( + (o && (!r || !Mr(o, r))) || + (a && r && Mr(a, r)) + ) + return e; + var s = this, + u = s.cache, + c = s.keys, + f = + null == e.key + ? n.Ctor.cid + + (n.tag ? "::" + n.tag : "") + : e.key; + u[f] + ? ((e.componentInstance = + u[f].componentInstance), + g(c, f), + c.push(f)) + : ((this.vnodeToCache = e), + (this.keyToCache = f)), + (e.data.keepAlive = !0); + } + return e || (t && t[0]); + }, + }, + $r = { KeepAlive: Rr }; + function Dr(t) { + var e = { + get: function () { + return B; + }, + }; + Object.defineProperty(t, "config", e), + (t.util = { + warn: pt, + extend: I, + mergeOptions: Zt, + defineReactive: Pt, + }), + (t.set = Nt), + (t.delete = Rt), + (t.nextTick = ve), + (t.observable = function (t) { + return Lt(t), t; + }), + (t.options = Object.create(null)), + z.forEach(function (e) { + t.options[e + "s"] = Object.create(null); + }), + (t.options._base = t), + I(t.options.components, $r), + Er(t), + Or(t), + Tr(t), + jr(t); + } + Dr(Sr), + Object.defineProperty(Sr.prototype, "$isServer", { + get: ut, + }), + Object.defineProperty(Sr.prototype, "$ssrContext", { + get: function () { + return this.$vnode && this.$vnode.ssrContext; + }, + }), + Object.defineProperty(Sr, "FunctionalRenderContext", { + value: Qe, + }), + (Sr.version = "2.6.14"); + var Fr = y("style,class"), + zr = y("input,textarea,option,select,progress"), + Ur = function (t, e, n) { + return ( + ("value" === n && zr(t) && "button" !== e) || + ("selected" === n && "option" === t) || + ("checked" === n && "input" === t) || + ("muted" === n && "video" === t) + ); + }, + Br = y("contenteditable,draggable,spellcheck"), + Vr = y("events,caret,typing,plaintext-only"), + Hr = function (t, e) { + return Jr(e) || "false" === e + ? "false" + : "contenteditable" === t && Vr(e) + ? e + : "true"; + }, + qr = y( + "allowfullscreen,async,autofocus,autoplay,checked,compact,controls,declare,default,defaultchecked,defaultmuted,defaultselected,defer,disabled,enabled,formnovalidate,hidden,indeterminate,inert,ismap,itemscope,loop,multiple,muted,nohref,noresize,noshade,novalidate,nowrap,open,pauseonexit,readonly,required,reversed,scoped,seamless,selected,sortable,truespeed,typemustmatch,visible" + ), + Wr = "http://www.w3.org/1999/xlink", + Gr = function (t) { + return ":" === t.charAt(5) && "xlink" === t.slice(0, 5); + }, + Zr = function (t) { + return Gr(t) ? t.slice(6, t.length) : ""; + }, + Jr = function (t) { + return null == t || !1 === t; + }; + function Kr(t) { + var e = t.data, + n = t, + r = t; + while (i(r.componentInstance)) + (r = r.componentInstance._vnode), + r && r.data && (e = Xr(r.data, e)); + while (i((n = n.parent))) + n && n.data && (e = Xr(e, n.data)); + return Yr(e.staticClass, e.class); + } + function Xr(t, e) { + return { + staticClass: Qr(t.staticClass, e.staticClass), + class: i(t.class) ? [t.class, e.class] : e.class, + }; + } + function Yr(t, e) { + return i(t) || i(e) ? Qr(t, ti(e)) : ""; + } + function Qr(t, e) { + return t ? (e ? t + " " + e : t) : e || ""; + } + function ti(t) { + return Array.isArray(t) + ? ei(t) + : u(t) + ? ni(t) + : "string" === typeof t + ? t + : ""; + } + function ei(t) { + for (var e, n = "", r = 0, o = t.length; r < o; r++) + i((e = ti(t[r]))) && + "" !== e && + (n && (n += " "), (n += e)); + return n; + } + function ni(t) { + var e = ""; + for (var n in t) t[n] && (e && (e += " "), (e += n)); + return e; + } + var ri = { + svg: "http://www.w3.org/2000/svg", + math: "http://www.w3.org/1998/Math/MathML", + }, + ii = y( + "html,body,base,head,link,meta,style,title,address,article,aside,footer,header,h1,h2,h3,h4,h5,h6,hgroup,nav,section,div,dd,dl,dt,figcaption,figure,picture,hr,img,li,main,ol,p,pre,ul,a,b,abbr,bdi,bdo,br,cite,code,data,dfn,em,i,kbd,mark,q,rp,rt,rtc,ruby,s,samp,small,span,strong,sub,sup,time,u,var,wbr,area,audio,map,track,video,embed,object,param,source,canvas,script,noscript,del,ins,caption,col,colgroup,table,thead,tbody,td,th,tr,button,datalist,fieldset,form,input,label,legend,meter,optgroup,option,output,progress,select,textarea,details,dialog,menu,menuitem,summary,content,element,shadow,template,blockquote,iframe,tfoot" + ), + oi = y( + "svg,animate,circle,clippath,cursor,defs,desc,ellipse,filter,font-face,foreignobject,g,glyph,image,line,marker,mask,missing-glyph,path,pattern,polygon,polyline,rect,switch,symbol,text,textpath,tspan,use,view", + !0 + ), + ai = function (t) { + return ii(t) || oi(t); + }; + function si(t) { + return oi(t) ? "svg" : "math" === t ? "math" : void 0; + } + var ui = Object.create(null); + function ci(t) { + if (!K) return !0; + if (ai(t)) return !1; + if (((t = t.toLowerCase()), null != ui[t])) return ui[t]; + var e = document.createElement(t); + return t.indexOf("-") > -1 + ? (ui[t] = + e.constructor === window.HTMLUnknownElement || + e.constructor === window.HTMLElement) + : (ui[t] = /HTMLUnknownElement/.test(e.toString())); + } + var fi = y("text,number,password,search,email,tel,url"); + function li(t) { + if ("string" === typeof t) { + var e = document.querySelector(t); + return e || document.createElement("div"); + } + return t; + } + function di(t, e) { + var n = document.createElement(t); + return ( + "select" !== t || + (e.data && + e.data.attrs && + void 0 !== e.data.attrs.multiple && + n.setAttribute("multiple", "multiple")), + n + ); + } + function pi(t, e) { + return document.createElementNS(ri[t], e); + } + function hi(t) { + return document.createTextNode(t); + } + function vi(t) { + return document.createComment(t); + } + function yi(t, e, n) { + t.insertBefore(e, n); + } + function mi(t, e) { + t.removeChild(e); + } + function gi(t, e) { + t.appendChild(e); + } + function bi(t) { + return t.parentNode; + } + function _i(t) { + return t.nextSibling; + } + function wi(t) { + return t.tagName; + } + function xi(t, e) { + t.textContent = e; + } + function ki(t, e) { + t.setAttribute(e, ""); + } + var Si = Object.freeze({ + createElement: di, + createElementNS: pi, + createTextNode: hi, + createComment: vi, + insertBefore: yi, + removeChild: mi, + appendChild: gi, + parentNode: bi, + nextSibling: _i, + tagName: wi, + setTextContent: xi, + setStyleScope: ki, + }), + Ei = { + create: function (t, e) { + Oi(e); + }, + update: function (t, e) { + t.data.ref !== e.data.ref && (Oi(t, !0), Oi(e)); + }, + destroy: function (t) { + Oi(t, !0); + }, + }; + function Oi(t, e) { + var n = t.data.ref; + if (i(n)) { + var r = t.context, + o = t.componentInstance || t.elm, + a = r.$refs; + e + ? Array.isArray(a[n]) + ? g(a[n], o) + : a[n] === o && (a[n] = void 0) + : t.data.refInFor + ? Array.isArray(a[n]) + ? a[n].indexOf(o) < 0 && a[n].push(o) + : (a[n] = [o]) + : (a[n] = o); + } + } + var Ti = new bt("", {}, []), + Ai = ["create", "activate", "update", "remove", "destroy"]; + function Ci(t, e) { + return ( + t.key === e.key && + t.asyncFactory === e.asyncFactory && + ((t.tag === e.tag && + t.isComment === e.isComment && + i(t.data) === i(e.data) && + ji(t, e)) || + (o(t.isAsyncPlaceholder) && + r(e.asyncFactory.error))) + ); + } + function ji(t, e) { + if ("input" !== t.tag) return !0; + var n, + r = i((n = t.data)) && i((n = n.attrs)) && n.type, + o = i((n = e.data)) && i((n = n.attrs)) && n.type; + return r === o || (fi(r) && fi(o)); + } + function Ii(t, e, n) { + var r, + o, + a = {}; + for (r = e; r <= n; ++r) (o = t[r].key), i(o) && (a[o] = r); + return a; + } + function Mi(t) { + var e, + n, + a = {}, + u = t.modules, + c = t.nodeOps; + for (e = 0; e < Ai.length; ++e) + for (a[Ai[e]] = [], n = 0; n < u.length; ++n) + i(u[n][Ai[e]]) && a[Ai[e]].push(u[n][Ai[e]]); + function f(t) { + return new bt( + c.tagName(t).toLowerCase(), + {}, + [], + void 0, + t + ); + } + function l(t, e) { + function n() { + 0 === --n.listeners && d(t); + } + return (n.listeners = e), n; + } + function d(t) { + var e = c.parentNode(t); + i(e) && c.removeChild(e, t); + } + function p(t, e, n, r, a, s, u) { + if ( + (i(t.elm) && i(s) && (t = s[u] = kt(t)), + (t.isRootInsert = !a), + !h(t, e, n, r)) + ) { + var f = t.data, + l = t.children, + d = t.tag; + i(d) + ? ((t.elm = t.ns + ? c.createElementNS(t.ns, d) + : c.createElement(d, t)), + x(t), + b(t, l, e), + i(f) && w(t, e), + g(n, t.elm, r)) + : o(t.isComment) + ? ((t.elm = c.createComment(t.text)), + g(n, t.elm, r)) + : ((t.elm = c.createTextNode(t.text)), + g(n, t.elm, r)); + } + } + function h(t, e, n, r) { + var a = t.data; + if (i(a)) { + var s = i(t.componentInstance) && a.keepAlive; + if ( + (i((a = a.hook)) && i((a = a.init)) && a(t, !1), + i(t.componentInstance)) + ) + return ( + v(t, e), + g(n, t.elm, r), + o(s) && m(t, e, n, r), + !0 + ); + } + } + function v(t, e) { + i(t.data.pendingInsert) && + (e.push.apply(e, t.data.pendingInsert), + (t.data.pendingInsert = null)), + (t.elm = t.componentInstance.$el), + _(t) ? (w(t, e), x(t)) : (Oi(t), e.push(t)); + } + function m(t, e, n, r) { + var o, + s = t; + while (s.componentInstance) + if ( + ((s = s.componentInstance._vnode), + i((o = s.data)) && i((o = o.transition))) + ) { + for (o = 0; o < a.activate.length; ++o) + a.activate[o](Ti, s); + e.push(s); + break; + } + g(n, t.elm, r); + } + function g(t, e, n) { + i(t) && + (i(n) + ? c.parentNode(n) === t && + c.insertBefore(t, e, n) + : c.appendChild(t, e)); + } + function b(t, e, n) { + if (Array.isArray(e)) { + 0; + for (var r = 0; r < e.length; ++r) + p(e[r], n, t.elm, null, !0, e, r); + } else + s(t.text) && + c.appendChild( + t.elm, + c.createTextNode(String(t.text)) + ); + } + function _(t) { + while (t.componentInstance) + t = t.componentInstance._vnode; + return i(t.tag); + } + function w(t, n) { + for (var r = 0; r < a.create.length; ++r) + a.create[r](Ti, t); + (e = t.data.hook), + i(e) && + (i(e.create) && e.create(Ti, t), + i(e.insert) && n.push(t)); + } + function x(t) { + var e; + if (i((e = t.fnScopeId))) c.setStyleScope(t.elm, e); + else { + var n = t; + while (n) + i((e = n.context)) && + i((e = e.$options._scopeId)) && + c.setStyleScope(t.elm, e), + (n = n.parent); + } + i((e = In)) && + e !== t.context && + e !== t.fnContext && + i((e = e.$options._scopeId)) && + c.setStyleScope(t.elm, e); + } + function k(t, e, n, r, i, o) { + for (; r <= i; ++r) p(n[r], o, t, e, !1, n, r); + } + function S(t) { + var e, + n, + r = t.data; + if (i(r)) + for ( + i((e = r.hook)) && i((e = e.destroy)) && e(t), + e = 0; + e < a.destroy.length; + ++e + ) + a.destroy[e](t); + if (i((e = t.children))) + for (n = 0; n < t.children.length; ++n) + S(t.children[n]); + } + function E(t, e, n) { + for (; e <= n; ++e) { + var r = t[e]; + i(r) && (i(r.tag) ? (O(r), S(r)) : d(r.elm)); + } + } + function O(t, e) { + if (i(e) || i(t.data)) { + var n, + r = a.remove.length + 1; + for ( + i(e) ? (e.listeners += r) : (e = l(t.elm, r)), + i((n = t.componentInstance)) && + i((n = n._vnode)) && + i(n.data) && + O(n, e), + n = 0; + n < a.remove.length; + ++n + ) + a.remove[n](t, e); + i((n = t.data.hook)) && i((n = n.remove)) + ? n(t, e) + : e(); + } else d(t.elm); + } + function T(t, e, n, o, a) { + var s, + u, + f, + l, + d = 0, + h = 0, + v = e.length - 1, + y = e[0], + m = e[v], + g = n.length - 1, + b = n[0], + _ = n[g], + w = !a; + while (d <= v && h <= g) + r(y) + ? (y = e[++d]) + : r(m) + ? (m = e[--v]) + : Ci(y, b) + ? (C(y, b, o, n, h), (y = e[++d]), (b = n[++h])) + : Ci(m, _) + ? (C(m, _, o, n, g), (m = e[--v]), (_ = n[--g])) + : Ci(y, _) + ? (C(y, _, o, n, g), + w && + c.insertBefore( + t, + y.elm, + c.nextSibling(m.elm) + ), + (y = e[++d]), + (_ = n[--g])) + : Ci(m, b) + ? (C(m, b, o, n, h), + w && c.insertBefore(t, m.elm, y.elm), + (m = e[--v]), + (b = n[++h])) + : (r(s) && (s = Ii(e, d, v)), + (u = i(b.key) ? s[b.key] : A(b, e, d, v)), + r(u) + ? p(b, o, t, y.elm, !1, n, h) + : ((f = e[u]), + Ci(f, b) + ? (C(f, b, o, n, h), + (e[u] = void 0), + w && + c.insertBefore( + t, + f.elm, + y.elm + )) + : p(b, o, t, y.elm, !1, n, h)), + (b = n[++h])); + d > v + ? ((l = r(n[g + 1]) ? null : n[g + 1].elm), + k(t, l, n, h, g, o)) + : h > g && E(e, d, v); + } + function A(t, e, n, r) { + for (var o = n; o < r; o++) { + var a = e[o]; + if (i(a) && Ci(t, a)) return o; + } + } + function C(t, e, n, s, u, f) { + if (t !== e) { + i(e.elm) && i(s) && (e = s[u] = kt(e)); + var l = (e.elm = t.elm); + if (o(t.isAsyncPlaceholder)) + i(e.asyncFactory.resolved) + ? M(t.elm, e, n) + : (e.isAsyncPlaceholder = !0); + else if ( + o(e.isStatic) && + o(t.isStatic) && + e.key === t.key && + (o(e.isCloned) || o(e.isOnce)) + ) + e.componentInstance = t.componentInstance; + else { + var d, + p = e.data; + i(p) && + i((d = p.hook)) && + i((d = d.prepatch)) && + d(t, e); + var h = t.children, + v = e.children; + if (i(p) && _(e)) { + for (d = 0; d < a.update.length; ++d) + a.update[d](t, e); + i((d = p.hook)) && + i((d = d.update)) && + d(t, e); + } + r(e.text) + ? i(h) && i(v) + ? h !== v && T(l, h, v, n, f) + : i(v) + ? (i(t.text) && c.setTextContent(l, ""), + k(l, null, v, 0, v.length - 1, n)) + : i(h) + ? E(h, 0, h.length - 1) + : i(t.text) && c.setTextContent(l, "") + : t.text !== e.text && + c.setTextContent(l, e.text), + i(p) && + i((d = p.hook)) && + i((d = d.postpatch)) && + d(t, e); + } + } + } + function j(t, e, n) { + if (o(n) && i(t.parent)) + t.parent.data.pendingInsert = e; + else + for (var r = 0; r < e.length; ++r) + e[r].data.hook.insert(e[r]); + } + var I = y("attrs,class,staticClass,staticStyle,key"); + function M(t, e, n, r) { + var a, + s = e.tag, + u = e.data, + c = e.children; + if ( + ((r = r || (u && u.pre)), + (e.elm = t), + o(e.isComment) && i(e.asyncFactory)) + ) + return (e.isAsyncPlaceholder = !0), !0; + if ( + i(u) && + (i((a = u.hook)) && i((a = a.init)) && a(e, !0), + i((a = e.componentInstance))) + ) + return v(e, n), !0; + if (i(s)) { + if (i(c)) + if (t.hasChildNodes()) + if ( + i((a = u)) && + i((a = a.domProps)) && + i((a = a.innerHTML)) + ) { + if (a !== t.innerHTML) return !1; + } else { + for ( + var f = !0, l = t.firstChild, d = 0; + d < c.length; + d++ + ) { + if (!l || !M(l, c[d], n, r)) { + f = !1; + break; + } + l = l.nextSibling; + } + if (!f || l) return !1; + } + else b(e, c, n); + if (i(u)) { + var p = !1; + for (var h in u) + if (!I(h)) { + (p = !0), w(e, n); + break; + } + !p && u["class"] && me(u["class"]); + } + } else t.data !== e.text && (t.data = e.text); + return !0; + } + return function (t, e, n, s) { + if (!r(e)) { + var u = !1, + l = []; + if (r(t)) (u = !0), p(e, l); + else { + var d = i(t.nodeType); + if (!d && Ci(t, e)) C(t, e, l, null, null, s); + else { + if (d) { + if ( + (1 === t.nodeType && + t.hasAttribute(F) && + (t.removeAttribute(F), + (n = !0)), + o(n) && M(t, e, l)) + ) + return j(e, l, !0), t; + t = f(t); + } + var h = t.elm, + v = c.parentNode(h); + if ( + (p( + e, + l, + h._leaveCb ? null : v, + c.nextSibling(h) + ), + i(e.parent)) + ) { + var y = e.parent, + m = _(e); + while (y) { + for ( + var g = 0; + g < a.destroy.length; + ++g + ) + a.destroy[g](y); + if (((y.elm = e.elm), m)) { + for ( + var b = 0; + b < a.create.length; + ++b + ) + a.create[b](Ti, y); + var w = y.data.hook.insert; + if (w.merged) + for ( + var x = 1; + x < w.fns.length; + x++ + ) + w.fns[x](); + } else Oi(y); + y = y.parent; + } + } + i(v) ? E([t], 0, 0) : i(t.tag) && S(t); + } + } + return j(e, l, u), e.elm; + } + i(t) && S(t); + }; + } + var Li = { + create: Pi, + update: Pi, + destroy: function (t) { + Pi(t, Ti); + }, + }; + function Pi(t, e) { + (t.data.directives || e.data.directives) && Ni(t, e); + } + function Ni(t, e) { + var n, + r, + i, + o = t === Ti, + a = e === Ti, + s = $i(t.data.directives, t.context), + u = $i(e.data.directives, e.context), + c = [], + f = []; + for (n in u) + (r = s[n]), + (i = u[n]), + r + ? ((i.oldValue = r.value), + (i.oldArg = r.arg), + Fi(i, "update", e, t), + i.def && i.def.componentUpdated && f.push(i)) + : (Fi(i, "bind", e, t), + i.def && i.def.inserted && c.push(i)); + if (c.length) { + var l = function () { + for (var n = 0; n < c.length; n++) + Fi(c[n], "inserted", e, t); + }; + o ? xe(e, "insert", l) : l(); + } + if ( + (f.length && + xe(e, "postpatch", function () { + for (var n = 0; n < f.length; n++) + Fi(f[n], "componentUpdated", e, t); + }), + !o) + ) + for (n in s) u[n] || Fi(s[n], "unbind", t, t, a); + } + var Ri = Object.create(null); + function $i(t, e) { + var n, + r, + i = Object.create(null); + if (!t) return i; + for (n = 0; n < t.length; n++) + (r = t[n]), + r.modifiers || (r.modifiers = Ri), + (i[Di(r)] = r), + (r.def = Jt(e.$options, "directives", r.name, !0)); + return i; + } + function Di(t) { + return ( + t.rawName || + t.name + "." + Object.keys(t.modifiers || {}).join(".") + ); + } + function Fi(t, e, n, r, i) { + var o = t.def && t.def[e]; + if (o) + try { + o(n.elm, t, n, r, i); + } catch (Sa) { + ne( + Sa, + n.context, + "directive " + t.name + " " + e + " hook" + ); + } + } + var zi = [Ei, Li]; + function Ui(t, e) { + var n = e.componentOptions; + if ( + (!i(n) || !1 !== n.Ctor.options.inheritAttrs) && + (!r(t.data.attrs) || !r(e.data.attrs)) + ) { + var o, + a, + s, + u = e.elm, + c = t.data.attrs || {}, + f = e.data.attrs || {}; + for (o in (i(f.__ob__) && (f = e.data.attrs = I({}, f)), + f)) + (a = f[o]), + (s = c[o]), + s !== a && Bi(u, o, a, e.data.pre); + for (o in ((tt || nt) && + f.value !== c.value && + Bi(u, "value", f.value), + c)) + r(f[o]) && + (Gr(o) + ? u.removeAttributeNS(Wr, Zr(o)) + : Br(o) || u.removeAttribute(o)); + } + } + function Bi(t, e, n, r) { + r || t.tagName.indexOf("-") > -1 + ? Vi(t, e, n) + : qr(e) + ? Jr(n) + ? t.removeAttribute(e) + : ((n = + "allowfullscreen" === e && + "EMBED" === t.tagName + ? "true" + : e), + t.setAttribute(e, n)) + : Br(e) + ? t.setAttribute(e, Hr(e, n)) + : Gr(e) + ? Jr(n) + ? t.removeAttributeNS(Wr, Zr(e)) + : t.setAttributeNS(Wr, e, n) + : Vi(t, e, n); + } + function Vi(t, e, n) { + if (Jr(n)) t.removeAttribute(e); + else { + if ( + tt && + !et && + "TEXTAREA" === t.tagName && + "placeholder" === e && + "" !== n && + !t.__ieph + ) { + var r = function (e) { + e.stopImmediatePropagation(), + t.removeEventListener("input", r); + }; + t.addEventListener("input", r), (t.__ieph = !0); + } + t.setAttribute(e, n); + } + } + var Hi = { create: Ui, update: Ui }; + function qi(t, e) { + var n = e.elm, + o = e.data, + a = t.data; + if ( + !( + r(o.staticClass) && + r(o.class) && + (r(a) || (r(a.staticClass) && r(a.class))) + ) + ) { + var s = Kr(e), + u = n._transitionClasses; + i(u) && (s = Qr(s, ti(u))), + s !== n._prevClass && + (n.setAttribute("class", s), + (n._prevClass = s)); + } + } + var Wi, + Gi = { create: qi, update: qi }, + Zi = "__r", + Ji = "__c"; + function Ki(t) { + if (i(t[Zi])) { + var e = tt ? "change" : "input"; + (t[e] = [].concat(t[Zi], t[e] || [])), delete t[Zi]; + } + i(t[Ji]) && + ((t.change = [].concat(t[Ji], t.change || [])), + delete t[Ji]); + } + function Xi(t, e, n) { + var r = Wi; + return function i() { + var o = e.apply(null, arguments); + null !== o && to(t, i, n, r); + }; + } + var Yi = se && !(it && Number(it[1]) <= 53); + function Qi(t, e, n, r) { + if (Yi) { + var i = Zn, + o = e; + e = o._wrapper = function (t) { + if ( + t.target === t.currentTarget || + t.timeStamp >= i || + t.timeStamp <= 0 || + t.target.ownerDocument !== document + ) + return o.apply(this, arguments); + }; + } + Wi.addEventListener( + t, + e, + at ? { capture: n, passive: r } : n + ); + } + function to(t, e, n, r) { + (r || Wi).removeEventListener(t, e._wrapper || e, n); + } + function eo(t, e) { + if (!r(t.data.on) || !r(e.data.on)) { + var n = e.data.on || {}, + i = t.data.on || {}; + (Wi = e.elm), + Ki(n), + we(n, i, Qi, to, Xi, e.context), + (Wi = void 0); + } + } + var no, + ro = { create: eo, update: eo }; + function io(t, e) { + if (!r(t.data.domProps) || !r(e.data.domProps)) { + var n, + o, + a = e.elm, + s = t.data.domProps || {}, + u = e.data.domProps || {}; + for (n in (i(u.__ob__) && + (u = e.data.domProps = I({}, u)), + s)) + n in u || (a[n] = ""); + for (n in u) { + if ( + ((o = u[n]), + "textContent" === n || "innerHTML" === n) + ) { + if ( + (e.children && (e.children.length = 0), + o === s[n]) + ) + continue; + 1 === a.childNodes.length && + a.removeChild(a.childNodes[0]); + } + if ("value" === n && "PROGRESS" !== a.tagName) { + a._value = o; + var c = r(o) ? "" : String(o); + oo(a, c) && (a.value = c); + } else if ( + "innerHTML" === n && + oi(a.tagName) && + r(a.innerHTML) + ) { + (no = no || document.createElement("div")), + (no.innerHTML = "" + o + ""); + var f = no.firstChild; + while (a.firstChild) + a.removeChild(a.firstChild); + while (f.firstChild) + a.appendChild(f.firstChild); + } else if (o !== s[n]) + try { + a[n] = o; + } catch (Sa) {} + } + } + } + function oo(t, e) { + return ( + !t.composing && + ("OPTION" === t.tagName || ao(t, e) || so(t, e)) + ); + } + function ao(t, e) { + var n = !0; + try { + n = document.activeElement !== t; + } catch (Sa) {} + return n && t.value !== e; + } + function so(t, e) { + var n = t.value, + r = t._vModifiers; + if (i(r)) { + if (r.number) return v(n) !== v(e); + if (r.trim) return n.trim() !== e.trim(); + } + return n !== e; + } + var uo = { create: io, update: io }, + co = w(function (t) { + var e = {}, + n = /;(?![^(]*\))/g, + r = /:(.+)/; + return ( + t.split(n).forEach(function (t) { + if (t) { + var n = t.split(r); + n.length > 1 && + (e[n[0].trim()] = n[1].trim()); + } + }), + e + ); + }); + function fo(t) { + var e = lo(t.style); + return t.staticStyle ? I(t.staticStyle, e) : e; + } + function lo(t) { + return Array.isArray(t) + ? M(t) + : "string" === typeof t + ? co(t) + : t; + } + function po(t, e) { + var n, + r = {}; + if (e) { + var i = t; + while (i.componentInstance) + (i = i.componentInstance._vnode), + i && i.data && (n = fo(i.data)) && I(r, n); + } + (n = fo(t.data)) && I(r, n); + var o = t; + while ((o = o.parent)) + o.data && (n = fo(o.data)) && I(r, n); + return r; + } + var ho, + vo = /^--/, + yo = /\s*!important$/, + mo = function (t, e, n) { + if (vo.test(e)) t.style.setProperty(e, n); + else if (yo.test(n)) + t.style.setProperty( + O(e), + n.replace(yo, ""), + "important" + ); + else { + var r = bo(e); + if (Array.isArray(n)) + for (var i = 0, o = n.length; i < o; i++) + t.style[r] = n[i]; + else t.style[r] = n; + } + }, + go = ["Webkit", "Moz", "ms"], + bo = w(function (t) { + if ( + ((ho = ho || document.createElement("div").style), + (t = k(t)), + "filter" !== t && t in ho) + ) + return t; + for ( + var e = t.charAt(0).toUpperCase() + t.slice(1), + n = 0; + n < go.length; + n++ + ) { + var r = go[n] + e; + if (r in ho) return r; + } + }); + function _o(t, e) { + var n = e.data, + o = t.data; + if ( + !( + r(n.staticStyle) && + r(n.style) && + r(o.staticStyle) && + r(o.style) + ) + ) { + var a, + s, + u = e.elm, + c = o.staticStyle, + f = o.normalizedStyle || o.style || {}, + l = c || f, + d = lo(e.data.style) || {}; + e.data.normalizedStyle = i(d.__ob__) ? I({}, d) : d; + var p = po(e, !0); + for (s in l) r(p[s]) && mo(u, s, ""); + for (s in p) + (a = p[s]), + a !== l[s] && mo(u, s, null == a ? "" : a); + } + } + var wo = { create: _o, update: _o }, + xo = /\s+/; + function ko(t, e) { + if (e && (e = e.trim())) + if (t.classList) + e.indexOf(" ") > -1 + ? e.split(xo).forEach(function (e) { + return t.classList.add(e); + }) + : t.classList.add(e); + else { + var n = " " + (t.getAttribute("class") || "") + " "; + n.indexOf(" " + e + " ") < 0 && + t.setAttribute("class", (n + e).trim()); + } + } + function So(t, e) { + if (e && (e = e.trim())) + if (t.classList) + e.indexOf(" ") > -1 + ? e.split(xo).forEach(function (e) { + return t.classList.remove(e); + }) + : t.classList.remove(e), + t.classList.length || + t.removeAttribute("class"); + else { + var n = " " + (t.getAttribute("class") || "") + " ", + r = " " + e + " "; + while (n.indexOf(r) >= 0) n = n.replace(r, " "); + (n = n.trim()), + n + ? t.setAttribute("class", n) + : t.removeAttribute("class"); + } + } + function Eo(t) { + if (t) { + if ("object" === typeof t) { + var e = {}; + return ( + !1 !== t.css && I(e, Oo(t.name || "v")), + I(e, t), + e + ); + } + return "string" === typeof t ? Oo(t) : void 0; + } + } + var Oo = w(function (t) { + return { + enterClass: t + "-enter", + enterToClass: t + "-enter-to", + enterActiveClass: t + "-enter-active", + leaveClass: t + "-leave", + leaveToClass: t + "-leave-to", + leaveActiveClass: t + "-leave-active", + }; + }), + To = K && !et, + Ao = "transition", + Co = "animation", + jo = "transition", + Io = "transitionend", + Mo = "animation", + Lo = "animationend"; + To && + (void 0 === window.ontransitionend && + void 0 !== window.onwebkittransitionend && + ((jo = "WebkitTransition"), + (Io = "webkitTransitionEnd")), + void 0 === window.onanimationend && + void 0 !== window.onwebkitanimationend && + ((Mo = "WebkitAnimation"), + (Lo = "webkitAnimationEnd"))); + var Po = K + ? window.requestAnimationFrame + ? window.requestAnimationFrame.bind(window) + : setTimeout + : function (t) { + return t(); + }; + function No(t) { + Po(function () { + Po(t); + }); + } + function Ro(t, e) { + var n = t._transitionClasses || (t._transitionClasses = []); + n.indexOf(e) < 0 && (n.push(e), ko(t, e)); + } + function $o(t, e) { + t._transitionClasses && g(t._transitionClasses, e), + So(t, e); + } + function Do(t, e, n) { + var r = zo(t, e), + i = r.type, + o = r.timeout, + a = r.propCount; + if (!i) return n(); + var s = i === Ao ? Io : Lo, + u = 0, + c = function () { + t.removeEventListener(s, f), n(); + }, + f = function (e) { + e.target === t && ++u >= a && c(); + }; + setTimeout(function () { + u < a && c(); + }, o + 1), + t.addEventListener(s, f); + } + var Fo = /\b(transform|all)(,|$)/; + function zo(t, e) { + var n, + r = window.getComputedStyle(t), + i = (r[jo + "Delay"] || "").split(", "), + o = (r[jo + "Duration"] || "").split(", "), + a = Uo(i, o), + s = (r[Mo + "Delay"] || "").split(", "), + u = (r[Mo + "Duration"] || "").split(", "), + c = Uo(s, u), + f = 0, + l = 0; + e === Ao + ? a > 0 && ((n = Ao), (f = a), (l = o.length)) + : e === Co + ? c > 0 && ((n = Co), (f = c), (l = u.length)) + : ((f = Math.max(a, c)), + (n = f > 0 ? (a > c ? Ao : Co) : null), + (l = n ? (n === Ao ? o.length : u.length) : 0)); + var d = n === Ao && Fo.test(r[jo + "Property"]); + return { + type: n, + timeout: f, + propCount: l, + hasTransform: d, + }; + } + function Uo(t, e) { + while (t.length < e.length) t = t.concat(t); + return Math.max.apply( + null, + e.map(function (e, n) { + return Bo(e) + Bo(t[n]); + }) + ); + } + function Bo(t) { + return 1e3 * Number(t.slice(0, -1).replace(",", ".")); + } + function Vo(t, e) { + var n = t.elm; + i(n._leaveCb) && + ((n._leaveCb.cancelled = !0), n._leaveCb()); + var o = Eo(t.data.transition); + if (!r(o) && !i(n._enterCb) && 1 === n.nodeType) { + var a = o.css, + s = o.type, + c = o.enterClass, + f = o.enterToClass, + l = o.enterActiveClass, + d = o.appearClass, + p = o.appearToClass, + h = o.appearActiveClass, + y = o.beforeEnter, + m = o.enter, + g = o.afterEnter, + b = o.enterCancelled, + _ = o.beforeAppear, + w = o.appear, + x = o.afterAppear, + k = o.appearCancelled, + S = o.duration, + E = In, + O = In.$vnode; + while (O && O.parent) (E = O.context), (O = O.parent); + var T = !E._isMounted || !t.isRootInsert; + if (!T || w || "" === w) { + var A = T && d ? d : c, + C = T && h ? h : l, + j = T && p ? p : f, + I = (T && _) || y, + M = T && "function" === typeof w ? w : m, + L = (T && x) || g, + P = (T && k) || b, + N = v(u(S) ? S.enter : S); + 0; + var R = !1 !== a && !et, + $ = Wo(M), + F = (n._enterCb = D(function () { + R && ($o(n, j), $o(n, C)), + F.cancelled + ? (R && $o(n, A), P && P(n)) + : L && L(n), + (n._enterCb = null); + })); + t.data.show || + xe(t, "insert", function () { + var e = n.parentNode, + r = + e && + e._pending && + e._pending[t.key]; + r && + r.tag === t.tag && + r.elm._leaveCb && + r.elm._leaveCb(), + M && M(n, F); + }), + I && I(n), + R && + (Ro(n, A), + Ro(n, C), + No(function () { + $o(n, A), + F.cancelled || + (Ro(n, j), + $ || + (qo(N) + ? setTimeout(F, N) + : Do(n, s, F))); + })), + t.data.show && (e && e(), M && M(n, F)), + R || $ || F(); + } + } + } + function Ho(t, e) { + var n = t.elm; + i(n._enterCb) && + ((n._enterCb.cancelled = !0), n._enterCb()); + var o = Eo(t.data.transition); + if (r(o) || 1 !== n.nodeType) return e(); + if (!i(n._leaveCb)) { + var a = o.css, + s = o.type, + c = o.leaveClass, + f = o.leaveToClass, + l = o.leaveActiveClass, + d = o.beforeLeave, + p = o.leave, + h = o.afterLeave, + y = o.leaveCancelled, + m = o.delayLeave, + g = o.duration, + b = !1 !== a && !et, + _ = Wo(p), + w = v(u(g) ? g.leave : g); + 0; + var x = (n._leaveCb = D(function () { + n.parentNode && + n.parentNode._pending && + (n.parentNode._pending[t.key] = null), + b && ($o(n, f), $o(n, l)), + x.cancelled + ? (b && $o(n, c), y && y(n)) + : (e(), h && h(n)), + (n._leaveCb = null); + })); + m ? m(k) : k(); + } + function k() { + x.cancelled || + (!t.data.show && + n.parentNode && + ((n.parentNode._pending || + (n.parentNode._pending = {}))[t.key] = t), + d && d(n), + b && + (Ro(n, c), + Ro(n, l), + No(function () { + $o(n, c), + x.cancelled || + (Ro(n, f), + _ || + (qo(w) + ? setTimeout(x, w) + : Do(n, s, x))); + })), + p && p(n, x), + b || _ || x()); + } + } + function qo(t) { + return "number" === typeof t && !isNaN(t); + } + function Wo(t) { + if (r(t)) return !1; + var e = t.fns; + return i(e) + ? Wo(Array.isArray(e) ? e[0] : e) + : (t._length || t.length) > 1; + } + function Go(t, e) { + !0 !== e.data.show && Vo(e); + } + var Zo = K + ? { + create: Go, + activate: Go, + remove: function (t, e) { + !0 !== t.data.show ? Ho(t, e) : e(); + }, + } + : {}, + Jo = [Hi, Gi, ro, uo, wo, Zo], + Ko = Jo.concat(zi), + Xo = Mi({ nodeOps: Si, modules: Ko }); + et && + document.addEventListener("selectionchange", function () { + var t = document.activeElement; + t && t.vmodel && oa(t, "input"); + }); + var Yo = { + inserted: function (t, e, n, r) { + "select" === n.tag + ? (r.elm && !r.elm._vOptions + ? xe(n, "postpatch", function () { + Yo.componentUpdated(t, e, n); + }) + : Qo(t, e, n.context), + (t._vOptions = [].map.call(t.options, na))) + : ("textarea" === n.tag || fi(t.type)) && + ((t._vModifiers = e.modifiers), + e.modifiers.lazy || + (t.addEventListener("compositionstart", ra), + t.addEventListener("compositionend", ia), + t.addEventListener("change", ia), + et && (t.vmodel = !0))); + }, + componentUpdated: function (t, e, n) { + if ("select" === n.tag) { + Qo(t, e, n.context); + var r = t._vOptions, + i = (t._vOptions = [].map.call(t.options, na)); + if ( + i.some(function (t, e) { + return !R(t, r[e]); + }) + ) { + var o = t.multiple + ? e.value.some(function (t) { + return ea(t, i); + }) + : e.value !== e.oldValue && ea(e.value, i); + o && oa(t, "change"); + } + } + }, + }; + function Qo(t, e, n) { + ta(t, e, n), + (tt || nt) && + setTimeout(function () { + ta(t, e, n); + }, 0); + } + function ta(t, e, n) { + var r = e.value, + i = t.multiple; + if (!i || Array.isArray(r)) { + for (var o, a, s = 0, u = t.options.length; s < u; s++) + if (((a = t.options[s]), i)) + (o = $(r, na(a)) > -1), + a.selected !== o && (a.selected = o); + else if (R(na(a), r)) + return void ( + t.selectedIndex !== s && + (t.selectedIndex = s) + ); + i || (t.selectedIndex = -1); + } + } + function ea(t, e) { + return e.every(function (e) { + return !R(e, t); + }); + } + function na(t) { + return "_value" in t ? t._value : t.value; + } + function ra(t) { + t.target.composing = !0; + } + function ia(t) { + t.target.composing && + ((t.target.composing = !1), oa(t.target, "input")); + } + function oa(t, e) { + var n = document.createEvent("HTMLEvents"); + n.initEvent(e, !0, !0), t.dispatchEvent(n); + } + function aa(t) { + return !t.componentInstance || (t.data && t.data.transition) + ? t + : aa(t.componentInstance._vnode); + } + var sa = { + bind: function (t, e, n) { + var r = e.value; + n = aa(n); + var i = n.data && n.data.transition, + o = (t.__vOriginalDisplay = + "none" === t.style.display + ? "" + : t.style.display); + r && i + ? ((n.data.show = !0), + Vo(n, function () { + t.style.display = o; + })) + : (t.style.display = r ? o : "none"); + }, + update: function (t, e, n) { + var r = e.value, + i = e.oldValue; + if (!r !== !i) { + n = aa(n); + var o = n.data && n.data.transition; + o + ? ((n.data.show = !0), + r + ? Vo(n, function () { + t.style.display = + t.__vOriginalDisplay; + }) + : Ho(n, function () { + t.style.display = "none"; + })) + : (t.style.display = r + ? t.__vOriginalDisplay + : "none"); + } + }, + unbind: function (t, e, n, r, i) { + i || (t.style.display = t.__vOriginalDisplay); + }, + }, + ua = { model: Yo, show: sa }, + ca = { + name: String, + appear: Boolean, + css: Boolean, + mode: String, + type: String, + enterClass: String, + leaveClass: String, + enterToClass: String, + leaveToClass: String, + enterActiveClass: String, + leaveActiveClass: String, + appearClass: String, + appearActiveClass: String, + appearToClass: String, + duration: [Number, String, Object], + }; + function fa(t) { + var e = t && t.componentOptions; + return e && e.Ctor.options.abstract + ? fa(Sn(e.children)) + : t; + } + function la(t) { + var e = {}, + n = t.$options; + for (var r in n.propsData) e[r] = t[r]; + var i = n._parentListeners; + for (var o in i) e[k(o)] = i[o]; + return e; + } + function da(t, e) { + if (/\d-keep-alive$/.test(e.tag)) + return t("keep-alive", { + props: e.componentOptions.propsData, + }); + } + function pa(t) { + while ((t = t.parent)) if (t.data.transition) return !0; + } + function ha(t, e) { + return e.key === t.key && e.tag === t.tag; + } + var va = function (t) { + return t.tag || Pe(t); + }, + ya = function (t) { + return "show" === t.name; + }, + ma = { + name: "transition", + props: ca, + abstract: !0, + render: function (t) { + var e = this, + n = this.$slots.default; + if (n && ((n = n.filter(va)), n.length)) { + 0; + var r = this.mode; + 0; + var i = n[0]; + if (pa(this.$vnode)) return i; + var o = fa(i); + if (!o) return i; + if (this._leaving) return da(t, i); + var a = "__transition-" + this._uid + "-"; + o.key = + null == o.key + ? o.isComment + ? a + "comment" + : a + o.tag + : s(o.key) + ? 0 === String(o.key).indexOf(a) + ? o.key + : a + o.key + : o.key; + var u = ((o.data || (o.data = {})).transition = + la(this)), + c = this._vnode, + f = fa(c); + if ( + (o.data.directives && + o.data.directives.some(ya) && + (o.data.show = !0), + f && + f.data && + !ha(o, f) && + !Pe(f) && + (!f.componentInstance || + !f.componentInstance._vnode + .isComment)) + ) { + var l = (f.data.transition = I({}, u)); + if ("out-in" === r) + return ( + (this._leaving = !0), + xe(l, "afterLeave", function () { + (e._leaving = !1), + e.$forceUpdate(); + }), + da(t, i) + ); + if ("in-out" === r) { + if (Pe(o)) return c; + var d, + p = function () { + d(); + }; + xe(u, "afterEnter", p), + xe(u, "enterCancelled", p), + xe(l, "delayLeave", function (t) { + d = t; + }); + } + } + return i; + } + }, + }, + ga = I({ tag: String, moveClass: String }, ca); + delete ga.mode; + var ba = { + props: ga, + beforeMount: function () { + var t = this, + e = this._update; + this._update = function (n, r) { + var i = Mn(t); + t.__patch__(t._vnode, t.kept, !1, !0), + (t._vnode = t.kept), + i(), + e.call(t, n, r); + }; + }, + render: function (t) { + for ( + var e = this.tag || this.$vnode.data.tag || "span", + n = Object.create(null), + r = (this.prevChildren = this.children), + i = this.$slots.default || [], + o = (this.children = []), + a = la(this), + s = 0; + s < i.length; + s++ + ) { + var u = i[s]; + if (u.tag) + if ( + null != u.key && + 0 !== String(u.key).indexOf("__vlist") + ) + o.push(u), + (n[u.key] = u), + ((u.data || (u.data = {})).transition = + a); + else; + } + if (r) { + for (var c = [], f = [], l = 0; l < r.length; l++) { + var d = r[l]; + (d.data.transition = a), + (d.data.pos = + d.elm.getBoundingClientRect()), + n[d.key] ? c.push(d) : f.push(d); + } + (this.kept = t(e, null, c)), (this.removed = f); + } + return t(e, null, o); + }, + updated: function () { + var t = this.prevChildren, + e = this.moveClass || (this.name || "v") + "-move"; + t.length && + this.hasMove(t[0].elm, e) && + (t.forEach(_a), + t.forEach(wa), + t.forEach(xa), + (this._reflow = document.body.offsetHeight), + t.forEach(function (t) { + if (t.data.moved) { + var n = t.elm, + r = n.style; + Ro(n, e), + (r.transform = + r.WebkitTransform = + r.transitionDuration = + ""), + n.addEventListener( + Io, + (n._moveCb = function t(r) { + (r && r.target !== n) || + (r && + !/transform$/.test( + r.propertyName + )) || + (n.removeEventListener( + Io, + t + ), + (n._moveCb = null), + $o(n, e)); + }) + ); + } + })); + }, + methods: { + hasMove: function (t, e) { + if (!To) return !1; + if (this._hasMove) return this._hasMove; + var n = t.cloneNode(); + t._transitionClasses && + t._transitionClasses.forEach(function (t) { + So(n, t); + }), + ko(n, e), + (n.style.display = "none"), + this.$el.appendChild(n); + var r = zo(n); + return ( + this.$el.removeChild(n), + (this._hasMove = r.hasTransform) + ); + }, + }, + }; + function _a(t) { + t.elm._moveCb && t.elm._moveCb(), + t.elm._enterCb && t.elm._enterCb(); + } + function wa(t) { + t.data.newPos = t.elm.getBoundingClientRect(); + } + function xa(t) { + var e = t.data.pos, + n = t.data.newPos, + r = e.left - n.left, + i = e.top - n.top; + if (r || i) { + t.data.moved = !0; + var o = t.elm.style; + (o.transform = o.WebkitTransform = + "translate(" + r + "px," + i + "px)"), + (o.transitionDuration = "0s"); + } + } + var ka = { Transition: ma, TransitionGroup: ba }; + (Sr.config.mustUseProp = Ur), + (Sr.config.isReservedTag = ai), + (Sr.config.isReservedAttr = Fr), + (Sr.config.getTagNamespace = si), + (Sr.config.isUnknownElement = ci), + I(Sr.options.directives, ua), + I(Sr.options.components, ka), + (Sr.prototype.__patch__ = K ? Xo : L), + (Sr.prototype.$mount = function (t, e) { + return (t = t && K ? li(t) : void 0), Nn(this, t, e); + }), + K && + setTimeout(function () { + B.devtools && ct && ct.emit("init", Sr); + }, 0), + (e["a"] = Sr); + }.call(this, n("c8ba"))); + }, + "2b4c": function (t, e, n) { + var r = n("5537")("wks"), + i = n("ca5a"), + o = n("7726").Symbol, + a = "function" == typeof o, + s = (t.exports = function (t) { + return ( + r[t] || + (r[t] = (a && o[t]) || (a ? o : i)("Symbol." + t)) + ); + }); + s.store = r; + }, + "2d00": function (t, e) { + t.exports = !1; + }, + "2d7d": function (t, e, n) { + t.exports = n("b347"); + }, + "2d95": function (t, e) { + var n = {}.toString; + t.exports = function (t) { + return n.call(t).slice(8, -1); + }; + }, + "2ea1": function (t, e, n) { + var r = n("6f8a"); + t.exports = function (t, e) { + if (!r(t)) return t; + var n, i; + if ( + e && + "function" == typeof (n = t.toString) && + !r((i = n.call(t))) + ) + return i; + if ("function" == typeof (n = t.valueOf) && !r((i = n.call(t)))) + return i; + if ( + !e && + "function" == typeof (n = t.toString) && + !r((i = n.call(t))) + ) + return i; + throw TypeError("Can't convert object to primitive value"); + }; + }, + "308d": function (t, e, n) { + "use strict"; + var r = n("67bb"), + i = n.n(r), + o = n("5d58"), + a = n.n(o); + function s(t) { + return ( + (s = + "function" == typeof i.a && "symbol" == typeof a.a + ? function (t) { + return typeof t; + } + : function (t) { + return t && + "function" == typeof i.a && + t.constructor === i.a && + t !== i.a.prototype + ? "symbol" + : typeof t; + }), + s(t) + ); + } + function u(t) { + if (void 0 === t) + throw new ReferenceError( + "this hasn't been initialised - super() hasn't been called" + ); + return t; + } + function c(t, e) { + if (e && ("object" === s(e) || "function" === typeof e)) + return e; + if (void 0 !== e) + throw new TypeError( + "Derived constructors may only return object or undefined" + ); + return u(t); + } + n.d(e, "a", function () { + return c; + }); + }, + "31c2": function (t, e) { + e.f = Object.getOwnPropertySymbols; + }, + "31f4": function (t, e) { + t.exports = function (t, e, n) { + var r = void 0 === n; + switch (e.length) { + case 0: + return r ? t() : t.call(n); + case 1: + return r ? t(e[0]) : t.call(n, e[0]); + case 2: + return r ? t(e[0], e[1]) : t.call(n, e[0], e[1]); + case 3: + return r + ? t(e[0], e[1], e[2]) + : t.call(n, e[0], e[1], e[2]); + case 4: + return r + ? t(e[0], e[1], e[2], e[3]) + : t.call(n, e[0], e[1], e[2], e[3]); + } + return t.apply(n, e); + }; + }, + "32e9": function (t, e, n) { + var r = n("86cc"), + i = n("4630"); + t.exports = n("9e1e") + ? function (t, e, n) { + return r.f(t, e, i(1, n)); + } + : function (t, e, n) { + return (t[e] = n), t; + }; + }, + 3360: function (t, e, n) { + "use strict"; + Object.defineProperty(e, "__esModule", { value: !0 }), + (e.default = void 0); + var r = n("78ef"), + i = function () { + for ( + var t = arguments.length, e = new Array(t), n = 0; + n < t; + n++ + ) + e[n] = arguments[n]; + return (0, r.withParams)({ type: "and" }, function () { + for ( + var t = this, + n = arguments.length, + r = new Array(n), + i = 0; + i < n; + i++ + ) + r[i] = arguments[i]; + return ( + e.length > 0 && + e.reduce(function (e, n) { + return e && n.apply(t, r); + }, !0) + ); + }); + }; + e.default = i; + }, + "33a4": function (t, e, n) { + var r = n("84f2"), + i = n("2b4c")("iterator"), + o = Array.prototype; + t.exports = function (t) { + return void 0 !== t && (r.Array === t || o[i] === t); + }; + }, + "376c": function (t, e, n) { + "use strict"; + (function (t) { + n.d(e, "a", function () { + return r; + }); + //! TrackJS JavaScript error monitoring agent. + //! COPYRIGHT (c) 2021 ALL RIGHTS RESERVED + //! See License at https://trackjs.com/terms/ + var r = (function (e, n, r) { + var i = function (t, e) { + (this.config = t), + (this.onError = e), + t.enabled && this.watch(); + }; + i.prototype = { + watch: function () { + h.forEach( + ["EventTarget", "Node", "XMLHttpRequest"], + function (t) { + h.has( + e, + t + ".prototype.addEventListener" + ) && + h.hasOwn( + e[t].prototype, + "addEventListener" + ) && + this.wrapEventTarget(e[t].prototype); + }, + this + ), + this.wrapTimer("setTimeout"), + this.wrapTimer("setInterval"); + }, + wrap: function (t) { + function e() { + try { + return t.apply(this, arguments); + } catch (e) { + throw ( + (i.onError("catch", e, { + bindTime: n, + bindStack: r, + }), + h.wrapError(e)) + ); + } + } + var n, + r, + i = this; + try { + if ( + !h.isFunction(t) || + h.hasOwn(t, "__trackjs__") + ) + return t; + if (h.hasOwn(t, "__trackjs_state__")) + return t.__trackjs_state__; + } catch (a) { + return t; + } + if (i.config.bindStack) + try { + throw Error(); + } catch (a) { + (r = a.stack), (n = h.isoNow()); + } + for (var o in t) h.hasOwn(t, o) && (e[o] = t[o]); + return ( + (e.prototype = t.prototype), + (e.__trackjs__ = !0), + (t.__trackjs_state__ = e) + ); + }, + wrapEventTarget: function (t) { + var n = this; + h.has(t, "addEventListener.call") && + h.has(t, "removeEventListener.call") && + (h.patch(t, "addEventListener", function (t) { + return function (r, i, o, a) { + try { + h.has(i, "handleEvent") && + (i.handleEvent = n.wrap( + i.handleEvent + )); + } catch (e) {} + return t.call(this, r, n.wrap(i), o, a); + }; + }), + h.patch(t, "removeEventListener", function (t) { + return function (e, n, r, i) { + try { + n = n && (n.__trackjs_state__ || n); + } catch (o) {} + return t.call(this, e, n, r, i); + }; + })); + }, + wrapTimer: function (t) { + var n = this; + h.patch(e, t, function (t) { + return function (e, r) { + var i = + Array.prototype.slice.call( + arguments + ), + o = i[0]; + return ( + h.isFunction(o) && (i[0] = n.wrap(o)), + h.has(t, "apply") + ? t.apply(this, i) + : t(i[0], i[1]) + ); + }; + }); + }, + }; + var o = function (t) { + this.initCurrent(t) || + console.warn("[TrackJS] invalid config"); + }; + o.prototype = { + current: {}, + initOnly: { + application: !0, + cookie: !0, + enabled: !0, + token: !0, + callback: { enabled: !0 }, + console: { enabled: !0 }, + navigation: { enabled: !0 }, + network: { enabled: !0, fetch: !0 }, + visitor: { enabled: !0 }, + window: { enabled: !0, promise: !0 }, + }, + defaults: { + application: "", + cookie: !1, + dedupe: !0, + dependencies: !0, + enabled: !0, + forwardingDomain: "", + errorURL: "https://capture.trackjs.com/capture", + errorNoSSLURL: "http://capture.trackjs.com/capture", + faultURL: "https://usage.trackjs.com/fault.gif", + usageURL: "https://usage.trackjs.com/usage.gif", + onError: function () { + return !0; + }, + serialize: function (t) { + function e(t) { + var e = "<" + t.tagName.toLowerCase(); + t = t.attributes || []; + for (var n = 0; n < t.length; n++) + e += + " " + + t[n].name + + '="' + + t[n].value + + '"'; + return e + ">"; + } + if ("" === t) return "Empty String"; + if (t === r) return "undefined"; + if ( + h.isString(t) || + h.isNumber(t) || + h.isBoolean(t) || + h.isFunction(t) + ) + return "" + t; + if (h.isElement(t)) return e(t); + if ("symbol" === typeof t) + return Symbol.prototype.toString.call(t); + var n; + try { + n = JSON.stringify(t, function (t, n) { + return n === r + ? "undefined" + : h.isNumber(n) && isNaN(n) + ? "NaN" + : h.isError(n) + ? { + name: n.name, + message: n.message, + stack: n.stack, + } + : h.isElement(n) + ? e(n) + : n; + }); + } catch (o) { + for (var i in ((n = ""), t)) + if (t.hasOwnProperty(i)) + try { + n += + ',"' + + i + + '":"' + + t[i] + + '"'; + } catch (a) {} + n = n + ? "{" + n.replace(",", "") + "}" + : "Unserializable Object"; + } + return n + .replace(/"undefined"/g, "undefined") + .replace(/"NaN"/g, "NaN"); + }, + sessionId: "", + token: "", + userId: "", + version: "", + callback: { enabled: !0, bindStack: !1 }, + console: { + enabled: !0, + display: !0, + error: !0, + warn: !1, + watch: [ + "log", + "debug", + "info", + "warn", + "error", + ], + }, + navigation: { enabled: !0 }, + network: { enabled: !0, error: !0, fetch: !0 }, + visitor: { enabled: !0 }, + window: { enabled: !0, promise: !0 }, + }, + initCurrent: function (t) { + return ( + this.removeEmpty(t), + this.validate( + t, + this.defaults, + "[TrackJS] config", + {} + ) + ? ((this.current = h.defaultsDeep( + {}, + t, + this.defaults + )), + !0) + : ((this.current = h.defaultsDeep( + {}, + this.defaults + )), + !1) + ); + }, + setCurrent: function (t) { + return ( + !!this.validate( + t, + this.defaults, + "[TrackJS] config", + this.initOnly + ) && + ((this.current = h.defaultsDeep( + {}, + t, + this.current + )), + !0) + ); + }, + removeEmpty: function (t) { + for (var e in t) + t.hasOwnProperty(e) && + t[e] === r && + delete t[e]; + }, + validate: function (t, e, n, r) { + var i = !0; + for (var o in ((n = n || ""), (r = r || {}), t)) + if (t.hasOwnProperty(o)) + if (e.hasOwnProperty(o)) { + var a = typeof e[o]; + a !== typeof t[o] + ? (console.warn( + n + + "." + + o + + ": property must be type " + + a + + "." + ), + (i = !1)) + : "[object Array]" !== + Object.prototype.toString.call( + t[o] + ) || + this.validateArray( + t[o], + e[o], + n + "." + o + ) + ? "[object Object]" === + Object.prototype.toString.call( + t[o] + ) + ? (i = this.validate( + t[o], + e[o], + n + "." + o, + r[o] + )) + : r.hasOwnProperty(o) && + (console.warn( + n + + "." + + o + + ": property cannot be set after load." + ), + (i = !1)) + : (i = !1); + } else + console.warn( + n + + "." + + o + + ": property not supported." + ), + (i = !1); + return i; + }, + validateArray: function (t, e, n) { + var r = !0; + n = n || ""; + for (var i = 0; i < t.length; i++) + h.contains(e, t[i]) || + (console.warn( + n + + "[" + + i + + "]: invalid value: " + + t[i] + + "." + ), + (r = !1)); + return r; + }, + }; + var a = function (t, e, n, r, i, o, a) { + (this.util = t), + (this.log = e), + (this.onError = n), + (this.onFault = r), + (this.serialize = i), + a.enabled && + (o.console = this.wrapConsoleObject( + o.console, + a + )); + }; + a.prototype = { + wrapConsoleObject: function (t, e) { + t = t || {}; + var n, + r = t.log || function () {}, + i = this; + for (n = 0; n < e.watch.length; n++) + (function (n) { + var o = t[n] || r; + t[n] = function () { + try { + var r = + Array.prototype.slice.call( + arguments + ); + if ( + (i.log.add("c", { + timestamp: i.util.isoNow(), + severity: n, + message: i.serialize( + 1 === r.length + ? r[0] + : r + ), + }), + e[n]) + ) + if ( + h.isError(r[0]) && + 1 === r.length + ) + i.onError("console", r[0]); + else + try { + throw Error( + i.serialize( + 1 === r.length + ? r[0] + : r + ) + ); + } catch (a) { + i.onError("console", a); + } + e.display && + (i.util.hasFunction(o, "apply") + ? o.apply(t, r) + : o(r[0])); + } catch (a) { + i.onFault(a); + } + }; + })(e.watch[n]); + return t; + }, + report: function () { + return this.log.all("c"); + }, + }; + var s = function (t, e, n, r, i) { + (this.config = t), + (this.util = e), + (this.log = n), + (this.window = r), + (this.document = i), + (this.correlationId = this.token = null), + this.initialize(); + }; + s.prototype = { + initialize: function () { + (this.token = this.getCustomerToken()), + (this.correlationId = this.getCorrelationId()); + }, + getCustomerToken: function () { + if (this.config.current.token) + return this.config.current.token; + var t = + this.document.getElementsByTagName("script"); + return t[t.length - 1].getAttribute("data-token"); + }, + getCorrelationId: function () { + var t; + if (!this.config.current.cookie) + return this.util.uuid(); + try { + (t = this.document.cookie.replace( + /(?:(?:^|.*;\s*)TrackJS\s*\=\s*([^;]*).*$)|^.*$/, + "$1" + )), + t || + ((t = this.util.uuid()), + (this.document.cookie = + "TrackJS=" + + t + + "; expires=Fri, 31 Dec 9999 23:59:59 GMT; path=/")); + } catch (e) { + t = this.util.uuid(); + } + return t; + }, + report: function () { + return { + application: this.config.current.application, + correlationId: this.correlationId, + sessionId: this.config.current.sessionId, + token: this.token, + userId: this.config.current.userId, + version: this.config.current.version, + }; + }, + }; + var u = function (t) { + (this.config = t), + (this.loadedOn = new Date().getTime()), + (this.originalUrl = h.getLocation()), + (this.referrer = h.isBrowser ? n.referrer : ""); + }; + u.prototype = { + discoverDependencies: function () { + var t = { TrackJS: "3.10.1" }; + for (var n in (e.jQuery && + e.jQuery.fn && + e.jQuery.fn.jquery && + (t.jQuery = e.jQuery.fn.jquery), + e.jQuery && + e.jQuery.ui && + e.jQuery.ui.version && + (t.jQueryUI = e.jQuery.ui.version), + e.angular && + e.angular.version && + e.angular.version.full && + (t.angular = e.angular.version.full), + e)) + if ( + "_trackJs" !== n && + "_trackJS" !== n && + "_trackjs" !== n && + "webkitStorageInfo" !== n && + "webkitIndexedDB" !== n && + "top" !== n && + "parent" !== n && + "frameElement" !== n + ) + try { + if (e[n]) { + var r = + e[n].version || + e[n].Version || + e[n].VERSION; + "string" === typeof r && (t[n] = r); + } + } catch (i) {} + return ( + t.TrackJS && t.trackJs && delete t.trackJs, t + ); + }, + report: function () { + return { + age: new Date().getTime() - this.loadedOn, + dependencies: this.config.current.dependencies + ? this.discoverDependencies() + : { trackJs: "3.10.1" }, + originalUrl: this.originalUrl, + referrer: this.referrer, + userAgent: e.navigator.userAgent, + viewportHeight: h.isBrowser + ? e.document.documentElement.clientHeight + : 0, + viewportWidth: h.isBrowser + ? e.document.documentElement.clientWidth + : 0, + }; + }, + }; + var c = function (t) { + (this.util = t), + (this.appender = []), + (this.maxLength = 30); + }; + c.prototype = { + all: function (t) { + var e, + n, + r = []; + for (n = 0; n < this.appender.length; n++) + (e = this.appender[n]) && + e.category === t && + r.push(e.value); + return r; + }, + clear: function () { + this.appender.length = 0; + }, + truncate: function () { + this.appender.length > this.maxLength && + (this.appender = this.appender.slice( + Math.max( + this.appender.length - this.maxLength, + 0 + ) + )); + }, + add: function (t, e) { + var n = this.util.uuid(); + return ( + this.appender.push({ + key: n, + category: t, + value: e, + }), + this.truncate(), + n + ); + }, + get: function (t, e) { + var n, r; + for (r = 0; r < this.appender.length; r++) + if ( + ((n = this.appender[r]), + n.category === t && n.key === e) + ) + return n.value; + return !1; + }, + }; + var f = function (t) { + var e = {}; + return { + addMetadata: function (t, n) { + e[t] = n; + }, + removeMetadata: function (t) { + delete e[t]; + }, + report: function () { + var n, + r = []; + for (n in e) + e.hasOwnProperty(n) && + r.push({ key: n, value: t(e[n]) }); + return r; + }, + store: e, + }; + }, + l = function (t, e) { + (this.log = t), + (this.options = e), + e.enabled && this.watch(); + }; + l.prototype = { + isCompatible: function (t) { + return ( + (t = t || e), + !h.has(t, "chrome.app.runtime") && + h.has(t, "addEventListener") && + h.has(t, "history.pushState") + ); + }, + record: function (t, e, n) { + this.log.add("h", { + type: t, + from: h.truncate(e, 250), + to: h.truncate(n, 250), + on: h.isoNow(), + }); + }, + report: function () { + return this.log.all("h"); + }, + watch: function () { + if (this.isCompatible()) { + var t = this, + n = h.getLocationURL().relative; + e.addEventListener( + "popstate", + function () { + var e = h.getLocationURL().relative; + t.record("popState", n, e), (n = e); + }, + !0 + ), + h.forEach( + ["pushState", "replaceState"], + function (e) { + h.patch(history, e, function (r) { + return function () { + n = + h.getLocationURL() + .relative; + var i = r.apply( + this, + arguments + ), + o = + h.getLocationURL() + .relative; + return ( + t.record(e, n, o), + (n = o), + i + ); + }; + }); + } + ); + } + }, + }; + var d = function (t, e, n, r, i, o) { + (this.util = t), + (this.log = e), + (this.onError = n), + (this.onFault = r), + (this.window = i), + (this.options = o), + o.enabled && this.initialize(i); + }; + d.prototype = { + initialize: function (t) { + t.XMLHttpRequest && + this.util.hasFunction( + t.XMLHttpRequest.prototype.open, + "apply" + ) && + this.watchNetworkObject(t.XMLHttpRequest), + t.XDomainRequest && + this.util.hasFunction( + t.XDomainRequest.prototype.open, + "apply" + ) && + this.watchNetworkObject(t.XDomainRequest), + this.options.fetch && + h.isWrappableFunction(t.fetch) && + this.watchFetch(); + }, + escapeUrl: function (t) { + return ("" + t) + .replace(/ /gi, "%20") + .replace(/\t/gi, "%09"); + }, + watchFetch: function () { + var t = this, + n = this.log, + r = this.options, + i = this.onError; + h.patch(e, "fetch", function (o) { + return function (a, s) { + if (s && s.__trackjs__) + return o.apply(e, arguments); + var u; + try { + throw Error(); + } catch (d) { + u = d.stack; + } + var c = a instanceof Request ? a.url : a, + f = + a instanceof Request + ? a.method + : (s || {}).method || "GET", + l = + ((c = t.escapeUrl(c)), + o.apply(e, arguments)); + return ( + (l.__trackjs_state__ = n.add("n", { + type: "fetch", + startedOn: h.isoNow(), + method: f, + url: h.truncate(c, 2e3), + })), + l + .then(function (t) { + var e = n.get( + "n", + l.__trackjs_state__ + ); + if (e) { + h.defaults(e, { + completedOn: h.isoNow(), + statusCode: t.status, + statusText: + t.statusText, + }); + var o = t.headers.get( + "trackjs-correlation-id" + ); + o && + (e.requestCorrelationId = + o), + r.error && + 400 <= t.status && + ((e = Error( + e.statusCode + + " : " + + e.method + + " " + + e.url + )), + (e.stack = u), + i("ajax", e)); + } + return t; + }) + ["catch"](function (t) { + t = t || {}; + var e = n.get( + "n", + l.__trackjs_state__ + ); + throw ( + (e && + (h.defaults(e, { + completedOn: + h.isoNow(), + statusCode: 0, + statusText: + t.toString(), + }), + r.error && + (i("ajax", { + name: t.name, + message: + (t.message || + "Failed") + + ": " + + e.method + + " " + + e.url, + stack: + t.stack || + u, + }), + (t.__trackjs_state__ = + !0))), + t) + ); + }) + ); + }; + }); + }, + watchNetworkObject: function (t) { + var e = this, + n = t.prototype.open, + r = t.prototype.send; + return ( + (t.prototype.open = function (t, r) { + var i = (r || "").toString(); + return ( + 0 > i.indexOf("localhost:0") && + ((i = e.escapeUrl(i)), + (this._trackJs = { + method: t, + url: i, + })), + n.apply(this, arguments) + ); + }), + (t.prototype.send = function () { + if (!this._trackJs) + try { + return r.apply(this, arguments); + } catch (t) { + return void e.onError("ajax", t); + } + try { + (this._trackJs.logId = e.log.add("n", { + type: "xhr", + startedOn: e.util.isoNow(), + method: this._trackJs.method, + url: h.truncate( + this._trackJs.url, + 2e3 + ), + })), + e.listenForNetworkComplete(this); + } catch (t) { + e.onFault(t); + } + return r.apply(this, arguments); + }), + t + ); + }, + listenForNetworkComplete: function (t) { + var e = this; + e.window.ProgressEvent && + t.addEventListener && + t.addEventListener( + "readystatechange", + function () { + 4 === t.readyState && + e.finalizeNetworkEvent(t); + }, + !0 + ), + t.addEventListener + ? t.addEventListener( + "load", + function () { + e.finalizeNetworkEvent(t), + e.checkNetworkFault(t); + }, + !0 + ) + : setTimeout(function () { + try { + var n = t.onload; + t.onload = function () { + e.finalizeNetworkEvent(t), + e.checkNetworkFault(t), + "function" === typeof n && + e.util.hasFunction( + n, + "apply" + ) && + n.apply(t, arguments); + }; + var r = t.onerror; + t.onerror = function () { + e.finalizeNetworkEvent(t), + e.checkNetworkFault(t), + "function" === + typeof oldOnError && + r.apply(t, arguments); + }; + } catch (h) { + e.onFault(h); + } + }, 0); + }, + finalizeNetworkEvent: function (t) { + if (t._trackJs) { + var e = this.log.get("n", t._trackJs.logId); + e && + ((e.completedOn = this.util.isoNow()), + t.getAllResponseHeaders && + t.getResponseHeader && + 0 <= + (t.getAllResponseHeaders() || "") + .toLowerCase() + .indexOf( + "trackjs-correlation-id" + ) && + (e.requestCorrelationId = + t.getResponseHeader( + "trackjs-correlation-id" + )), + (e.statusCode = + 1223 == t.status ? 204 : t.status), + (e.statusText = + 1223 == t.status + ? "No Content" + : t.statusText)); + } + }, + checkNetworkFault: function (t) { + if ( + this.options.error && + 400 <= t.status && + 1223 != t.status + ) { + var e = t._trackJs || {}; + this.onError( + "ajax", + t.status + " : " + e.method + " " + e.url + ); + } + }, + report: function () { + return this.log.all("n"); + }, + }; + var p = function (t, n) { + (this.util = t), + (this.config = n), + (this.disabled = !1), + (this.throttleStats = { + attemptCount: 0, + throttledCount: 0, + lastAttempt: new Date().getTime(), + }), + (e.JSON && e.JSON.stringify) || + (this.disabled = !0); + }; + p.prototype = { + errorEndpoint: function (t) { + var n = this.config.current, + r = n.errorURL; + return ( + h.isBrowser && + !h.testCrossdomainXhr() && + -1 === e.location.protocol.indexOf("https") + ? (r = n.errorNoSSLURL) + : n.forwardingDomain && + (r = + "https://" + + n.forwardingDomain + + "/capture"), + r + "?token=" + t + "&v=3.10.1" + ); + }, + usageEndpoint: function (t) { + var e = this.config.current, + n = e.usageURL; + return ( + e.forwardingDomain && + (n = + "https://" + + e.forwardingDomain + + "/usage.gif"), + this.appendObjectAsQuery(t, n) + ); + }, + trackerFaultEndpoint: function (t) { + var e = + (this.config || {}).current || + o.prototype.defaults, + n = e.faultURL; + return ( + e.forwardingDomain && + (n = + "https://" + + e.forwardingDomain + + "/fault.gif"), + this.appendObjectAsQuery(t, n) + ); + }, + appendObjectAsQuery: function (t, e) { + for (var n in ((e += "?"), t)) + t.hasOwnProperty(n) && + (e += + encodeURIComponent(n) + + "=" + + encodeURIComponent(t[n]) + + "&"); + return e; + }, + getCORSRequest: function (t, n) { + var r; + return ( + this.util.testCrossdomainXhr() + ? ((r = new e.XMLHttpRequest()), + r.open(t, n), + r.setRequestHeader( + "Content-Type", + "text/plain" + )) + : "undefined" !== typeof e.XDomainRequest + ? ((r = new e.XDomainRequest()), + r.open(t, n)) + : (r = null), + r + ); + }, + sendTrackerFault: function (t) { + this.throttle(t) || + (h.isBrowser + ? (n.createElement("img").src = + this.trackerFaultEndpoint(t)) + : fetch(this.trackerFaultEndpoint(t), { + mode: "no-cors", + __trackjs__: !0, + })); + }, + sendUsage: function (t) { + h.isBrowser + ? (n.createElement("img").src = + this.usageEndpoint(t)) + : fetch(this.usageEndpoint(t), { + mode: "no-cors", + __trackjs__: !0, + }); + }, + sendError: function (t, n) { + var i = this; + if (!this.disabled && !this.throttle(t)) + try { + if (h.isBrowser) { + var o = this.getCORSRequest( + "POST", + this.errorEndpoint(n) + ); + (o.onreadystatechange = function () { + 4 !== o.readyState || + h.contains( + [200, 202], + o.status + ) || + (i.disabled = !0); + }), + (o._trackJs = r), + o.send(e.JSON.stringify(t)); + } else if (h.isWorker) { + var a = { + method: "POST", + mode: "cors", + body: e.JSON.stringify(t), + __trackjs__: 1, + }; + fetch(this.errorEndpoint(n), a) + .then(function (t) { + t.ok || (i.disabled = !0); + }) + ["catch"](function (t) { + i.disabled = !0; + }); + } + } catch (s) { + throw ((this.disabled = !0), s); + } + }, + throttle: function (t) { + var e = new Date().getTime(); + if ( + (this.throttleStats.attemptCount++, + this.throttleStats.lastAttempt + 1e3 >= e) + ) { + if ( + ((this.throttleStats.lastAttempt = e), + 10 < this.throttleStats.attemptCount) + ) + return ( + this.throttleStats.throttledCount++, !0 + ); + } else + (t.throttled = + this.throttleStats.throttledCount), + (this.throttleStats.attemptCount = 0), + (this.throttleStats.lastAttempt = e), + (this.throttleStats.throttledCount = 0); + return !1; + }, + }; + var h = (function () { + function i(t, e, n, a) { + return ( + (n = n || !1), + (a = a || 0), + h.forEach(e, function (e) { + h.forEach(h.keys(e), function (s) { + null === e[s] || e[s] === r + ? (t[s] = e[s]) + : n && + 10 > a && + "[object Object]" === o(e[s]) + ? ((t[s] = t[s] || {}), + i(t[s], [e[s]], n, a + 1)) + : t.hasOwnProperty(s) || + (t[s] = e[s]); + }); + }), + t + ); + } + function o(t) { + return Object.prototype.toString.call(t); + } + return { + isBrowser: + "undefined" !== typeof e && + "undefined" !== typeof e.document, + isWorker: + "object" === typeof self && + self.constructor && + 0 <= + (self.constructor.name || "").indexOf( + "WorkerGlobalScope" + ), + isNode: + "undefined" !== typeof t && + null != t.versions && + null != t.versions.node, + addEventListenerSafe: function (t, e, n, r) { + t.addEventListener + ? t.addEventListener(e, n, r) + : t.attachEvent && + t.attachEvent("on" + e, n); + }, + afterDocumentLoad: function (t) { + if (h.isWorker) h.defer(t); + else { + var e = !1; + "complete" === n.readyState + ? h.defer(t) + : (h.addEventListenerSafe( + n, + "readystatechange", + function () { + "complete" !== + n.readyState || + e || + (h.defer(t), + (e = !0)); + } + ), + setTimeout(function () { + e || (h.defer(t), (e = !0)); + }, 1e4)); + } + }, + bind: function (t, e) { + return function () { + return t.apply( + e, + Array.prototype.slice.call( + arguments + ) + ); + }; + }, + contains: function (t, e) { + return 0 <= t.indexOf(e); + }, + defaults: function (t) { + return i( + t, + Array.prototype.slice.call( + arguments, + 1 + ), + !1 + ); + }, + defaultsDeep: function (t) { + return i( + t, + Array.prototype.slice.call( + arguments, + 1 + ), + !0 + ); + }, + defer: function (t, e) { + setTimeout(function () { + t.apply(e); + }); + }, + forEach: function (t, e, n) { + if (h.isArray(t)) { + if (t.forEach) return t.forEach(e, n); + for (var r = 0; r < t.length; ) + e.call(n, t[r], r, t), r++; + } + }, + getLocation: function () { + return e.location + .toString() + .replace(/ /g, "%20"); + }, + getLocationURL: function () { + return h.parseURL(h.getLocation()); + }, + has: function (t, e) { + try { + for ( + var n = e.split("."), r = t, i = 0; + i < n.length; + i++ + ) { + if (!r[n[i]]) return !1; + r = r[n[i]]; + } + return !0; + } catch (o) { + return !1; + } + }, + hasFunction: function (t, e) { + try { + return !!t[e]; + } catch (h) { + return !1; + } + }, + hasOwn: function (t, e) { + return Object.prototype.hasOwnProperty.call( + t, + e + ); + }, + isArray: function (t) { + return "[object Array]" === o(t); + }, + isBoolean: function (t) { + return ( + "boolean" === typeof t || + (h.isObject(t) && + "[object Boolean]" === o(t)) + ); + }, + isBrowserIE: function (t) { + t = t || e.navigator.userAgent; + var n = t.match(/Trident\/([\d.]+)/); + return n && "7.0" === n[1] + ? 11 + : !!(t = t.match(/MSIE ([\d.]+)/)) && + parseInt(t[1], 10); + }, + isBrowserSupported: function () { + var t = this.isBrowserIE(); + return !t || 8 <= t; + }, + isError: function (t) { + if (!h.isObject(t)) return !1; + var e = o(t); + return ( + "[object Error]" === e || + "[object DOMException]" === e || + (h.isString(t.name) && + h.isString(t.message)) + ); + }, + isElement: function (t) { + return h.isObject(t) && 1 === t.nodeType; + }, + isFunction: function (t) { + return !(!t || "function" !== typeof t); + }, + isNumber: function (t) { + return ( + "number" === typeof t || + (h.isObject(t) && + "[object Number]" === o(t)) + ); + }, + isObject: function (t) { + return !(!t || "object" !== typeof t); + }, + isString: function (t) { + return ( + "string" === typeof t || + (!h.isArray(t) && + h.isObject(t) && + "[object String]" === o(t)) + ); + }, + isWrappableFunction: function (t) { + return ( + this.isFunction(t) && + this.hasFunction(t, "apply") + ); + }, + isoNow: function () { + var t = new Date(); + return t.toISOString + ? t.toISOString() + : t.getUTCFullYear() + + "-" + + this.pad(t.getUTCMonth() + 1) + + "-" + + this.pad(t.getUTCDate()) + + "T" + + this.pad(t.getUTCHours()) + + ":" + + this.pad(t.getUTCMinutes()) + + ":" + + this.pad(t.getUTCSeconds()) + + "." + + String( + ( + t.getUTCMilliseconds() / + 1e3 + ).toFixed(3) + ).slice(2, 5) + + "Z"; + }, + keys: function (t) { + if (!h.isObject(t)) return []; + var e, + n = []; + for (e in t) + t.hasOwnProperty(e) && n.push(e); + return n; + }, + noop: function () {}, + pad: function (t) { + return ( + (t = String(t)), + 1 === t.length && (t = "0" + t), + t + ); + }, + parseURL: function (t) { + var e = t.match( + /^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$/ + ); + return e + ? ((e = { + protocol: e[2], + host: e[4], + path: e[5], + query: e[6], + hash: e[8], + }), + (e.origin = + (e.protocol || "") + + "://" + + (e.host || "")), + (e.relative = + (e.path || "") + + (e.query || "") + + (e.hash || "")), + (e.href = t), + e) + : {}; + }, + patch: function (t, e, n) { + t[e] = n(t[e] || h.noop); + }, + testCrossdomainXhr: function () { + return ( + h.isBrowser && + "withCredentials" in + new XMLHttpRequest() + ); + }, + truncate: function (t, e) { + if (((t = "" + t), t.length <= e)) return t; + var n = t.length - e; + return t.substr(0, e) + "...{" + n + "}"; + }, + tryGet: function (t, e) { + try { + return t[e]; + } catch (h) {} + }, + uuid: function () { + return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace( + /[xy]/g, + function (t) { + var e = (16 * Math.random()) | 0; + return ( + "x" == t ? e : (3 & e) | 8 + ).toString(16); + } + ); + }, + wrapError: function (t) { + var e = + t || Object.prototype.toString.call(t); + if (e && e.innerError) return t; + var n = Error( + "TrackJS Caught: " + (e.message || e) + ); + return ( + (n.description = + "TrackJS Caught: " + e.description), + (n.file = e.file), + (n.line = e.line || e.lineNumber), + (n.column = e.column || e.columnNumber), + (n.stack = e.stack), + (n.innerError = t), + n + ); + }, + }; + })(), + v = function (t, e, n, r, i, o) { + (this.util = t), + (this.log = e), + (this.onError = n), + (this.onFault = r), + (this.options = o), + (this.document = i), + t.isBrowser && o.enabled && this.initialize(i); + }; + v.prototype = { + initialize: function (t) { + var e = this.util.bind( + this.onDocumentClicked, + this + ), + n = this.util.bind(this.onInputChanged, this); + t.addEventListener + ? (t.addEventListener("click", e, !0), + t.addEventListener("blur", n, !0)) + : t.attachEvent && + (t.attachEvent("onclick", e), + t.attachEvent("onfocusout", n)); + }, + onDocumentClicked: function (t) { + try { + var e = this.getElementFromEvent(t); + e && + e.tagName && + (this.isDescribedElement(e, "a") || + this.isDescribedElement(e, "button") || + this.isDescribedElement(e, "input", [ + "button", + "submit", + ]) + ? this.writeVisitorEvent(e, "click") + : this.isDescribedElement(e, "input", [ + "checkbox", + "radio", + ]) && + this.writeVisitorEvent( + e, + "input", + e.value, + e.checked + )); + } catch (n) { + this.onFault(n); + } + }, + onInputChanged: function (t) { + try { + var e = this.getElementFromEvent(t); + e && + e.tagName && + (this.isDescribedElement(e, "textarea") + ? this.writeVisitorEvent( + e, + "input", + e.value + ) + : this.isDescribedElement( + e, + "select" + ) && + e.options && + e.options.length + ? this.onSelectInputChanged(e) + : this.isDescribedElement(e, "input") && + !this.isDescribedElement(e, "input", [ + "button", + "submit", + "hidden", + "checkbox", + "radio", + ]) && + this.writeVisitorEvent( + e, + "input", + e.value + )); + } catch (n) { + this.onFault(n); + } + }, + onSelectInputChanged: function (t) { + if (t.multiple) + for (var e = 0; e < t.options.length; e++) + t.options[e].selected && + this.writeVisitorEvent( + t, + "input", + t.options[e].value + ); + else + 0 <= t.selectedIndex && + t.options[t.selectedIndex] && + this.writeVisitorEvent( + t, + "input", + t.options[t.selectedIndex].value + ); + }, + writeVisitorEvent: function (t, e, n, i) { + "password" === this.getElementType(t) && (n = r); + var o = this.getElementAttributes(t); + t.innerText && + (o.__trackjs_element_text = this.util.truncate( + t.innerText, + 500 + )), + this.log.add("v", { + timestamp: this.util.isoNow(), + action: e, + element: { + tag: t.tagName.toLowerCase(), + attributes: o, + value: this.getMetaValue(n, i), + }, + }); + }, + getElementFromEvent: function (t) { + return ( + t.target || + n.elementFromPoint(t.clientX, t.clientY) + ); + }, + isDescribedElement: function (t, e, n) { + if (t.tagName.toLowerCase() !== e.toLowerCase()) + return !1; + if (!n) return !0; + for ( + t = this.getElementType(t), e = 0; + e < n.length; + e++ + ) + if (n[e] === t) return !0; + return !1; + }, + getElementType: function (t) { + return (t.getAttribute("type") || "").toLowerCase(); + }, + getElementAttributes: function (t) { + for ( + var e = {}, + n = Math.min(t.attributes.length, 10), + r = 0; + r < n; + r++ + ) { + var i = t.attributes[r]; + h.contains( + ["data-value", "value"], + i.name.toLowerCase() + ) || (e[i.name] = h.truncate(i.value, 100)); + } + return e; + }, + getMetaValue: function (t, e) { + return t === r + ? r + : { + length: t.length, + pattern: this.matchInputPattern(t), + checked: e, + }; + }, + matchInputPattern: function (t) { + return "" === t + ? "empty" + : /^[a-z0-9!#$%&'*+=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test( + t + ) + ? "email" + : /^(0?[1-9]|[12][0-9]|3[01])[\/\-](0?[1-9]|1[012])[\/\-]\d{4}$/.test( + t + ) || + /^(\d{4}[\/\-](0?[1-9]|1[012])[\/\-]0?[1-9]|[12][0-9]|3[01])$/.test( + t + ) + ? "date" + : /^(?:(?:\+?1\s*(?:[.-]\s*)?)?(?:\(\s*([2-9]1[02-9]|[2-9][02-8]1|[2-9][02-8][02-9])\s*\)|([2-9]1[02-9]|[2-9][02-8]1|[2-9][02-8][02-9]))\s*(?:[.-]\s*)?)?([2-9]1[02-9]|[2-9][02-9]1|[2-9][02-9]{2})\s*(?:[.-]\s*)?([0-9]{4})(?:\s*(?:#|x\.?|ext\.?|extension)\s*(\d+))?$/.test( + t + ) + ? "usphone" + : /^\s*$/.test(t) + ? "whitespace" + : /^\d*$/.test(t) + ? "numeric" + : /^[a-zA-Z]*$/.test(t) + ? "alpha" + : /^[a-zA-Z0-9]*$/.test(t) + ? "alphanumeric" + : "characters"; + }, + report: function () { + return this.log.all("v"); + }, + }; + var y = function (t, e, n, r, i) { + (this.onError = t), + (this.onFault = e), + (this.serialize = n), + i.enabled && this.watchWindowErrors(r), + i.promise && this.watchPromiseErrors(r); + }; + y.prototype = { + watchPromiseErrors: function (t) { + var e = this; + t.addEventListener + ? t.addEventListener( + "unhandledrejection", + function (t) { + try { + t = t || {}; + var n = t.detail + ? h.tryGet(t.detail, "reason") + : h.tryGet(t, "reason"); + if ( + n !== r && + null !== n && + !n.__trackjs_state__ + ) { + if (!h.isError(n)) + try { + throw Error( + e.serialize(n) + ); + } catch (i) { + n = i; + } + e.onError("promise", n); + } + } catch (i) { + e.onFault(i); + } + } + ) + : h.patch( + t, + "onunhandledrejection", + function (t) { + return function (n) { + e.onError("promise", n), + t.apply(this, arguments); + }; + } + ); + }, + watchWindowErrors: function (t) { + var e = this; + h.patch(t, "onerror", function (t) { + return function (n, i, o, a, s) { + try { + if (h.isError(s)) + return ( + e.onError("window", s), + void t.apply(this, arguments) + ); + s = s || {}; + var u = { + message: + s.message || e.serialize(n), + name: s.name || "Error", + line: + s.line || + parseInt(o, 10) || + null, + column: + s.column || + parseInt(a, 10) || + null, + stack: s.stack || null, + }; + "[object Event]" !== + Object.prototype.toString.call(n) || + i + ? (u.file = + s.file || e.serialize(i)) + : (u.file = (n.target || {}).src), + e.onError("window", u); + } catch (r) { + e.onFault(r); + } + t.apply(this, arguments); + }; + }); + }, + }; + var m = function () { + (this.hasInstalled = !1), + (this.hasEnabled = !0), + (this.window = e), + (this.document = n), + (this.util = h), + (this.install = h.bind(this.install, this)), + (this.onError = h.bind(this.onError, this)), + (this.onFault = h.bind(this.onFault, this)), + (this.serialize = h.bind(this.serialize, this)), + (this.log = new c(h)), + (this.metadata = new f(this.serialize)); + var t = e && (e._trackJs || e._trackJS || e._trackjs); + t && this.install(t); + }; + return ( + (m.prototype = { + install: function (t) { + try { + if (h.isNode) + return ( + this.warn( + "monitoring disabled in node" + ), + !1 + ); + if (!h.has(t, "token")) + return this.warn("missing token"), !1; + if (this.hasInstalled) + return ( + this.warn("already installed"), !1 + ); + if ( + ((this.config = new o(t)), + (this.transmitter = new p( + this.util, + this.config + )), + (this.environment = new u(this.config)), + (this.customer = new s( + this.config, + this.util, + this.log, + this.window, + this.document + )), + !this.config.current.enabled) + ) + return (this.hasEnabled = !1); + if ( + ((this.windowConsoleWatcher = new a( + this.util, + this.log, + this.onError, + this.onFault, + this.serialize, + this.window, + this.config.current.console + )), + !this.util.isBrowserSupported()) + ) + return !1; + (this.callbackWatcher = new i( + this.config.current.callback, + this.onError, + this.onFault + )), + (this.visitorWatcher = new v( + this.util, + this.log, + this.onError, + this.onFault, + this.document, + this.config.current.visitor + )), + (this.navigationWatcher = new l( + this.log, + this.config.current.navigation + )), + (this.networkWatcher = new d( + this.util, + this.log, + this.onError, + this.onFault, + this.window, + this.config.current.network + )), + (this.windowWatcher = new y( + this.onError, + this.onFault, + this.serialize, + this.window, + this.config.current.window + )); + var e = this; + return ( + h.afterDocumentLoad(function () { + e.transmitter.sendUsage({ + token: e.customer.token, + correlationId: + e.customer.correlationId, + application: + e.config.current + .application, + x: e.util.uuid(), + }); + }), + (this.hasInstalled = !0) + ); + } catch (n) { + return this.onFault(n), !1; + } + }, + pub: function () { + var t = this, + n = { + addMetadata: this.metadata.addMetadata, + attempt: function (n, r) { + try { + var i = + Array.prototype.slice.call( + arguments, + 2 + ); + return n.apply(r || this, i); + } catch (e) { + throw ( + (t.onError("catch", e), + h.wrapError(e)) + ); + } + }, + configure: function (e) { + return !t.hasInstalled && + t.hasEnabled + ? (t.warn( + "agent must be installed" + ), + !1) + : t.config.setCurrent(e); + }, + hash: "fb090f9249a14e8440f317f57bd82ec8d6ea32a4", + isInstalled: function () { + return t.hasInstalled; + }, + install: this.install, + removeMetadata: + this.metadata.removeMetadata, + track: function (e) { + if (!t.hasInstalled && t.hasEnabled) + t.warn( + "agent must be installed" + ); + else { + var n = h.isError(e) + ? e.message + : t.serialize(e); + if (((e = e || {}), !e.stack)) + try { + throw Error(n); + } catch (r) { + e = r; + } + t.onError("direct", e); + } + }, + version: "3.10.1", + watch: function (n, r) { + return function () { + try { + var i = + Array.prototype.slice.call( + arguments, + 0 + ); + return n.apply( + r || this, + i + ); + } catch (e) { + throw ( + (t.onError("catch", e), + h.wrapError(e)) + ); + } + }; + }, + watchAll: function (t) { + var e, + n = Array.prototype.slice.call( + arguments, + 1 + ); + for (e in t) + "function" !== typeof t[e] || + h.contains(n, e) || + (t[e] = this.watch( + t[e], + t + )); + return t; + }, + }; + return ( + new a( + h, + t.log, + t.onError, + t.onFault, + t.serialize, + n, + o.prototype.defaults.console + ), + n + ); + }, + onError: (function () { + var t, + n = !1; + return function (r, i, o) { + if ( + this.hasInstalled && + this.hasEnabled && + h.isBrowserSupported() + ) + try { + if ( + ((o = o || { + bindStack: null, + bindTime: null, + force: !1, + }), + (i && h.isError(i)) || + (i = { + name: "Error", + message: this.serialize( + i, + o.force + ), + }), + -1 === + i.message.indexOf( + "TrackJS Caught" + )) + ) + if ( + n && + -1 !== + i.message.indexOf( + "Script error" + ) + ) + n = !1; + else { + var a = h.defaultsDeep( + {}, + { + agentPlatform: + h.isBrowser + ? "browser" + : "worker", + bindStack: + o.bindStack, + bindTime: + o.bindTime, + column: + i.column || + i.columnNumber, + console: + this.windowConsoleWatcher.report(), + customer: + this.customer.report(), + entry: r, + environment: + this.environment.report(), + file: + i.file || + i.fileName, + line: + i.line || + i.lineNumber, + message: i.message, + metadata: + this.metadata.report(), + nav: this.navigationWatcher.report(), + network: + this.networkWatcher.report(), + url: ( + e.location || "" + ).toString(), + stack: i.stack, + timestamp: + this.util.isoNow(), + visitor: + this.visitorWatcher.report(), + version: "3.10.1", + } + ); + if (!o.force) + try { + if ( + !this.config.current.onError( + a, + i + ) + ) + return; + } catch (c) { + a.console.push({ + timestamp: + this.util.isoNow(), + severity: + "error", + message: + c.message, + }); + var s = this; + setTimeout( + function () { + s.onError( + "catch", + c, + { + force: !0, + } + ); + }, + 0 + ); + } + if ( + this.config.current + .dedupe + ) { + var u = ( + a.message + a.stack + ).substr(0, 1e4); + if (u === t) return; + t = u; + } + (function () { + function t() { + var t = 0; + return ( + h.forEach( + a.console, + function ( + e + ) { + t += ( + e.message || + "" + ) + .length; + } + ), + 8e4 <= t + ); + } + for ( + var e = 0; + t() && + e < + a.console + .length; + + ) + (a.console[ + e + ].message = h.truncate( + a.console[e] + .message, + 1e3 + )), + e++; + })(), + this.log.clear(), + setTimeout(function () { + n = !1; + }), + (n = !0), + this.transmitter.sendError( + a, + this.customer.token + ); + } + } catch (c) { + this.onFault(c); + } + }; + })(), + onFault: function (t) { + var e = this.transmitter || new p(); + (t = t || {}), + (t = { + token: (this.customer || {}).token, + file: t.file || t.fileName, + msg: t.message || "unknown", + stack: (t.stack || "unknown").substr( + 0, + 1e3 + ), + url: this.window.location, + v: "3.10.1", + h: "fb090f9249a14e8440f317f57bd82ec8d6ea32a4", + x: this.util.uuid(), + }), + e.sendTrackerFault(t); + }, + serialize: function (t, e) { + if ( + this.hasInstalled && + this.config.current.serialize && + !e + ) + try { + return this.config.current.serialize(t); + } catch (h) { + this.onError("catch", h, { force: !0 }); + } + return o.prototype.defaults.serialize(t); + }, + warn: function (t) { + h.has(e, "console.warn") && + e.console.warn("TrackJS: " + t); + }, + }), + new m().pub() + ); + })( + "undefined" === typeof self ? void 0 : self, + "undefined" === typeof document ? void 0 : document + ); + }.call(this, n("f28c"))); + }, + "38fd": function (t, e, n) { + var r = n("69a8"), + i = n("4bf8"), + o = n("613b")("IE_PROTO"), + a = Object.prototype; + t.exports = + Object.getPrototypeOf || + function (t) { + return ( + (t = i(t)), + r(t, o) + ? t[o] + : "function" == typeof t.constructor && + t instanceof t.constructor + ? t.constructor.prototype + : t instanceof Object + ? a + : null + ); + }; + }, + 3904: function (t, e, n) { + var r = n("8ce0"); + t.exports = function (t, e, n) { + for (var i in e) n && t[i] ? (t[i] = e[i]) : r(t, i, e[i]); + return t; + }; + }, + "3a54": function (t, e, n) { + "use strict"; + Object.defineProperty(e, "__esModule", { value: !0 }), + (e.default = void 0); + var r = n("78ef"), + i = (0, r.regex)("alphaNum", /^[a-zA-Z0-9]*$/); + e.default = i; + }, + "3adc": function (t, e, n) { + var r = n("0f89"), + i = n("a47f"), + o = n("2ea1"), + a = Object.defineProperty; + e.f = n("7d95") + ? Object.defineProperty + : function (t, e, n) { + if ((r(t), (e = o(e, !0)), r(n), i)) + try { + return a(t, e, n); + } catch (s) {} + if ("get" in n || "set" in n) + throw TypeError("Accessors not supported!"); + return "value" in n && (t[e] = n.value), t; + }; + }, + "41a0": function (t, e, n) { + "use strict"; + var r = n("2aeb"), + i = n("4630"), + o = n("7f20"), + a = {}; + n("32e9")(a, n("2b4c")("iterator"), function () { + return this; + }), + (t.exports = function (t, e, n) { + (t.prototype = r(a, { next: i(1, n) })), + o(t, e + " Iterator"); + }); + }, + "436c": function (t, e, n) { + var r = n("1b55")("iterator"), + i = !1; + try { + var o = [7][r](); + (o["return"] = function () { + i = !0; + }), + Array.from(o, function () { + throw 2; + }); + } catch (a) {} + t.exports = function (t, e) { + if (!e && !i) return !1; + var n = !1; + try { + var o = [7], + s = o[r](); + (s.next = function () { + return { done: (n = !0) }; + }), + (o[r] = function () { + return s; + }), + t(o); + } catch (a) {} + return n; + }; + }, + "43c8": function (t, e) { + var n = {}.hasOwnProperty; + t.exports = function (t, e) { + return n.call(t, e); + }; + }, + "456d": function (t, e, n) { + var r = n("4bf8"), + i = n("0d58"); + n("5eda")("keys", function () { + return function (t) { + return i(r(t)); + }; + }); + }, + 4588: function (t, e) { + var n = Math.ceil, + r = Math.floor; + t.exports = function (t) { + return isNaN((t = +t)) ? 0 : (t > 0 ? r : n)(t); + }; + }, + "45b8": function (t, e, n) { + "use strict"; + Object.defineProperty(e, "__esModule", { value: !0 }), + (e.default = void 0); + var r = n("78ef"), + i = (0, r.regex)("numeric", /^[0-9]*$/); + e.default = i; + }, + 4630: function (t, e) { + t.exports = function (t, e) { + return { + enumerable: !(1 & t), + configurable: !(2 & t), + writable: !(4 & t), + value: e, + }; + }; + }, + "46bc": function (t, e, n) { + "use strict"; + Object.defineProperty(e, "__esModule", { value: !0 }), + (e.default = void 0); + var r = n("78ef"), + i = function (t) { + return (0, r.withParams)( + { type: "maxValue", max: t }, + function (e) { + return ( + !(0, r.req)(e) || + ((!/\s/.test(e) || e instanceof Date) && + +e <= +t) + ); + } + ); + }; + e.default = i; + }, + "4a59": function (t, e, n) { + var r = n("9b43"), + i = n("1fa8"), + o = n("33a4"), + a = n("cb7c"), + s = n("9def"), + u = n("27ee"), + c = {}, + f = {}; + e = t.exports = function (t, e, n, l, d) { + var p, + h, + v, + y, + m = d + ? function () { + return t; + } + : u(t), + g = r(n, l, e ? 2 : 1), + b = 0; + if ("function" != typeof m) + throw TypeError(t + " is not iterable!"); + if (o(m)) { + for (p = s(t.length); p > b; b++) + if ( + ((y = e ? g(a((h = t[b]))[0], h[1]) : g(t[b])), + y === c || y === f) + ) + return y; + } else + for (v = m.call(t); !(h = v.next()).done; ) + if (((y = i(v, g, h.value, e)), y === c || y === f)) + return y; + }; + (e.BREAK = c), (e.RETURN = f); + }, + "4aa6": function (t, e, n) { + t.exports = n("af7e"); + }, + "4bf8": function (t, e, n) { + var r = n("be13"); + t.exports = function (t) { + return Object(r(t)); + }; + }, + "4d16": function (t, e, n) { + t.exports = n("a438"); + }, + "4e2b": function (t, e, n) { + "use strict"; + n.d(e, "a", function () { + return u; + }); + var r = n("4aa6"), + i = n.n(r), + o = n("85f2"), + a = n.n(o), + s = n("54b6"); + function u(t, e) { + if ("function" !== typeof e && null !== e) + throw new TypeError( + "Super expression must either be null or a function" + ); + (t.prototype = i()(e && e.prototype, { + constructor: { value: t, writable: !0, configurable: !0 }, + })), + a()(t, "prototype", { writable: !1 }), + e && Object(s["a"])(t, e); + } + }, + "50e9": function (t, e, n) { + var r = n("d13f"); + r(r.S, "Object", { create: n("7108") }); + }, + 5147: function (t, e, n) { + var r = n("2b4c")("match"); + t.exports = function (t) { + var e = /./; + try { + "/./"[t](e); + } catch (n) { + try { + return (e[r] = !1), !"/./"[t](e); + } catch (i) {} + } + return !0; + }; + }, + "520a": function (t, e, n) { + "use strict"; + var r = n("0bfb"), + i = RegExp.prototype.exec, + o = String.prototype.replace, + a = i, + s = "lastIndex", + u = (function () { + var t = /a/, + e = /b*/g; + return ( + i.call(t, "a"), i.call(e, "a"), 0 !== t[s] || 0 !== e[s] + ); + })(), + c = void 0 !== /()??/.exec("")[1], + f = u || c; + f && + (a = function (t) { + var e, + n, + a, + f, + l = this; + return ( + c && + (n = new RegExp( + "^" + l.source + "$(?!\\s)", + r.call(l) + )), + u && (e = l[s]), + (a = i.call(l, t)), + u && a && (l[s] = l.global ? a.index + a[0].length : e), + c && + a && + a.length > 1 && + o.call(a[0], n, function () { + for (f = 1; f < arguments.length - 2; f++) + void 0 === arguments[f] && (a[f] = void 0); + }), + a + ); + }), + (t.exports = a); + }, + "52a7": function (t, e) { + e.f = {}.propertyIsEnumerable; + }, + "54b6": function (t, e, n) { + "use strict"; + n.d(e, "a", function () { + return o; + }); + var r = n("4d16"), + i = n.n(r); + function o(t, e) { + return ( + (o = + i.a || + function (t, e) { + return (t.__proto__ = e), t; + }), + o(t, e) + ); + } + }, + "551c": function (t, e, n) { + "use strict"; + var r, + i, + o, + a, + s = n("2d00"), + u = n("7726"), + c = n("9b43"), + f = n("23c6"), + l = n("5ca1"), + d = n("d3f4"), + p = n("d8e8"), + h = n("f605"), + v = n("4a59"), + y = n("ebd6"), + m = n("1991").set, + g = n("8079")(), + b = n("a5b8"), + _ = n("9c80"), + w = n("a25f"), + x = n("bcaa"), + k = "Promise", + S = u.TypeError, + E = u.process, + O = E && E.versions, + T = (O && O.v8) || "", + A = u[k], + C = "process" == f(E), + j = function () {}, + I = (i = b.f), + M = !!(function () { + try { + var t = A.resolve(1), + e = ((t.constructor = {})[n("2b4c")("species")] = + function (t) { + t(j, j); + }); + return ( + (C || "function" == typeof PromiseRejectionEvent) && + t.then(j) instanceof e && + 0 !== T.indexOf("6.6") && + -1 === w.indexOf("Chrome/66") + ); + } catch (r) {} + })(), + L = function (t) { + var e; + return !(!d(t) || "function" != typeof (e = t.then)) && e; + }, + P = function (t, e) { + if (!t._n) { + t._n = !0; + var n = t._c; + g(function () { + var r = t._v, + i = 1 == t._s, + o = 0, + a = function (e) { + var n, + o, + a, + s = i ? e.ok : e.fail, + u = e.resolve, + c = e.reject, + f = e.domain; + try { + s + ? (i || + (2 == t._h && $(t), + (t._h = 1)), + !0 === s + ? (n = r) + : (f && f.enter(), + (n = s(r)), + f && (f.exit(), (a = !0))), + n === e.promise + ? c(S("Promise-chain cycle")) + : (o = L(n)) + ? o.call(n, u, c) + : u(n)) + : c(r); + } catch (l) { + f && !a && f.exit(), c(l); + } + }; + while (n.length > o) a(n[o++]); + (t._c = []), (t._n = !1), e && !t._h && N(t); + }); + } + }, + N = function (t) { + m.call(u, function () { + var e, + n, + r, + i = t._v, + o = R(t); + if ( + (o && + ((e = _(function () { + C + ? E.emit("unhandledRejection", i, t) + : (n = u.onunhandledrejection) + ? n({ promise: t, reason: i }) + : (r = u.console) && + r.error && + r.error( + "Unhandled promise rejection", + i + ); + })), + (t._h = C || R(t) ? 2 : 1)), + (t._a = void 0), + o && e.e) + ) + throw e.v; + }); + }, + R = function (t) { + return 1 !== t._h && 0 === (t._a || t._c).length; + }, + $ = function (t) { + m.call(u, function () { + var e; + C + ? E.emit("rejectionHandled", t) + : (e = u.onrejectionhandled) && + e({ promise: t, reason: t._v }); + }); + }, + D = function (t) { + var e = this; + e._d || + ((e._d = !0), + (e = e._w || e), + (e._v = t), + (e._s = 2), + e._a || (e._a = e._c.slice()), + P(e, !0)); + }, + F = function (t) { + var e, + n = this; + if (!n._d) { + (n._d = !0), (n = n._w || n); + try { + if (n === t) + throw S("Promise can't be resolved itself"); + (e = L(t)) + ? g(function () { + var r = { _w: n, _d: !1 }; + try { + e.call(t, c(F, r, 1), c(D, r, 1)); + } catch (i) { + D.call(r, i); + } + }) + : ((n._v = t), (n._s = 1), P(n, !1)); + } catch (r) { + D.call({ _w: n, _d: !1 }, r); + } + } + }; + M || + ((A = function (t) { + h(this, A, k, "_h"), p(t), r.call(this); + try { + t(c(F, this, 1), c(D, this, 1)); + } catch (e) { + D.call(this, e); + } + }), + (r = function (t) { + (this._c = []), + (this._a = void 0), + (this._s = 0), + (this._d = !1), + (this._v = void 0), + (this._h = 0), + (this._n = !1); + }), + (r.prototype = n("dcbc")(A.prototype, { + then: function (t, e) { + var n = I(y(this, A)); + return ( + (n.ok = "function" != typeof t || t), + (n.fail = "function" == typeof e && e), + (n.domain = C ? E.domain : void 0), + this._c.push(n), + this._a && this._a.push(n), + this._s && P(this, !1), + n.promise + ); + }, + catch: function (t) { + return this.then(void 0, t); + }, + })), + (o = function () { + var t = new r(); + (this.promise = t), + (this.resolve = c(F, t, 1)), + (this.reject = c(D, t, 1)); + }), + (b.f = I = + function (t) { + return t === A || t === a ? new o(t) : i(t); + })), + l(l.G + l.W + l.F * !M, { Promise: A }), + n("7f20")(A, k), + n("7a56")(k), + (a = n("8378")[k]), + l(l.S + l.F * !M, k, { + reject: function (t) { + var e = I(this), + n = e.reject; + return n(t), e.promise; + }, + }), + l(l.S + l.F * (s || !M), k, { + resolve: function (t) { + return x(s && this === a ? A : this, t); + }, + }), + l( + l.S + + l.F * + !( + M && + n("5cc5")(function (t) { + A.all(t)["catch"](j); + }) + ), + k, + { + all: function (t) { + var e = this, + n = I(e), + r = n.resolve, + i = n.reject, + o = _(function () { + var n = [], + o = 0, + a = 1; + v(t, !1, function (t) { + var s = o++, + u = !1; + n.push(void 0), + a++, + e.resolve(t).then(function (t) { + u || + ((u = !0), + (n[s] = t), + --a || r(n)); + }, i); + }), + --a || r(n); + }); + return o.e && i(o.v), n.promise; + }, + race: function (t) { + var e = this, + n = I(e), + r = n.reject, + i = _(function () { + v(t, !1, function (t) { + e.resolve(t).then(n.resolve, r); + }); + }); + return i.e && r(i.v), n.promise; + }, + } + ); + }, + 5537: function (t, e, n) { + var r = n("8378"), + i = n("7726"), + o = "__core-js_shared__", + a = i[o] || (i[o] = {}); + (t.exports = function (t, e) { + return a[t] || (a[t] = void 0 !== e ? e : {}); + })("versions", []).push({ + version: r.version, + mode: n("2d00") ? "pure" : "global", + copyright: "© 2019 Denis Pushkarev (zloirock.ru)", + }); + }, + "560b": function (t, e, n) { + var r = n("bc25"), + i = n("9c93"), + o = n("c227"), + a = n("0f89"), + s = n("a5ab"), + u = n("f159"), + c = {}, + f = {}; + e = t.exports = function (t, e, n, l, d) { + var p, + h, + v, + y, + m = d + ? function () { + return t; + } + : u(t), + g = r(n, l, e ? 2 : 1), + b = 0; + if ("function" != typeof m) + throw TypeError(t + " is not iterable!"); + if (o(m)) { + for (p = s(t.length); p > b; b++) + if ( + ((y = e ? g(a((h = t[b]))[0], h[1]) : g(t[b])), + y === c || y === f) + ) + return y; + } else + for (v = m.call(t); !(h = v.next()).done; ) + if (((y = i(v, g, h.value, e)), y === c || y === f)) + return y; + }; + (e.BREAK = c), (e.RETURN = f); + }, + "565d": function (t, e, n) { + var r = n("6a9b"), + i = n("d876").f, + o = {}.toString, + a = + "object" == typeof window && + window && + Object.getOwnPropertyNames + ? Object.getOwnPropertyNames(window) + : [], + s = function (t) { + try { + return i(t); + } catch (e) { + return a.slice(); + } + }; + t.exports.f = function (t) { + return a && "[object Window]" == o.call(t) ? s(t) : i(r(t)); + }; + }, + "57f7": function (t, e, n) { + n("93c4"), n("6109"), (t.exports = n("a7d3").Array.from); + }, + 5927: function (t, e, n) { + n("93c4"), n("b42c"), (t.exports = n("fda1").f("iterator")); + }, + "5ca1": function (t, e, n) { + var r = n("7726"), + i = n("8378"), + o = n("32e9"), + a = n("2aba"), + s = n("9b43"), + u = "prototype", + c = function (t, e, n) { + var f, + l, + d, + p, + h = t & c.F, + v = t & c.G, + y = t & c.S, + m = t & c.P, + g = t & c.B, + b = v ? r : y ? r[e] || (r[e] = {}) : (r[e] || {})[u], + _ = v ? i : i[e] || (i[e] = {}), + w = _[u] || (_[u] = {}); + for (f in (v && (n = e), n)) + (l = !h && b && void 0 !== b[f]), + (d = (l ? b : n)[f]), + (p = + g && l + ? s(d, r) + : m && "function" == typeof d + ? s(Function.call, d) + : d), + b && a(b, f, d, t & c.U), + _[f] != d && o(_, f, p), + m && w[f] != d && (w[f] = d); + }; + (r.core = i), + (c.F = 1), + (c.G = 2), + (c.S = 4), + (c.P = 8), + (c.B = 16), + (c.W = 32), + (c.U = 64), + (c.R = 128), + (t.exports = c); + }, + "5cc5": function (t, e, n) { + var r = n("2b4c")("iterator"), + i = !1; + try { + var o = [7][r](); + (o["return"] = function () { + i = !0; + }), + Array.from(o, function () { + throw 2; + }); + } catch (a) {} + t.exports = function (t, e) { + if (!e && !i) return !1; + var n = !1; + try { + var o = [7], + s = o[r](); + (s.next = function () { + return { done: (n = !0) }; + }), + (o[r] = function () { + return s; + }), + t(o); + } catch (a) {} + return n; + }; + }, + "5ce7": function (t, e, n) { + "use strict"; + var r = n("7108"), + i = n("f845"), + o = n("c0d8"), + a = {}; + n("8ce0")(a, n("1b55")("iterator"), function () { + return this; + }), + (t.exports = function (t, e, n) { + (t.prototype = r(a, { next: i(1, n) })), + o(t, e + " Iterator"); + }); + }, + "5d58": function (t, e, n) { + t.exports = n("5927"); + }, + "5d75": function (t, e, n) { + "use strict"; + Object.defineProperty(e, "__esModule", { value: !0 }), + (e.default = void 0); + var r = n("78ef"), + i = + /(^$|^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$)/, + o = (0, r.regex)("email", i); + e.default = o; + }, + "5d8f": function (t, e, n) { + var r = n("7772")("keys"), + i = n("7b00"); + t.exports = function (t) { + return r[t] || (r[t] = i(t)); + }; + }, + "5db3": function (t, e, n) { + "use strict"; + Object.defineProperty(e, "__esModule", { value: !0 }), + (e.default = void 0); + var r = n("78ef"), + i = function (t) { + return (0, r.withParams)( + { type: "minLength", min: t }, + function (e) { + return !(0, r.req)(e) || (0, r.len)(e) >= t; + } + ); + }; + e.default = i; + }, + "5dbc": function (t, e, n) { + var r = n("d3f4"), + i = n("8b97").set; + t.exports = function (t, e, n) { + var o, + a = e.constructor; + return ( + a !== n && + "function" == typeof a && + (o = a.prototype) !== n.prototype && + r(o) && + i && + i(t, o), + t + ); + }; + }, + "5df3": function (t, e, n) { + "use strict"; + var r = n("02f4")(!0); + n("01f9")( + String, + "String", + function (t) { + (this._t = String(t)), (this._i = 0); + }, + function () { + var t, + e = this._t, + n = this._i; + return n >= e.length + ? { value: void 0, done: !0 } + : ((t = r(e, n)), + (this._i += t.length), + { value: t, done: !1 }); + } + ); + }, + "5eda": function (t, e, n) { + var r = n("5ca1"), + i = n("8378"), + o = n("79e5"); + t.exports = function (t, e) { + var n = (i.Object || {})[t] || Object[t], + a = {}; + (a[t] = e(n)), + r( + r.S + + r.F * + o(function () { + n(1); + }), + "Object", + a + ); + }; + }, + "5f1b": function (t, e, n) { + "use strict"; + var r = n("23c6"), + i = RegExp.prototype.exec; + t.exports = function (t, e) { + var n = t.exec; + if ("function" === typeof n) { + var o = n.call(t, e); + if ("object" !== typeof o) + throw new TypeError( + "RegExp exec method returned something other than an Object or null" + ); + return o; + } + if ("RegExp" !== r(t)) + throw new TypeError( + "RegExp#exec called on incompatible receiver" + ); + return i.call(t, e); + }; + }, + 6109: function (t, e, n) { + "use strict"; + var r = n("bc25"), + i = n("d13f"), + o = n("0185"), + a = n("9c93"), + s = n("c227"), + u = n("a5ab"), + c = n("b3ec"), + f = n("f159"); + i( + i.S + + i.F * + !n("436c")(function (t) { + Array.from(t); + }), + "Array", + { + from: function (t) { + var e, + n, + i, + l, + d = o(t), + p = "function" == typeof this ? this : Array, + h = arguments.length, + v = h > 1 ? arguments[1] : void 0, + y = void 0 !== v, + m = 0, + g = f(d); + if ( + (y && (v = r(v, h > 2 ? arguments[2] : void 0, 2)), + void 0 == g || (p == Array && s(g))) + ) + for (e = u(d.length), n = new p(e); e > m; m++) + c(n, m, y ? v(d[m], m) : d[m]); + else + for ( + l = g.call(d), n = new p(); + !(i = l.next()).done; + m++ + ) + c( + n, + m, + y ? a(l, v, [i.value, m], !0) : i.value + ); + return (n.length = m), n; + }, + } + ); + }, + "613b": function (t, e, n) { + var r = n("5537")("keys"), + i = n("ca5a"); + t.exports = function (t) { + return r[t] || (r[t] = i(t)); + }; + }, + 6235: function (t, e, n) { + "use strict"; + Object.defineProperty(e, "__esModule", { value: !0 }), + (e.default = void 0); + var r = n("78ef"), + i = (0, r.regex)("alpha", /^[a-zA-Z]*$/); + e.default = i; + }, + "626a": function (t, e, n) { + var r = n("2d95"); + t.exports = Object("z").propertyIsEnumerable(0) + ? Object + : function (t) { + return "String" == r(t) ? t.split("") : Object(t); + }; + }, + "626e": function (t, e, n) { + var r = n("d74e"), + i = n("f845"), + o = n("6a9b"), + a = n("2ea1"), + s = n("43c8"), + u = n("a47f"), + c = Object.getOwnPropertyDescriptor; + e.f = n("7d95") + ? c + : function (t, e) { + if (((t = o(t)), (e = a(e, !0)), u)) + try { + return c(t, e); + } catch (n) {} + if (s(t, e)) return i(!r.f.call(t, e), t[e]); + }; + }, + 6277: function (t, e, n) { + var r = n("7b00")("meta"), + i = n("6f8a"), + o = n("43c8"), + a = n("3adc").f, + s = 0, + u = + Object.isExtensible || + function () { + return !0; + }, + c = !n("d782")(function () { + return u(Object.preventExtensions({})); + }), + f = function (t) { + a(t, r, { value: { i: "O" + ++s, w: {} } }); + }, + l = function (t, e) { + if (!i(t)) + return "symbol" == typeof t + ? t + : ("string" == typeof t ? "S" : "P") + t; + if (!o(t, r)) { + if (!u(t)) return "F"; + if (!e) return "E"; + f(t); + } + return t[r].i; + }, + d = function (t, e) { + if (!o(t, r)) { + if (!u(t)) return !0; + if (!e) return !1; + f(t); + } + return t[r].w; + }, + p = function (t) { + return c && h.NEED && u(t) && !o(t, r) && f(t), t; + }, + h = (t.exports = { + KEY: r, + NEED: !1, + fastKey: l, + getWeak: d, + onFreeze: p, + }); + }, + 6417: function (t, e, n) { + "use strict"; + Object.defineProperty(e, "__esModule", { value: !0 }), + (e.default = void 0); + var r = n("78ef"), + i = function (t) { + return (0, r.withParams)({ type: "not" }, function (e, n) { + return !(0, r.req)(e) || !t.call(this, e, n); + }); + }; + e.default = i; + }, + 6494: function (t, e, n) { + var r = n("6f8a"), + i = n("0f89"), + o = function (t, e) { + if ((i(t), !r(e) && null !== e)) + throw TypeError(e + ": can't set as prototype!"); + }; + t.exports = { + set: + Object.setPrototypeOf || + ("__proto__" in {} + ? (function (t, e, r) { + try { + (r = n("bc25")( + Function.call, + n("626e").f(Object.prototype, "__proto__") + .set, + 2 + )), + r(t, []), + (e = !(t instanceof Array)); + } catch (i) { + e = !0; + } + return function (t, n) { + return ( + o(t, n), + e ? (t.__proto__ = n) : r(t, n), + t + ); + }; + })({}, !1) + : void 0), + check: o, + }; + }, + "67ab": function (t, e, n) { + var r = n("ca5a")("meta"), + i = n("d3f4"), + o = n("69a8"), + a = n("86cc").f, + s = 0, + u = + Object.isExtensible || + function () { + return !0; + }, + c = !n("79e5")(function () { + return u(Object.preventExtensions({})); + }), + f = function (t) { + a(t, r, { value: { i: "O" + ++s, w: {} } }); + }, + l = function (t, e) { + if (!i(t)) + return "symbol" == typeof t + ? t + : ("string" == typeof t ? "S" : "P") + t; + if (!o(t, r)) { + if (!u(t)) return "F"; + if (!e) return "E"; + f(t); + } + return t[r].i; + }, + d = function (t, e) { + if (!o(t, r)) { + if (!u(t)) return !0; + if (!e) return !1; + f(t); + } + return t[r].w; + }, + p = function (t) { + return c && h.NEED && u(t) && !o(t, r) && f(t), t; + }, + h = (t.exports = { + KEY: r, + NEED: !1, + fastKey: l, + getWeak: d, + onFreeze: p, + }); + }, + "67bb": function (t, e, n) { + t.exports = n("b258"); + }, + 6821: function (t, e, n) { + var r = n("626a"), + i = n("be13"); + t.exports = function (t) { + return r(i(t)); + }; + }, + "69a8": function (t, e) { + var n = {}.hasOwnProperty; + t.exports = function (t, e) { + return n.call(t, e); + }; + }, + "6a99": function (t, e, n) { + var r = n("d3f4"); + t.exports = function (t, e) { + if (!r(t)) return t; + var n, i; + if ( + e && + "function" == typeof (n = t.toString) && + !r((i = n.call(t))) + ) + return i; + if ("function" == typeof (n = t.valueOf) && !r((i = n.call(t)))) + return i; + if ( + !e && + "function" == typeof (n = t.toString) && + !r((i = n.call(t))) + ) + return i; + throw TypeError("Can't convert object to primitive value"); + }; + }, + "6a9b": function (t, e, n) { + var r = n("8bab"), + i = n("e5fa"); + t.exports = function (t) { + return r(i(t)); + }; + }, + "6bb5": function (t, e, n) { + "use strict"; + n.d(e, "a", function () { + return s; + }); + var r = n("4d16"), + i = n.n(r), + o = n("061b"), + a = n.n(o); + function s(t) { + return ( + (s = i.a + ? a.a + : function (t) { + return t.__proto__ || a()(t); + }), + s(t) + ); + } + }, + "6d93": function (t, e, n) { + "use strict"; + var r = + ("undefined" !== typeof globalThis && globalThis) || + ("undefined" !== typeof self && self) || + ("undefined" !== typeof r && r), + i = { + searchParams: "URLSearchParams" in r, + iterable: "Symbol" in r && "iterator" in Symbol, + blob: + "FileReader" in r && + "Blob" in r && + (function () { + try { + return new Blob(), !0; + } catch (t) { + return !1; + } + })(), + formData: "FormData" in r, + arrayBuffer: "ArrayBuffer" in r, + }; + function o(t) { + return t && DataView.prototype.isPrototypeOf(t); + } + if (i.arrayBuffer) + var a = [ + "[object Int8Array]", + "[object Uint8Array]", + "[object Uint8ClampedArray]", + "[object Int16Array]", + "[object Uint16Array]", + "[object Int32Array]", + "[object Uint32Array]", + "[object Float32Array]", + "[object Float64Array]", + ], + s = + ArrayBuffer.isView || + function (t) { + return ( + t && + a.indexOf(Object.prototype.toString.call(t)) > + -1 + ); + }; + function u(t) { + if ( + ("string" !== typeof t && (t = String(t)), + /[^a-z0-9\-#$%&'*+.^_`|~!]/i.test(t) || "" === t) + ) + throw new TypeError( + 'Invalid character in header field name: "' + t + '"' + ); + return t.toLowerCase(); + } + function c(t) { + return "string" !== typeof t && (t = String(t)), t; + } + function f(t) { + var e = { + next: function () { + var e = t.shift(); + return { done: void 0 === e, value: e }; + }, + }; + return ( + i.iterable && + (e[Symbol.iterator] = function () { + return e; + }), + e + ); + } + function l(t) { + (this.map = {}), + t instanceof l + ? t.forEach(function (t, e) { + this.append(e, t); + }, this) + : Array.isArray(t) + ? t.forEach(function (t) { + this.append(t[0], t[1]); + }, this) + : t && + Object.getOwnPropertyNames(t).forEach(function (e) { + this.append(e, t[e]); + }, this); + } + function d(t) { + if (t.bodyUsed) + return Promise.reject(new TypeError("Already read")); + t.bodyUsed = !0; + } + function p(t) { + return new Promise(function (e, n) { + (t.onload = function () { + e(t.result); + }), + (t.onerror = function () { + n(t.error); + }); + }); + } + function h(t) { + var e = new FileReader(), + n = p(e); + return e.readAsArrayBuffer(t), n; + } + function v(t) { + var e = new FileReader(), + n = p(e); + return e.readAsText(t), n; + } + function y(t) { + for ( + var e = new Uint8Array(t), n = new Array(e.length), r = 0; + r < e.length; + r++ + ) + n[r] = String.fromCharCode(e[r]); + return n.join(""); + } + function m(t) { + if (t.slice) return t.slice(0); + var e = new Uint8Array(t.byteLength); + return e.set(new Uint8Array(t)), e.buffer; + } + function g() { + return ( + (this.bodyUsed = !1), + (this._initBody = function (t) { + (this.bodyUsed = this.bodyUsed), + (this._bodyInit = t), + t + ? "string" === typeof t + ? (this._bodyText = t) + : i.blob && Blob.prototype.isPrototypeOf(t) + ? (this._bodyBlob = t) + : i.formData && + FormData.prototype.isPrototypeOf(t) + ? (this._bodyFormData = t) + : i.searchParams && + URLSearchParams.prototype.isPrototypeOf(t) + ? (this._bodyText = t.toString()) + : i.arrayBuffer && i.blob && o(t) + ? ((this._bodyArrayBuffer = m(t.buffer)), + (this._bodyInit = new Blob([ + this._bodyArrayBuffer, + ]))) + : i.arrayBuffer && + (ArrayBuffer.prototype.isPrototypeOf(t) || + s(t)) + ? (this._bodyArrayBuffer = m(t)) + : (this._bodyText = t = + Object.prototype.toString.call(t)) + : (this._bodyText = ""), + this.headers.get("content-type") || + ("string" === typeof t + ? this.headers.set( + "content-type", + "text/plain;charset=UTF-8" + ) + : this._bodyBlob && this._bodyBlob.type + ? this.headers.set( + "content-type", + this._bodyBlob.type + ) + : i.searchParams && + URLSearchParams.prototype.isPrototypeOf( + t + ) && + this.headers.set( + "content-type", + "application/x-www-form-urlencoded;charset=UTF-8" + )); + }), + i.blob && + ((this.blob = function () { + var t = d(this); + if (t) return t; + if (this._bodyBlob) + return Promise.resolve(this._bodyBlob); + if (this._bodyArrayBuffer) + return Promise.resolve( + new Blob([this._bodyArrayBuffer]) + ); + if (this._bodyFormData) + throw new Error( + "could not read FormData body as blob" + ); + return Promise.resolve(new Blob([this._bodyText])); + }), + (this.arrayBuffer = function () { + if (this._bodyArrayBuffer) { + var t = d(this); + return ( + t || + (ArrayBuffer.isView(this._bodyArrayBuffer) + ? Promise.resolve( + this._bodyArrayBuffer.buffer.slice( + this._bodyArrayBuffer + .byteOffset, + this._bodyArrayBuffer + .byteOffset + + this._bodyArrayBuffer + .byteLength + ) + ) + : Promise.resolve( + this._bodyArrayBuffer + )) + ); + } + return this.blob().then(h); + })), + (this.text = function () { + var t = d(this); + if (t) return t; + if (this._bodyBlob) return v(this._bodyBlob); + if (this._bodyArrayBuffer) + return Promise.resolve(y(this._bodyArrayBuffer)); + if (this._bodyFormData) + throw new Error( + "could not read FormData body as text" + ); + return Promise.resolve(this._bodyText); + }), + i.formData && + (this.formData = function () { + return this.text().then(x); + }), + (this.json = function () { + return this.text().then(JSON.parse); + }), + this + ); + } + (l.prototype.append = function (t, e) { + (t = u(t)), (e = c(e)); + var n = this.map[t]; + this.map[t] = n ? n + ", " + e : e; + }), + (l.prototype["delete"] = function (t) { + delete this.map[u(t)]; + }), + (l.prototype.get = function (t) { + return (t = u(t)), this.has(t) ? this.map[t] : null; + }), + (l.prototype.has = function (t) { + return this.map.hasOwnProperty(u(t)); + }), + (l.prototype.set = function (t, e) { + this.map[u(t)] = c(e); + }), + (l.prototype.forEach = function (t, e) { + for (var n in this.map) + this.map.hasOwnProperty(n) && + t.call(e, this.map[n], n, this); + }), + (l.prototype.keys = function () { + var t = []; + return ( + this.forEach(function (e, n) { + t.push(n); + }), + f(t) + ); + }), + (l.prototype.values = function () { + var t = []; + return ( + this.forEach(function (e) { + t.push(e); + }), + f(t) + ); + }), + (l.prototype.entries = function () { + var t = []; + return ( + this.forEach(function (e, n) { + t.push([n, e]); + }), + f(t) + ); + }), + i.iterable && + (l.prototype[Symbol.iterator] = l.prototype.entries); + var b = ["DELETE", "GET", "HEAD", "OPTIONS", "POST", "PUT"]; + function _(t) { + var e = t.toUpperCase(); + return b.indexOf(e) > -1 ? e : t; + } + function w(t, e) { + if (!(this instanceof w)) + throw new TypeError( + 'Please use the "new" operator, this DOM object constructor cannot be called as a function.' + ); + e = e || {}; + var n = e.body; + if (t instanceof w) { + if (t.bodyUsed) throw new TypeError("Already read"); + (this.url = t.url), + (this.credentials = t.credentials), + e.headers || (this.headers = new l(t.headers)), + (this.method = t.method), + (this.mode = t.mode), + (this.signal = t.signal), + n || + null == t._bodyInit || + ((n = t._bodyInit), (t.bodyUsed = !0)); + } else this.url = String(t); + if ( + ((this.credentials = + e.credentials || this.credentials || "same-origin"), + (!e.headers && this.headers) || + (this.headers = new l(e.headers)), + (this.method = _(e.method || this.method || "GET")), + (this.mode = e.mode || this.mode || null), + (this.signal = e.signal || this.signal), + (this.referrer = null), + ("GET" === this.method || "HEAD" === this.method) && n) + ) + throw new TypeError( + "Body not allowed for GET or HEAD requests" + ); + if ( + (this._initBody(n), + ("GET" === this.method || "HEAD" === this.method) && + ("no-store" === e.cache || "no-cache" === e.cache)) + ) { + var r = /([?&])_=[^&]*/; + if (r.test(this.url)) + this.url = this.url.replace( + r, + "$1_=" + new Date().getTime() + ); + else { + var i = /\?/; + this.url += + (i.test(this.url) ? "&" : "?") + + "_=" + + new Date().getTime(); + } + } + } + function x(t) { + var e = new FormData(); + return ( + t + .trim() + .split("&") + .forEach(function (t) { + if (t) { + var n = t.split("="), + r = n.shift().replace(/\+/g, " "), + i = n.join("=").replace(/\+/g, " "); + e.append( + decodeURIComponent(r), + decodeURIComponent(i) + ); + } + }), + e + ); + } + function k(t) { + var e = new l(), + n = t.replace(/\r?\n[\t ]+/g, " "); + return ( + n + .split("\r") + .map(function (t) { + return 0 === t.indexOf("\n") + ? t.substr(1, t.length) + : t; + }) + .forEach(function (t) { + var n = t.split(":"), + r = n.shift().trim(); + if (r) { + var i = n.join(":").trim(); + e.append(r, i); + } + }), + e + ); + } + function S(t, e) { + if (!(this instanceof S)) + throw new TypeError( + 'Please use the "new" operator, this DOM object constructor cannot be called as a function.' + ); + e || (e = {}), + (this.type = "default"), + (this.status = void 0 === e.status ? 200 : e.status), + (this.ok = this.status >= 200 && this.status < 300), + (this.statusText = + void 0 === e.statusText ? "" : "" + e.statusText), + (this.headers = new l(e.headers)), + (this.url = e.url || ""), + this._initBody(t); + } + (w.prototype.clone = function () { + return new w(this, { body: this._bodyInit }); + }), + g.call(w.prototype), + g.call(S.prototype), + (S.prototype.clone = function () { + return new S(this._bodyInit, { + status: this.status, + statusText: this.statusText, + headers: new l(this.headers), + url: this.url, + }); + }), + (S.error = function () { + var t = new S(null, { status: 0, statusText: "" }); + return (t.type = "error"), t; + }); + var E = [301, 302, 303, 307, 308]; + S.redirect = function (t, e) { + if (-1 === E.indexOf(e)) + throw new RangeError("Invalid status code"); + return new S(null, { status: e, headers: { location: t } }); + }; + var O = r.DOMException; + try { + new O(); + } catch (A) { + (O = function (t, e) { + (this.message = t), (this.name = e); + var n = Error(t); + this.stack = n.stack; + }), + (O.prototype = Object.create(Error.prototype)), + (O.prototype.constructor = O); + } + function T(t, e) { + return new Promise(function (n, o) { + var a = new w(t, e); + if (a.signal && a.signal.aborted) + return o(new O("Aborted", "AbortError")); + var s = new XMLHttpRequest(); + function u() { + s.abort(); + } + function f(t) { + try { + return "" === t && r.location.href + ? r.location.href + : t; + } catch (e) { + return t; + } + } + (s.onload = function () { + var t = { + status: s.status, + statusText: s.statusText, + headers: k(s.getAllResponseHeaders() || ""), + }; + t.url = + "responseURL" in s + ? s.responseURL + : t.headers.get("X-Request-URL"); + var e = "response" in s ? s.response : s.responseText; + setTimeout(function () { + n(new S(e, t)); + }, 0); + }), + (s.onerror = function () { + setTimeout(function () { + o(new TypeError("Network request failed")); + }, 0); + }), + (s.ontimeout = function () { + setTimeout(function () { + o(new TypeError("Network request failed")); + }, 0); + }), + (s.onabort = function () { + setTimeout(function () { + o(new O("Aborted", "AbortError")); + }, 0); + }), + s.open(a.method, f(a.url), !0), + "include" === a.credentials + ? (s.withCredentials = !0) + : "omit" === a.credentials && + (s.withCredentials = !1), + "responseType" in s && + (i.blob + ? (s.responseType = "blob") + : i.arrayBuffer && + a.headers.get("Content-Type") && + -1 !== + a.headers + .get("Content-Type") + .indexOf( + "application/octet-stream" + ) && + (s.responseType = "arraybuffer")), + !e || + "object" !== typeof e.headers || + e.headers instanceof l + ? a.headers.forEach(function (t, e) { + s.setRequestHeader(e, t); + }) + : Object.getOwnPropertyNames(e.headers).forEach( + function (t) { + s.setRequestHeader(t, c(e.headers[t])); + } + ), + a.signal && + (a.signal.addEventListener("abort", u), + (s.onreadystatechange = function () { + 4 === s.readyState && + a.signal.removeEventListener("abort", u); + })), + s.send( + "undefined" === typeof a._bodyInit + ? null + : a._bodyInit + ); + }); + } + (T.polyfill = !0), + r.fetch || + ((r.fetch = T), + (r.Headers = l), + (r.Request = w), + (r.Response = S)); + }, + "6e1f": function (t, e) { + var n = {}.toString; + t.exports = function (t) { + return n.call(t).slice(8, -1); + }; + }, + "6f8a": function (t, e) { + t.exports = function (t) { + return "object" === typeof t + ? null !== t + : "function" === typeof t; + }; + }, + 7017: function (t, e, n) { + n("85cd"), (t.exports = n("a7d3").Object.getPrototypeOf); + }, + 7108: function (t, e, n) { + var r = n("0f89"), + i = n("f568"), + o = n("0029"), + a = n("5d8f")("IE_PROTO"), + s = function () {}, + u = "prototype", + c = function () { + var t, + e = n("12fd")("iframe"), + r = o.length, + i = "<", + a = ">"; + (e.style.display = "none"), + n("103a").appendChild(e), + (e.src = "javascript:"), + (t = e.contentWindow.document), + t.open(), + t.write( + i + + "script" + + a + + "document.F=Object" + + i + + "/script" + + a + ), + t.close(), + (c = t.F); + while (r--) delete c[u][o[r]]; + return c(); + }; + t.exports = + Object.create || + function (t, e) { + var n; + return ( + null !== t + ? ((s[u] = r(t)), + (n = new s()), + (s[u] = null), + (n[a] = t)) + : (n = c()), + void 0 === e ? n : i(n, e) + ); + }; + }, + 7333: function (t, e, n) { + "use strict"; + var r = n("9e1e"), + i = n("0d58"), + o = n("2621"), + a = n("52a7"), + s = n("4bf8"), + u = n("626a"), + c = Object.assign; + t.exports = + !c || + n("79e5")(function () { + var t = {}, + e = {}, + n = Symbol(), + r = "abcdefghijklmnopqrst"; + return ( + (t[n] = 7), + r.split("").forEach(function (t) { + e[t] = t; + }), + 7 != c({}, t)[n] || Object.keys(c({}, e)).join("") != r + ); + }) + ? function (t, e) { + var n = s(t), + c = arguments.length, + f = 1, + l = o.f, + d = a.f; + while (c > f) { + var p, + h = u(arguments[f++]), + v = l ? i(h).concat(l(h)) : i(h), + y = v.length, + m = 0; + while (y > m) + (p = v[m++]), + (r && !d.call(h, p)) || (n[p] = h[p]); + } + return n; + } + : c; + }, + 7514: function (t, e, n) { + "use strict"; + var r = n("5ca1"), + i = n("0a49")(5), + o = "find", + a = !0; + o in [] && + Array(1)[o](function () { + a = !1; + }), + r(r.P + r.F * a, "Array", { + find: function (t) { + return i( + this, + t, + arguments.length > 1 ? arguments[1] : void 0 + ); + }, + }), + n("9c6c")(o); + }, + "75fc": function (t, e, n) { + "use strict"; + var r = n("a745"), + i = n.n(r); + function o(t, e) { + (null == e || e > t.length) && (e = t.length); + for (var n = 0, r = new Array(e); n < e; n++) r[n] = t[n]; + return r; + } + function a(t) { + if (i()(t)) return o(t); + } + var s = n("67bb"), + u = n.n(s), + c = n("5d58"), + f = n.n(c), + l = n("774e"), + d = n.n(l); + function p(t) { + if ( + ("undefined" !== typeof u.a && null != t[f.a]) || + null != t["@@iterator"] + ) + return d()(t); + } + function h(t, e) { + if (t) { + if ("string" === typeof t) return o(t, e); + var n = Object.prototype.toString.call(t).slice(8, -1); + return ( + "Object" === n && + t.constructor && + (n = t.constructor.name), + "Map" === n || "Set" === n + ? d()(t) + : "Arguments" === n || + /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n) + ? o(t, e) + : void 0 + ); + } + } + function v() { + throw new TypeError( + "Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method." + ); + } + function y(t) { + return a(t) || p(t) || h(t) || v(); + } + n.d(e, "a", function () { + return y; + }); + }, + 7633: function (t, e, n) { + var r = n("2695"), + i = n("0029"); + t.exports = + Object.keys || + function (t) { + return r(t, i); + }; + }, + 7726: function (t, e) { + var n = (t.exports = + "undefined" != typeof window && window.Math == Math + ? window + : "undefined" != typeof self && self.Math == Math + ? self + : Function("return this")()); + "number" == typeof __g && (__g = n); + }, + "772d": function (t, e, n) { + "use strict"; + Object.defineProperty(e, "__esModule", { value: !0 }), + (e.default = void 0); + var r = n("78ef"), + i = + /^(?:(?:https?|ftp):\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:[/?#]\S*)?$/i, + o = (0, r.regex)("url", i); + e.default = o; + }, + "774e": function (t, e, n) { + t.exports = n("57f7"); + }, + 7772: function (t, e, n) { + var r = n("a7d3"), + i = n("da3c"), + o = "__core-js_shared__", + a = i[o] || (i[o] = {}); + (t.exports = function (t, e) { + return a[t] || (a[t] = void 0 !== e ? e : {}); + })("versions", []).push({ + version: r.version, + mode: n("b457") ? "pure" : "global", + copyright: "© 2020 Denis Pushkarev (zloirock.ru)", + }); + }, + "77f1": function (t, e, n) { + var r = n("4588"), + i = Math.max, + o = Math.min; + t.exports = function (t, e) { + return (t = r(t)), t < 0 ? i(t + e, 0) : o(t, e); + }; + }, + "78ef": function (t, e, n) { + "use strict"; + Object.defineProperty(e, "__esModule", { value: !0 }), + Object.defineProperty(e, "withParams", { + enumerable: !0, + get: function () { + return r.default; + }, + }), + (e.regex = e.ref = e.len = e.req = void 0); + var r = i(n("8750")); + function i(t) { + return t && t.__esModule ? t : { default: t }; + } + function o(t) { + return ( + (o = + "function" === typeof Symbol && + "symbol" === typeof Symbol.iterator + ? function (t) { + return typeof t; + } + : function (t) { + return t && + "function" === typeof Symbol && + t.constructor === Symbol && + t !== Symbol.prototype + ? "symbol" + : typeof t; + }), + o(t) + ); + } + var a = function (t) { + if (Array.isArray(t)) return !!t.length; + if (void 0 === t || null === t) return !1; + if (!1 === t) return !0; + if (t instanceof Date) return !isNaN(t.getTime()); + if ("object" === o(t)) { + for (var e in t) return !0; + return !1; + } + return !!String(t).length; + }; + e.req = a; + var s = function (t) { + return Array.isArray(t) + ? t.length + : "object" === o(t) + ? Object.keys(t).length + : String(t).length; + }; + e.len = s; + var u = function (t, e, n) { + return "function" === typeof t ? t.call(e, n) : n[t]; + }; + e.ref = u; + var c = function (t, e) { + return (0, r.default)({ type: t }, function (t) { + return !a(t) || e.test(t); + }); + }; + e.regex = c; + }, + "79e5": function (t, e) { + t.exports = function (t) { + try { + return !!t(); + } catch (e) { + return !0; + } + }; + }, + "7a56": function (t, e, n) { + "use strict"; + var r = n("7726"), + i = n("86cc"), + o = n("9e1e"), + a = n("2b4c")("species"); + t.exports = function (t) { + var e = r[t]; + o && + e && + !e[a] && + i.f(e, a, { + configurable: !0, + get: function () { + return this; + }, + }); + }; + }, + "7b00": function (t, e) { + var n = 0, + r = Math.random(); + t.exports = function (t) { + return "Symbol(".concat( + void 0 === t ? "" : t, + ")_", + (++n + r).toString(36) + ); + }; + }, + "7d8a": function (t, e, n) { + var r = n("6e1f"), + i = n("1b55")("toStringTag"), + o = + "Arguments" == + r( + (function () { + return arguments; + })() + ), + a = function (t, e) { + try { + return t[e]; + } catch (n) {} + }; + t.exports = function (t) { + var e, n, s; + return void 0 === t + ? "Undefined" + : null === t + ? "Null" + : "string" == typeof (n = a((e = Object(t)), i)) + ? n + : o + ? r(e) + : "Object" == (s = r(e)) && "function" == typeof e.callee + ? "Arguments" + : s; + }; + }, + "7d95": function (t, e, n) { + t.exports = !n("d782")(function () { + return ( + 7 != + Object.defineProperty({}, "a", { + get: function () { + return 7; + }, + }).a + ); + }); + }, + "7f20": function (t, e, n) { + var r = n("86cc").f, + i = n("69a8"), + o = n("2b4c")("toStringTag"); + t.exports = function (t, e, n) { + t && + !i((t = n ? t : t.prototype), o) && + r(t, o, { configurable: !0, value: e }); + }; + }, + 8079: function (t, e, n) { + var r = n("7726"), + i = n("1991").set, + o = r.MutationObserver || r.WebKitMutationObserver, + a = r.process, + s = r.Promise, + u = "process" == n("2d95")(a); + t.exports = function () { + var t, + e, + n, + c = function () { + var r, i; + u && (r = a.domain) && r.exit(); + while (t) { + (i = t.fn), (t = t.next); + try { + i(); + } catch (o) { + throw (t ? n() : (e = void 0), o); + } + } + (e = void 0), r && r.enter(); + }; + if (u) + n = function () { + a.nextTick(c); + }; + else if (!o || (r.navigator && r.navigator.standalone)) + if (s && s.resolve) { + var f = s.resolve(void 0); + n = function () { + f.then(c); + }; + } else + n = function () { + i.call(r, c); + }; + else { + var l = !0, + d = document.createTextNode(""); + new o(c).observe(d, { characterData: !0 }), + (n = function () { + d.data = l = !l; + }); + } + return function (r) { + var i = { fn: r, next: void 0 }; + e && (e.next = i), t || ((t = i), n()), (e = i); + }; + }; + }, + 8378: function (t, e) { + var n = (t.exports = { version: "2.6.9" }); + "number" == typeof __e && (__e = n); + }, + "84f2": function (t, e) { + t.exports = {}; + }, + "852e": function (t, e, n) { + (function (e, n) { + t.exports = n(); + })(0, function () { + "use strict"; + function t(t) { + for (var e = 1; e < arguments.length; e++) { + var n = arguments[e]; + for (var r in n) t[r] = n[r]; + } + return t; + } + var e = { + read: function (t) { + return ( + '"' === t[0] && (t = t.slice(1, -1)), + t.replace(/(%[\dA-F]{2})+/gi, decodeURIComponent) + ); + }, + write: function (t) { + return encodeURIComponent(t).replace( + /%(2[346BF]|3[AC-F]|40|5[BDE]|60|7[BCD])/g, + decodeURIComponent + ); + }, + }; + function n(e, r) { + function i(n, i, o) { + if ("undefined" !== typeof document) { + (o = t({}, r, o)), + "number" === typeof o.expires && + (o.expires = new Date( + Date.now() + 864e5 * o.expires + )), + o.expires && + (o.expires = o.expires.toUTCString()), + (n = encodeURIComponent(n) + .replace( + /%(2[346B]|5E|60|7C)/g, + decodeURIComponent + ) + .replace(/[()]/g, escape)); + var a = ""; + for (var s in o) + o[s] && + ((a += "; " + s), + !0 !== o[s] && + (a += "=" + o[s].split(";")[0])); + return (document.cookie = + n + "=" + e.write(i, n) + a); + } + } + function o(t) { + if ( + "undefined" !== typeof document && + (!arguments.length || t) + ) { + for ( + var n = document.cookie + ? document.cookie.split("; ") + : [], + r = {}, + i = 0; + i < n.length; + i++ + ) { + var o = n[i].split("="), + a = o.slice(1).join("="); + try { + var s = decodeURIComponent(o[0]); + if (((r[s] = e.read(a, s)), t === s)) break; + } catch (u) {} + } + return t ? r[t] : r; + } + } + return Object.create( + { + set: i, + get: o, + remove: function (e, n) { + i(e, "", t({}, n, { expires: -1 })); + }, + withAttributes: function (e) { + return n( + this.converter, + t({}, this.attributes, e) + ); + }, + withConverter: function (e) { + return n( + t({}, this.converter, e), + this.attributes + ); + }, + }, + { + attributes: { value: Object.freeze(r) }, + converter: { value: Object.freeze(e) }, + } + ); + } + var r = n(e, { path: "/" }); + return r; + }); + }, + "85cd": function (t, e, n) { + var r = n("0185"), + i = n("ff0c"); + n("c165")("getPrototypeOf", function () { + return function (t) { + return i(r(t)); + }; + }); + }, + "85f2": function (t, e, n) { + t.exports = n("ec5b"); + }, + "86cc": function (t, e, n) { + var r = n("cb7c"), + i = n("c69a"), + o = n("6a99"), + a = Object.defineProperty; + e.f = n("9e1e") + ? Object.defineProperty + : function (t, e, n) { + if ((r(t), (e = o(e, !0)), r(n), i)) + try { + return a(t, e, n); + } catch (s) {} + if ("get" in n || "set" in n) + throw TypeError("Accessors not supported!"); + return "value" in n && (t[e] = n.value), t; + }; + }, + 8747: function (t, e, n) { + var r = n("d13f"); + r(r.P + r.R, "Map", { toJSON: n("034c")("Map") }); + }, + 8750: function (t, e, n) { + "use strict"; + Object.defineProperty(e, "__esModule", { value: !0 }), + (e.default = void 0); + var r = + "web" === + Object({ NODE_ENV: "production", BASE_URL: "/" }).BUILD + ? n("cb69").withParams + : n("0234").withParams, + i = r; + e.default = i; + }, + "8a77": function (t, e, n) { + n("111f")("Map"); + }, + "8b97": function (t, e, n) { + var r = n("d3f4"), + i = n("cb7c"), + o = function (t, e) { + if ((i(t), !r(e) && null !== e)) + throw TypeError(e + ": can't set as prototype!"); + }; + t.exports = { + set: + Object.setPrototypeOf || + ("__proto__" in {} + ? (function (t, e, r) { + try { + (r = n("9b43")( + Function.call, + n("11e9").f(Object.prototype, "__proto__") + .set, + 2 + )), + r(t, []), + (e = !(t instanceof Array)); + } catch (i) { + e = !0; + } + return function (t, n) { + return ( + o(t, n), + e ? (t.__proto__ = n) : r(t, n), + t + ); + }; + })({}, !1) + : void 0), + check: o, + }; + }, + "8bab": function (t, e, n) { + var r = n("6e1f"); + t.exports = Object("z").propertyIsEnumerable(0) + ? Object + : function (t) { + return "String" == r(t) ? t.split("") : Object(t); + }; + }, + "8ce0": function (t, e, n) { + var r = n("3adc"), + i = n("f845"); + t.exports = n("7d95") + ? function (t, e, n) { + return r.f(t, e, i(1, n)); + } + : function (t, e, n) { + return (t[e] = n), t; + }; + }, + "91d3": function (t, e, n) { + "use strict"; + Object.defineProperty(e, "__esModule", { value: !0 }), + (e.default = void 0); + var r = n("78ef"), + i = function () { + var t = + arguments.length > 0 && void 0 !== arguments[0] + ? arguments[0] + : ":"; + return (0, r.withParams)( + { type: "macAddress" }, + function (e) { + if (!(0, r.req)(e)) return !0; + if ("string" !== typeof e) return !1; + var n = + "string" === typeof t && "" !== t + ? e.split(t) + : 12 === e.length || 16 === e.length + ? e.match(/.{2}/g) + : null; + return ( + null !== n && + (6 === n.length || 8 === n.length) && + n.every(o) + ); + } + ); + }; + e.default = i; + var o = function (t) { + return t.toLowerCase().match(/^[0-9a-f]{2}$/); + }; + }, + "93c4": function (t, e, n) { + "use strict"; + var r = n("2a4e")(!0); + n("e4a9")( + String, + "String", + function (t) { + (this._t = String(t)), (this._i = 0); + }, + function () { + var t, + e = this._t, + n = this._i; + return n >= e.length + ? { value: void 0, done: !0 } + : ((t = r(e, n)), + (this._i += t.length), + { value: t, done: !1 }); + } + ); + }, + "9b43": function (t, e, n) { + var r = n("d8e8"); + t.exports = function (t, e, n) { + if ((r(t), void 0 === e)) return t; + switch (n) { + case 1: + return function (n) { + return t.call(e, n); + }; + case 2: + return function (n, r) { + return t.call(e, n, r); + }; + case 3: + return function (n, r, i) { + return t.call(e, n, r, i); + }; + } + return function () { + return t.apply(e, arguments); + }; + }; + }, + "9c6c": function (t, e, n) { + var r = n("2b4c")("unscopables"), + i = Array.prototype; + void 0 == i[r] && n("32e9")(i, r, {}), + (t.exports = function (t) { + i[r][t] = !0; + }); + }, + "9c80": function (t, e) { + t.exports = function (t) { + try { + return { e: !1, v: t() }; + } catch (e) { + return { e: !0, v: e }; + } + }; + }, + "9c93": function (t, e, n) { + var r = n("0f89"); + t.exports = function (t, e, n, i) { + try { + return i ? e(r(n)[0], n[1]) : e(n); + } catch (a) { + var o = t["return"]; + throw (void 0 !== o && r(o.call(t)), a); + } + }; + }, + "9def": function (t, e, n) { + var r = n("4588"), + i = Math.min; + t.exports = function (t) { + return t > 0 ? i(r(t), 9007199254740991) : 0; + }; + }, + "9e1e": function (t, e, n) { + t.exports = !n("79e5")(function () { + return ( + 7 != + Object.defineProperty({}, "a", { + get: function () { + return 7; + }, + }).a + ); + }); + }, + a25f: function (t, e, n) { + var r = n("7726"), + i = r.navigator; + t.exports = (i && i.userAgent) || ""; + }, + a438: function (t, e, n) { + n("07c8"), (t.exports = n("a7d3").Object.setPrototypeOf); + }, + a47f: function (t, e, n) { + t.exports = + !n("7d95") && + !n("d782")(function () { + return ( + 7 != + Object.defineProperty(n("12fd")("div"), "a", { + get: function () { + return 7; + }, + }).a + ); + }); + }, + a481: function (t, e, n) { + "use strict"; + var r = n("cb7c"), + i = n("4bf8"), + o = n("9def"), + a = n("4588"), + s = n("0390"), + u = n("5f1b"), + c = Math.max, + f = Math.min, + l = Math.floor, + d = /\$([$&`']|\d\d?|<[^>]*>)/g, + p = /\$([$&`']|\d\d?)/g, + h = function (t) { + return void 0 === t ? t : String(t); + }; + n("214f")("replace", 2, function (t, e, n, v) { + return [ + function (r, i) { + var o = t(this), + a = void 0 == r ? void 0 : r[e]; + return void 0 !== a + ? a.call(r, o, i) + : n.call(String(o), r, i); + }, + function (t, e) { + var i = v(n, t, this, e); + if (i.done) return i.value; + var l = r(t), + d = String(this), + p = "function" === typeof e; + p || (e = String(e)); + var m = l.global; + if (m) { + var g = l.unicode; + l.lastIndex = 0; + } + var b = []; + while (1) { + var _ = u(l, d); + if (null === _) break; + if ((b.push(_), !m)) break; + var w = String(_[0]); + "" === w && (l.lastIndex = s(d, o(l.lastIndex), g)); + } + for (var x = "", k = 0, S = 0; S < b.length; S++) { + _ = b[S]; + for ( + var E = String(_[0]), + O = c(f(a(_.index), d.length), 0), + T = [], + A = 1; + A < _.length; + A++ + ) + T.push(h(_[A])); + var C = _.groups; + if (p) { + var j = [E].concat(T, O, d); + void 0 !== C && j.push(C); + var I = String(e.apply(void 0, j)); + } else I = y(E, d, O, T, C, e); + O >= k && + ((x += d.slice(k, O) + I), (k = O + E.length)); + } + return x + d.slice(k); + }, + ]; + function y(t, e, r, o, a, s) { + var u = r + t.length, + c = o.length, + f = p; + return ( + void 0 !== a && ((a = i(a)), (f = d)), + n.call(s, f, function (n, i) { + var s; + switch (i.charAt(0)) { + case "$": + return "$"; + case "&": + return t; + case "`": + return e.slice(0, r); + case "'": + return e.slice(u); + case "<": + s = a[i.slice(1, -1)]; + break; + default: + var f = +i; + if (0 === f) return n; + if (f > c) { + var d = l(f / 10); + return 0 === d + ? n + : d <= c + ? void 0 === o[d - 1] + ? i.charAt(1) + : o[d - 1] + i.charAt(1) + : n; + } + s = o[f - 1]; + } + return void 0 === s ? "" : s; + }) + ); + } + }); + }, + a5ab: function (t, e, n) { + var r = n("a812"), + i = Math.min; + t.exports = function (t) { + return t > 0 ? i(r(t), 9007199254740991) : 0; + }; + }, + a5b2: function (t, e, n) { + t.exports = n("f4bb"); + }, + a5b8: function (t, e, n) { + "use strict"; + var r = n("d8e8"); + function i(t) { + var e, n; + (this.promise = new t(function (t, r) { + if (void 0 !== e || void 0 !== n) + throw TypeError("Bad Promise constructor"); + (e = t), (n = r); + })), + (this.resolve = r(e)), + (this.reject = r(n)); + } + t.exports.f = function (t) { + return new i(t); + }; + }, + a745: function (t, e, n) { + t.exports = n("d604"); + }, + a7d3: function (t, e) { + var n = (t.exports = { version: "2.6.12" }); + "number" == typeof __e && (__e = n); + }, + a812: function (t, e) { + var n = Math.ceil, + r = Math.floor; + t.exports = function (t) { + return isNaN((t = +t)) ? 0 : (t > 0 ? r : n)(t); + }; + }, + a925: function (t, e, n) { + "use strict"; + /*! + * vue-i18n v8.12.0 + * (c) 2019 kazuya kawaguchi + * Released under the MIT License. + */ var r = [ + "style", + "currency", + "currencyDisplay", + "useGrouping", + "minimumIntegerDigits", + "minimumFractionDigits", + "maximumFractionDigits", + "minimumSignificantDigits", + "maximumSignificantDigits", + "localeMatcher", + "formatMatcher", + ]; + function i(t, e) { + "undefined" !== typeof console && + (console.warn("[vue-i18n] " + t), + e && console.warn(e.stack)); + } + function o(t, e) { + "undefined" !== typeof console && + (console.error("[vue-i18n] " + t), + e && console.error(e.stack)); + } + function a(t) { + return null !== t && "object" === typeof t; + } + var s = Object.prototype.toString, + u = "[object Object]"; + function c(t) { + return s.call(t) === u; + } + function f(t) { + return null === t || void 0 === t; + } + function l() { + var t = [], + e = arguments.length; + while (e--) t[e] = arguments[e]; + var n = null, + r = null; + return ( + 1 === t.length + ? a(t[0]) || Array.isArray(t[0]) + ? (r = t[0]) + : "string" === typeof t[0] && (n = t[0]) + : 2 === t.length && + ("string" === typeof t[0] && (n = t[0]), + (a(t[1]) || Array.isArray(t[1])) && (r = t[1])), + { locale: n, params: r } + ); + } + function d(t) { + return JSON.parse(JSON.stringify(t)); + } + function p(t, e) { + if (t.length) { + var n = t.indexOf(e); + if (n > -1) return t.splice(n, 1); + } + } + var h = Object.prototype.hasOwnProperty; + function v(t, e) { + return h.call(t, e); + } + function y(t) { + for ( + var e = arguments, n = Object(t), r = 1; + r < arguments.length; + r++ + ) { + var i = e[r]; + if (void 0 !== i && null !== i) { + var o = void 0; + for (o in i) + v(i, o) && + (a(i[o]) + ? (n[o] = y(n[o], i[o])) + : (n[o] = i[o])); + } + } + return n; + } + function m(t, e) { + if (t === e) return !0; + var n = a(t), + r = a(e); + if (!n || !r) return !n && !r && String(t) === String(e); + try { + var i = Array.isArray(t), + o = Array.isArray(e); + if (i && o) + return ( + t.length === e.length && + t.every(function (t, n) { + return m(t, e[n]); + }) + ); + if (i || o) return !1; + var s = Object.keys(t), + u = Object.keys(e); + return ( + s.length === u.length && + s.every(function (n) { + return m(t[n], e[n]); + }) + ); + } catch (c) { + return !1; + } + } + function g(t) { + t.prototype.hasOwnProperty("$i18n") || + Object.defineProperty(t.prototype, "$i18n", { + get: function () { + return this._i18n; + }, + }), + (t.prototype.$t = function (t) { + var e = [], + n = arguments.length - 1; + while (n-- > 0) e[n] = arguments[n + 1]; + var r = this.$i18n; + return r._t.apply( + r, + [t, r.locale, r._getMessages(), this].concat(e) + ); + }), + (t.prototype.$tc = function (t, e) { + var n = [], + r = arguments.length - 2; + while (r-- > 0) n[r] = arguments[r + 2]; + var i = this.$i18n; + return i._tc.apply( + i, + [t, i.locale, i._getMessages(), this, e].concat(n) + ); + }), + (t.prototype.$te = function (t, e) { + var n = this.$i18n; + return n._te(t, n.locale, n._getMessages(), e); + }), + (t.prototype.$d = function (t) { + var e, + n = [], + r = arguments.length - 1; + while (r-- > 0) n[r] = arguments[r + 1]; + return (e = this.$i18n).d.apply(e, [t].concat(n)); + }), + (t.prototype.$n = function (t) { + var e, + n = [], + r = arguments.length - 1; + while (r-- > 0) n[r] = arguments[r + 1]; + return (e = this.$i18n).n.apply(e, [t].concat(n)); + }); + } + var b, + _ = { + beforeCreate: function () { + var t = this.$options; + if ( + ((t.i18n = t.i18n || (t.__i18n ? {} : null)), + t.i18n) + ) + if (t.i18n instanceof lt) { + if (t.__i18n) + try { + var e = {}; + t.__i18n.forEach(function (t) { + e = y(e, JSON.parse(t)); + }), + Object.keys(e).forEach(function ( + n + ) { + t.i18n.mergeLocaleMessage( + n, + e[n] + ); + }); + } catch (o) { + 0; + } + (this._i18n = t.i18n), + (this._i18nWatcher = + this._i18n.watchI18nData()); + } else if (c(t.i18n)) { + if ( + (this.$root && + this.$root.$i18n && + this.$root.$i18n instanceof lt && + ((t.i18n.root = this.$root), + (t.i18n.formatter = + this.$root.$i18n.formatter), + (t.i18n.fallbackLocale = + this.$root.$i18n.fallbackLocale), + (t.i18n.silentTranslationWarn = + this.$root.$i18n.silentTranslationWarn), + (t.i18n.silentFallbackWarn = + this.$root.$i18n.silentFallbackWarn), + (t.i18n.pluralizationRules = + this.$root.$i18n.pluralizationRules), + (t.i18n.preserveDirectiveContent = + this.$root.$i18n.preserveDirectiveContent)), + t.__i18n) + ) + try { + var n = {}; + t.__i18n.forEach(function (t) { + n = y(n, JSON.parse(t)); + }), + (t.i18n.messages = n); + } catch (o) { + 0; + } + var r = t.i18n, + i = r.sharedMessages; + i && + c(i) && + (t.i18n.messages = y(t.i18n.messages, i)), + (this._i18n = new lt(t.i18n)), + (this._i18nWatcher = + this._i18n.watchI18nData()), + (void 0 === t.i18n.sync || t.i18n.sync) && + (this._localeWatcher = + this.$i18n.watchLocale()); + } else 0; + else + this.$root && + this.$root.$i18n && + this.$root.$i18n instanceof lt + ? (this._i18n = this.$root.$i18n) + : t.parent && + t.parent.$i18n && + t.parent.$i18n instanceof lt && + (this._i18n = t.parent.$i18n); + }, + beforeMount: function () { + var t = this.$options; + (t.i18n = t.i18n || (t.__i18n ? {} : null)), + t.i18n + ? (t.i18n instanceof lt || c(t.i18n)) && + (this._i18n.subscribeDataChanging(this), + (this._subscribing = !0)) + : ((this.$root && + this.$root.$i18n && + this.$root.$i18n instanceof lt) || + (t.parent && + t.parent.$i18n && + t.parent.$i18n instanceof lt)) && + (this._i18n.subscribeDataChanging(this), + (this._subscribing = !0)); + }, + beforeDestroy: function () { + if (this._i18n) { + var t = this; + this.$nextTick(function () { + t._subscribing && + (t._i18n.unsubscribeDataChanging(t), + delete t._subscribing), + t._i18nWatcher && + (t._i18nWatcher(), + t._i18n.destroyVM(), + delete t._i18nWatcher), + t._localeWatcher && + (t._localeWatcher(), + delete t._localeWatcher), + (t._i18n = null); + }); + } + }, + }, + w = { + name: "i18n", + functional: !0, + props: { + tag: { type: String, default: "span" }, + path: { type: String, required: !0 }, + locale: { type: String }, + places: { type: [Array, Object] }, + }, + render: function (t, e) { + var n = e.props, + r = e.data, + i = e.children, + o = e.parent, + a = o.$i18n; + if ( + ((i = (i || []).filter(function (t) { + return t.tag || (t.text = t.text.trim()); + })), + !a) + ) + return i; + var s = n.path, + u = n.locale, + c = {}, + f = n.places || {}, + l = + (Array.isArray(f) + ? f.length + : Object.keys(f).length, + i.every(function (t) { + if (t.data && t.data.attrs) { + var e = t.data.attrs.place; + return ( + "undefined" !== typeof e && "" !== e + ); + } + })); + return ( + Array.isArray(f) + ? f.forEach(function (t, e) { + c[e] = t; + }) + : Object.keys(f).forEach(function (t) { + c[t] = f[t]; + }), + i.forEach(function (t, e) { + var n = l ? "" + t.data.attrs.place : "" + e; + c[n] = t; + }), + t(n.tag, r, a.i(s, u, c)) + ); + }, + }, + x = { + name: "i18n-n", + functional: !0, + props: { + tag: { type: String, default: "span" }, + value: { type: Number, required: !0 }, + format: { type: [String, Object] }, + locale: { type: String }, + }, + render: function (t, e) { + var n = e.props, + i = e.parent, + o = e.data, + s = i.$i18n; + if (!s) return null; + var u = null, + c = null; + "string" === typeof n.format + ? (u = n.format) + : a(n.format) && + (n.format.key && (u = n.format.key), + (c = Object.keys(n.format).reduce(function ( + t, + e + ) { + var i; + return r.includes(e) + ? Object.assign( + {}, + t, + ((i = {}), (i[e] = n.format[e]), i) + ) + : t; + }, + null))); + var f = n.locale || s.locale, + l = s._ntp(n.value, f, u, c), + d = l.map(function (t, e) { + var n, + r = o.scopedSlots && o.scopedSlots[t.type]; + return r + ? r( + ((n = {}), + (n[t.type] = t.value), + (n.index = e), + (n.parts = l), + n) + ) + : t.value; + }); + return t( + n.tag, + { + attrs: o.attrs, + class: o["class"], + staticClass: o.staticClass, + }, + d + ); + }, + }; + function k(t, e, n) { + O(t, n) && A(t, e, n); + } + function S(t, e, n, r) { + if (O(t, n)) { + var i = n.context.$i18n; + (T(t, n) && + m(e.value, e.oldValue) && + m(t._localeMessage, i.getLocaleMessage(i.locale))) || + A(t, e, n); + } + } + function E(t, e, n, r) { + var o = n.context; + if (o) { + var a = n.context.$i18n || {}; + e.modifiers.preserve || + a.preserveDirectiveContent || + (t.textContent = ""), + (t._vt = void 0), + delete t["_vt"], + (t._locale = void 0), + delete t["_locale"], + (t._localeMessage = void 0), + delete t["_localeMessage"]; + } else i("Vue instance does not exists in VNode context"); + } + function O(t, e) { + var n = e.context; + return n + ? !!n.$i18n || + (i( + "VueI18n instance does not exists in Vue instance" + ), + !1) + : (i("Vue instance does not exists in VNode context"), !1); + } + function T(t, e) { + var n = e.context; + return t._locale === n.$i18n.locale; + } + function A(t, e, n) { + var r, + o, + a = e.value, + s = C(a), + u = s.path, + c = s.locale, + f = s.args, + l = s.choice; + if (u || c || f) + if (u) { + var d = n.context; + (t._vt = t.textContent = + l + ? (r = d.$i18n).tc.apply( + r, + [u, l].concat(j(c, f)) + ) + : (o = d.$i18n).t.apply( + o, + [u].concat(j(c, f)) + )), + (t._locale = d.$i18n.locale), + (t._localeMessage = d.$i18n.getLocaleMessage( + d.$i18n.locale + )); + } else i("`path` is required in v-t directive"); + else i("value type not supported"); + } + function C(t) { + var e, n, r, i; + return ( + "string" === typeof t + ? (e = t) + : c(t) && + ((e = t.path), + (n = t.locale), + (r = t.args), + (i = t.choice)), + { path: e, locale: n, args: r, choice: i } + ); + } + function j(t, e) { + var n = []; + return ( + t && n.push(t), + e && (Array.isArray(e) || c(e)) && n.push(e), + n + ); + } + function I(t) { + (I.installed = !0), (b = t); + b.version && Number(b.version.split(".")[0]); + g(b), + b.mixin(_), + b.directive("t", { bind: k, update: S, unbind: E }), + b.component(w.name, w), + b.component(x.name, x); + var e = b.config.optionMergeStrategies; + e.i18n = function (t, e) { + return void 0 === e ? t : e; + }; + } + var M = function () { + this._caches = Object.create(null); + }; + M.prototype.interpolate = function (t, e) { + if (!e) return [t]; + var n = this._caches[t]; + return n || ((n = N(t)), (this._caches[t] = n)), R(n, e); + }; + var L = /^(?:\d)+/, + P = /^(?:\w)+/; + function N(t) { + var e = [], + n = 0, + r = ""; + while (n < t.length) { + var i = t[n++]; + if ("{" === i) { + r && e.push({ type: "text", value: r }), (r = ""); + var o = ""; + i = t[n++]; + while (void 0 !== i && "}" !== i) + (o += i), (i = t[n++]); + var a = "}" === i, + s = L.test(o) + ? "list" + : a && P.test(o) + ? "named" + : "unknown"; + e.push({ value: o, type: s }); + } else "%" === i ? "{" !== t[n] && (r += i) : (r += i); + } + return r && e.push({ type: "text", value: r }), e; + } + function R(t, e) { + var n = [], + r = 0, + i = Array.isArray(e) ? "list" : a(e) ? "named" : "unknown"; + if ("unknown" === i) return n; + while (r < t.length) { + var o = t[r]; + switch (o.type) { + case "text": + n.push(o.value); + break; + case "list": + n.push(e[parseInt(o.value, 10)]); + break; + case "named": + "named" === i && n.push(e[o.value]); + break; + case "unknown": + 0; + break; + } + r++; + } + return n; + } + var $ = 0, + D = 1, + F = 2, + z = 3, + U = 0, + B = 1, + V = 2, + H = 3, + q = 4, + W = 5, + G = 6, + Z = 7, + J = 8, + K = []; + (K[U] = { ws: [U], ident: [H, $], "[": [q], eof: [Z] }), + (K[B] = { ws: [B], ".": [V], "[": [q], eof: [Z] }), + (K[V] = { ws: [V], ident: [H, $], 0: [H, $], number: [H, $] }), + (K[H] = { + ident: [H, $], + 0: [H, $], + number: [H, $], + ws: [B, D], + ".": [V, D], + "[": [q, D], + eof: [Z, D], + }), + (K[q] = { + "'": [W, $], + '"': [G, $], + "[": [q, F], + "]": [B, z], + eof: J, + else: [q, $], + }), + (K[W] = { "'": [q, $], eof: J, else: [W, $] }), + (K[G] = { '"': [q, $], eof: J, else: [G, $] }); + var X = /^\s?(?:true|false|-?[\d.]+|'[^']*'|"[^"]*")\s?$/; + function Y(t) { + return X.test(t); + } + function Q(t) { + var e = t.charCodeAt(0), + n = t.charCodeAt(t.length - 1); + return e !== n || (34 !== e && 39 !== e) ? t : t.slice(1, -1); + } + function tt(t) { + if (void 0 === t || null === t) return "eof"; + var e = t.charCodeAt(0); + switch (e) { + case 91: + case 93: + case 46: + case 34: + case 39: + return t; + case 95: + case 36: + case 45: + return "ident"; + case 9: + case 10: + case 13: + case 160: + case 65279: + case 8232: + case 8233: + return "ws"; + } + return "ident"; + } + function et(t) { + var e = t.trim(); + return ( + ("0" !== t.charAt(0) || !isNaN(t)) && + (Y(e) ? Q(e) : "*" + e) + ); + } + function nt(t) { + var e, + n, + r, + i, + o, + a, + s, + u = [], + c = -1, + f = U, + l = 0, + d = []; + function p() { + var e = t[c + 1]; + if ((f === W && "'" === e) || (f === G && '"' === e)) + return c++, (r = "\\" + e), d[$](), !0; + } + (d[D] = function () { + void 0 !== n && (u.push(n), (n = void 0)); + }), + (d[$] = function () { + void 0 === n ? (n = r) : (n += r); + }), + (d[F] = function () { + d[$](), l++; + }), + (d[z] = function () { + if (l > 0) l--, (f = q), d[$](); + else { + if (((l = 0), (n = et(n)), !1 === n)) return !1; + d[D](); + } + }); + while (null !== f) + if ((c++, (e = t[c]), "\\" !== e || !p())) { + if ( + ((i = tt(e)), + (s = K[f]), + (o = s[i] || s["else"] || J), + o === J) + ) + return; + if ( + ((f = o[0]), + (a = d[o[1]]), + a && + ((r = o[2]), + (r = void 0 === r ? e : r), + !1 === a())) + ) + return; + if (f === Z) return u; + } + } + var rt = function () { + this._cache = Object.create(null); + }; + (rt.prototype.parsePath = function (t) { + var e = this._cache[t]; + return e || ((e = nt(t)), e && (this._cache[t] = e)), e || []; + }), + (rt.prototype.getPathValue = function (t, e) { + if (!a(t)) return null; + var n = this.parsePath(e); + if (0 === n.length) return null; + var r = n.length, + i = t, + o = 0; + while (o < r) { + var s = i[n[o]]; + if (void 0 === s) return null; + (i = s), o++; + } + return i; + }); + var it, + ot = /<\/?[\w\s="/.':;#-\/]+>/, + at = /(?:@(?:\.[a-z]+)?:(?:[\w\-_|.]+|\([\w\-_|.]+\)))/g, + st = /^@(?:\.([a-z]+))?:/, + ut = /[()]/g, + ct = { + upper: function (t) { + return t.toLocaleUpperCase(); + }, + lower: function (t) { + return t.toLocaleLowerCase(); + }, + }, + ft = new M(), + lt = function (t) { + var e = this; + void 0 === t && (t = {}), + !b && + "undefined" !== typeof window && + window.Vue && + I(window.Vue); + var n = t.locale || "en-US", + r = t.fallbackLocale || "en-US", + i = t.messages || {}, + o = t.dateTimeFormats || {}, + a = t.numberFormats || {}; + (this._vm = null), + (this._formatter = t.formatter || ft), + (this._missing = t.missing || null), + (this._root = t.root || null), + (this._sync = void 0 === t.sync || !!t.sync), + (this._fallbackRoot = + void 0 === t.fallbackRoot || !!t.fallbackRoot), + (this._silentTranslationWarn = + void 0 !== t.silentTranslationWarn && + !!t.silentTranslationWarn), + (this._silentFallbackWarn = + void 0 !== t.silentFallbackWarn && + !!t.silentFallbackWarn), + (this._dateTimeFormatters = {}), + (this._numberFormatters = {}), + (this._path = new rt()), + (this._dataListeners = []), + (this._preserveDirectiveContent = + void 0 !== t.preserveDirectiveContent && + !!t.preserveDirectiveContent), + (this.pluralizationRules = t.pluralizationRules || {}), + (this._warnHtmlInMessage = + t.warnHtmlInMessage || "off"), + (this._exist = function (t, n) { + return ( + !(!t || !n) && + (!f(e._path.getPathValue(t, n)) || !!t[n]) + ); + }), + ("warn" !== this._warnHtmlInMessage && + "error" !== this._warnHtmlInMessage) || + Object.keys(i).forEach(function (t) { + e._checkLocaleMessage( + t, + e._warnHtmlInMessage, + i[t] + ); + }), + this._initVM({ + locale: n, + fallbackLocale: r, + messages: i, + dateTimeFormats: o, + numberFormats: a, + }); + }, + dt = { + vm: { configurable: !0 }, + messages: { configurable: !0 }, + dateTimeFormats: { configurable: !0 }, + numberFormats: { configurable: !0 }, + availableLocales: { configurable: !0 }, + locale: { configurable: !0 }, + fallbackLocale: { configurable: !0 }, + missing: { configurable: !0 }, + formatter: { configurable: !0 }, + silentTranslationWarn: { configurable: !0 }, + silentFallbackWarn: { configurable: !0 }, + preserveDirectiveContent: { configurable: !0 }, + warnHtmlInMessage: { configurable: !0 }, + }; + (lt.prototype._checkLocaleMessage = function (t, e, n) { + var r = [], + a = function (t, e, n, r) { + if (c(n)) + Object.keys(n).forEach(function (i) { + var o = n[i]; + c(o) + ? (r.push(i), + r.push("."), + a(t, e, o, r), + r.pop(), + r.pop()) + : (r.push(i), a(t, e, o, r), r.pop()); + }); + else if (Array.isArray(n)) + n.forEach(function (n, i) { + c(n) + ? (r.push("[" + i + "]"), + r.push("."), + a(t, e, n, r), + r.pop(), + r.pop()) + : (r.push("[" + i + "]"), + a(t, e, n, r), + r.pop()); + }); + else if ("string" === typeof n) { + var s = ot.test(n); + if (s) { + var u = + "Detected HTML in message '" + + n + + "' of keypath '" + + r.join("") + + "' at '" + + e + + "'. Consider component interpolation with '' to avoid XSS. See https://bit.ly/2ZqJzkp"; + "warn" === t ? i(u) : "error" === t && o(u); + } + } + }; + a(e, t, n, r); + }), + (lt.prototype._initVM = function (t) { + var e = b.config.silent; + (b.config.silent = !0), + (this._vm = new b({ data: t })), + (b.config.silent = e); + }), + (lt.prototype.destroyVM = function () { + this._vm.$destroy(); + }), + (lt.prototype.subscribeDataChanging = function (t) { + this._dataListeners.push(t); + }), + (lt.prototype.unsubscribeDataChanging = function (t) { + p(this._dataListeners, t); + }), + (lt.prototype.watchI18nData = function () { + var t = this; + return this._vm.$watch( + "$data", + function () { + var e = t._dataListeners.length; + while (e--) + b.nextTick(function () { + t._dataListeners[e] && + t._dataListeners[e].$forceUpdate(); + }); + }, + { deep: !0 } + ); + }), + (lt.prototype.watchLocale = function () { + if (!this._sync || !this._root) return null; + var t = this._vm; + return this._root.$i18n.vm.$watch( + "locale", + function (e) { + t.$set(t, "locale", e), t.$forceUpdate(); + }, + { immediate: !0 } + ); + }), + (dt.vm.get = function () { + return this._vm; + }), + (dt.messages.get = function () { + return d(this._getMessages()); + }), + (dt.dateTimeFormats.get = function () { + return d(this._getDateTimeFormats()); + }), + (dt.numberFormats.get = function () { + return d(this._getNumberFormats()); + }), + (dt.availableLocales.get = function () { + return Object.keys(this.messages).sort(); + }), + (dt.locale.get = function () { + return this._vm.locale; + }), + (dt.locale.set = function (t) { + this._vm.$set(this._vm, "locale", t); + }), + (dt.fallbackLocale.get = function () { + return this._vm.fallbackLocale; + }), + (dt.fallbackLocale.set = function (t) { + this._vm.$set(this._vm, "fallbackLocale", t); + }), + (dt.missing.get = function () { + return this._missing; + }), + (dt.missing.set = function (t) { + this._missing = t; + }), + (dt.formatter.get = function () { + return this._formatter; + }), + (dt.formatter.set = function (t) { + this._formatter = t; + }), + (dt.silentTranslationWarn.get = function () { + return this._silentTranslationWarn; + }), + (dt.silentTranslationWarn.set = function (t) { + this._silentTranslationWarn = t; + }), + (dt.silentFallbackWarn.get = function () { + return this._silentFallbackWarn; + }), + (dt.silentFallbackWarn.set = function (t) { + this._silentFallbackWarn = t; + }), + (dt.preserveDirectiveContent.get = function () { + return this._preserveDirectiveContent; + }), + (dt.preserveDirectiveContent.set = function (t) { + this._preserveDirectiveContent = t; + }), + (dt.warnHtmlInMessage.get = function () { + return this._warnHtmlInMessage; + }), + (dt.warnHtmlInMessage.set = function (t) { + var e = this, + n = this._warnHtmlInMessage; + if ( + ((this._warnHtmlInMessage = t), + n !== t && ("warn" === t || "error" === t)) + ) { + var r = this._getMessages(); + Object.keys(r).forEach(function (t) { + e._checkLocaleMessage( + t, + e._warnHtmlInMessage, + r[t] + ); + }); + } + }), + (lt.prototype._getMessages = function () { + return this._vm.messages; + }), + (lt.prototype._getDateTimeFormats = function () { + return this._vm.dateTimeFormats; + }), + (lt.prototype._getNumberFormats = function () { + return this._vm.numberFormats; + }), + (lt.prototype._warnDefault = function (t, e, n, r, i) { + if (!f(n)) return n; + if (this._missing) { + var o = this._missing.apply(null, [t, e, r, i]); + if ("string" === typeof o) return o; + } else 0; + return e; + }), + (lt.prototype._isFallbackRoot = function (t) { + return !t && !f(this._root) && this._fallbackRoot; + }), + (lt.prototype._isSilentFallback = function (t) { + return ( + this._silentFallbackWarn && + (this._isFallbackRoot() || t !== this.fallbackLocale) + ); + }), + (lt.prototype._interpolate = function (t, e, n, r, i, o, a) { + if (!e) return null; + var s, + u = this._path.getPathValue(e, n); + if (Array.isArray(u) || c(u)) return u; + if (f(u)) { + if (!c(e)) return null; + if (((s = e[n]), "string" !== typeof s)) return null; + } else { + if ("string" !== typeof u) return null; + s = u; + } + return ( + (s.indexOf("@:") >= 0 || s.indexOf("@.") >= 0) && + (s = this._link(t, e, s, r, "raw", o, a)), + this._render(s, i, o, n) + ); + }), + (lt.prototype._link = function (t, e, n, r, i, o, a) { + var s = n, + u = s.match(at); + for (var c in u) + if (u.hasOwnProperty(c)) { + var f = u[c], + l = f.match(st), + d = l[0], + p = l[1], + h = f.replace(d, "").replace(ut, ""); + if (a.includes(h)) return s; + a.push(h); + var v = this._interpolate( + t, + e, + h, + r, + "raw" === i ? "string" : i, + "raw" === i ? void 0 : o, + a + ); + if (this._isFallbackRoot(v)) { + if (!this._root) + throw Error("unexpected error"); + var y = this._root.$i18n; + v = y._translate( + y._getMessages(), + y.locale, + y.fallbackLocale, + h, + r, + i, + o + ); + } + (v = this._warnDefault( + t, + h, + v, + r, + Array.isArray(o) ? o : [o] + )), + ct.hasOwnProperty(p) && (v = ct[p](v)), + a.pop(), + (s = v ? s.replace(f, v) : s); + } + return s; + }), + (lt.prototype._render = function (t, e, n, r) { + var i = this._formatter.interpolate(t, n, r); + return ( + i || (i = ft.interpolate(t, n, r)), + "string" === e ? i.join("") : i + ); + }), + (lt.prototype._translate = function (t, e, n, r, i, o, a) { + var s = this._interpolate(e, t[e], r, i, o, a, [r]); + return f(s) + ? ((s = this._interpolate(n, t[n], r, i, o, a, [r])), + f(s) ? null : s) + : s; + }), + (lt.prototype._t = function (t, e, n, r) { + var i, + o = [], + a = arguments.length - 4; + while (a-- > 0) o[a] = arguments[a + 4]; + if (!t) return ""; + var s = l.apply(void 0, o), + u = s.locale || e, + c = this._translate( + n, + u, + this.fallbackLocale, + t, + r, + "string", + s.params + ); + if (this._isFallbackRoot(c)) { + if (!this._root) throw Error("unexpected error"); + return (i = this._root).$t.apply(i, [t].concat(o)); + } + return this._warnDefault(u, t, c, r, o); + }), + (lt.prototype.t = function (t) { + var e, + n = [], + r = arguments.length - 1; + while (r-- > 0) n[r] = arguments[r + 1]; + return (e = this)._t.apply( + e, + [t, this.locale, this._getMessages(), null].concat(n) + ); + }), + (lt.prototype._i = function (t, e, n, r, i) { + var o = this._translate( + n, + e, + this.fallbackLocale, + t, + r, + "raw", + i + ); + if (this._isFallbackRoot(o)) { + if (!this._root) throw Error("unexpected error"); + return this._root.$i18n.i(t, e, i); + } + return this._warnDefault(e, t, o, r, [i]); + }), + (lt.prototype.i = function (t, e, n) { + return t + ? ("string" !== typeof e && (e = this.locale), + this._i(t, e, this._getMessages(), null, n)) + : ""; + }), + (lt.prototype._tc = function (t, e, n, r, i) { + var o, + a = [], + s = arguments.length - 5; + while (s-- > 0) a[s] = arguments[s + 5]; + if (!t) return ""; + void 0 === i && (i = 1); + var u = { count: i, n: i }, + c = l.apply(void 0, a); + return ( + (c.params = Object.assign(u, c.params)), + (a = + null === c.locale + ? [c.params] + : [c.locale, c.params]), + this.fetchChoice( + (o = this)._t.apply(o, [t, e, n, r].concat(a)), + i + ) + ); + }), + (lt.prototype.fetchChoice = function (t, e) { + if (!t && "string" !== typeof t) return null; + var n = t.split("|"); + return ( + (e = this.getChoiceIndex(e, n.length)), + n[e] ? n[e].trim() : t + ); + }), + (lt.prototype.getChoiceIndex = function (t, e) { + var n = function (t, e) { + return ( + (t = Math.abs(t)), + 2 === e + ? t + ? t > 1 + ? 1 + : 0 + : 1 + : t + ? Math.min(t, 2) + : 0 + ); + }; + return this.locale in this.pluralizationRules + ? this.pluralizationRules[this.locale].apply(this, [ + t, + e, + ]) + : n(t, e); + }), + (lt.prototype.tc = function (t, e) { + var n, + r = [], + i = arguments.length - 2; + while (i-- > 0) r[i] = arguments[i + 2]; + return (n = this)._tc.apply( + n, + [t, this.locale, this._getMessages(), null, e].concat(r) + ); + }), + (lt.prototype._te = function (t, e, n) { + var r = [], + i = arguments.length - 3; + while (i-- > 0) r[i] = arguments[i + 3]; + var o = l.apply(void 0, r).locale || e; + return this._exist(n[o], t); + }), + (lt.prototype.te = function (t, e) { + return this._te(t, this.locale, this._getMessages(), e); + }), + (lt.prototype.getLocaleMessage = function (t) { + return d(this._vm.messages[t] || {}); + }), + (lt.prototype.setLocaleMessage = function (t, e) { + (("warn" !== this._warnHtmlInMessage && + "error" !== this._warnHtmlInMessage) || + (this._checkLocaleMessage( + t, + this._warnHtmlInMessage, + e + ), + "error" !== this._warnHtmlInMessage)) && + this._vm.$set(this._vm.messages, t, e); + }), + (lt.prototype.mergeLocaleMessage = function (t, e) { + (("warn" !== this._warnHtmlInMessage && + "error" !== this._warnHtmlInMessage) || + (this._checkLocaleMessage( + t, + this._warnHtmlInMessage, + e + ), + "error" !== this._warnHtmlInMessage)) && + this._vm.$set( + this._vm.messages, + t, + y(this._vm.messages[t] || {}, e) + ); + }), + (lt.prototype.getDateTimeFormat = function (t) { + return d(this._vm.dateTimeFormats[t] || {}); + }), + (lt.prototype.setDateTimeFormat = function (t, e) { + this._vm.$set(this._vm.dateTimeFormats, t, e); + }), + (lt.prototype.mergeDateTimeFormat = function (t, e) { + this._vm.$set( + this._vm.dateTimeFormats, + t, + y(this._vm.dateTimeFormats[t] || {}, e) + ); + }), + (lt.prototype._localizeDateTime = function (t, e, n, r, i) { + var o = e, + a = r[o]; + if ( + ((f(a) || f(a[i])) && ((o = n), (a = r[o])), + f(a) || f(a[i])) + ) + return null; + var s = a[i], + u = o + "__" + i, + c = this._dateTimeFormatters[u]; + return ( + c || + (c = this._dateTimeFormatters[u] = + new Intl.DateTimeFormat(o, s)), + c.format(t) + ); + }), + (lt.prototype._d = function (t, e, n) { + if (!n) return new Intl.DateTimeFormat(e).format(t); + var r = this._localizeDateTime( + t, + e, + this.fallbackLocale, + this._getDateTimeFormats(), + n + ); + if (this._isFallbackRoot(r)) { + if (!this._root) throw Error("unexpected error"); + return this._root.$i18n.d(t, n, e); + } + return r || ""; + }), + (lt.prototype.d = function (t) { + var e = [], + n = arguments.length - 1; + while (n-- > 0) e[n] = arguments[n + 1]; + var r = this.locale, + i = null; + return ( + 1 === e.length + ? "string" === typeof e[0] + ? (i = e[0]) + : a(e[0]) && + (e[0].locale && (r = e[0].locale), + e[0].key && (i = e[0].key)) + : 2 === e.length && + ("string" === typeof e[0] && (i = e[0]), + "string" === typeof e[1] && (r = e[1])), + this._d(t, r, i) + ); + }), + (lt.prototype.getNumberFormat = function (t) { + return d(this._vm.numberFormats[t] || {}); + }), + (lt.prototype.setNumberFormat = function (t, e) { + this._vm.$set(this._vm.numberFormats, t, e); + }), + (lt.prototype.mergeNumberFormat = function (t, e) { + this._vm.$set( + this._vm.numberFormats, + t, + y(this._vm.numberFormats[t] || {}, e) + ); + }), + (lt.prototype._getNumberFormatter = function ( + t, + e, + n, + r, + i, + o + ) { + var a = e, + s = r[a]; + if ( + ((f(s) || f(s[i])) && ((a = n), (s = r[a])), + f(s) || f(s[i])) + ) + return null; + var u, + c = s[i]; + if (o) + u = new Intl.NumberFormat(a, Object.assign({}, c, o)); + else { + var l = a + "__" + i; + (u = this._numberFormatters[l]), + u || + (u = this._numberFormatters[l] = + new Intl.NumberFormat(a, c)); + } + return u; + }), + (lt.prototype._n = function (t, e, n, r) { + if (!lt.availabilities.numberFormat) return ""; + if (!n) { + var i = r + ? new Intl.NumberFormat(e, r) + : new Intl.NumberFormat(e); + return i.format(t); + } + var o = this._getNumberFormatter( + t, + e, + this.fallbackLocale, + this._getNumberFormats(), + n, + r + ), + a = o && o.format(t); + if (this._isFallbackRoot(a)) { + if (!this._root) throw Error("unexpected error"); + return this._root.$i18n.n( + t, + Object.assign({}, { key: n, locale: e }, r) + ); + } + return a || ""; + }), + (lt.prototype.n = function (t) { + var e = [], + n = arguments.length - 1; + while (n-- > 0) e[n] = arguments[n + 1]; + var i = this.locale, + o = null, + s = null; + return ( + 1 === e.length + ? "string" === typeof e[0] + ? (o = e[0]) + : a(e[0]) && + (e[0].locale && (i = e[0].locale), + e[0].key && (o = e[0].key), + (s = Object.keys(e[0]).reduce(function ( + t, + n + ) { + var i; + return r.includes(n) + ? Object.assign( + {}, + t, + ((i = {}), (i[n] = e[0][n]), i) + ) + : t; + }, + null))) + : 2 === e.length && + ("string" === typeof e[0] && (o = e[0]), + "string" === typeof e[1] && (i = e[1])), + this._n(t, i, o, s) + ); + }), + (lt.prototype._ntp = function (t, e, n, r) { + if (!lt.availabilities.numberFormat) return []; + if (!n) { + var i = r + ? new Intl.NumberFormat(e, r) + : new Intl.NumberFormat(e); + return i.formatToParts(t); + } + var o = this._getNumberFormatter( + t, + e, + this.fallbackLocale, + this._getNumberFormats(), + n, + r + ), + a = o && o.formatToParts(t); + if (this._isFallbackRoot(a)) { + if (!this._root) throw Error("unexpected error"); + return this._root.$i18n._ntp(t, e, n, r); + } + return a || []; + }), + Object.defineProperties(lt.prototype, dt), + Object.defineProperty(lt, "availabilities", { + get: function () { + if (!it) { + var t = "undefined" !== typeof Intl; + it = { + dateTimeFormat: + t && + "undefined" !== typeof Intl.DateTimeFormat, + numberFormat: + t && + "undefined" !== typeof Intl.NumberFormat, + }; + } + return it; + }, + }), + (lt.install = I), + (lt.version = "8.12.0"), + (e["a"] = lt); + }, + aa82: function (t, e, n) { + "use strict"; + Object.defineProperty(e, "__esModule", { value: !0 }), + (e.default = void 0); + var r = n("78ef"), + i = function (t) { + return (0, r.withParams)( + { type: "requiredIf", prop: t }, + function (e, n) { + return !(0, r.ref)(t, this, n) || (0, r.req)(e); + } + ); + }; + e.default = i; + }, + aae3: function (t, e, n) { + var r = n("d3f4"), + i = n("2d95"), + o = n("2b4c")("match"); + t.exports = function (t) { + var e; + return r(t) && (void 0 !== (e = t[o]) ? !!e : "RegExp" == i(t)); + }; + }, + ac6a: function (t, e, n) { + for ( + var r = n("cadf"), + i = n("0d58"), + o = n("2aba"), + a = n("7726"), + s = n("32e9"), + u = n("84f2"), + c = n("2b4c"), + f = c("iterator"), + l = c("toStringTag"), + d = u.Array, + p = { + CSSRuleList: !0, + CSSStyleDeclaration: !1, + CSSValueList: !1, + ClientRectList: !1, + DOMRectList: !1, + DOMStringList: !1, + DOMTokenList: !0, + DataTransferItemList: !1, + FileList: !1, + HTMLAllCollection: !1, + HTMLCollection: !1, + HTMLFormElement: !1, + HTMLSelectElement: !1, + MediaList: !0, + MimeTypeArray: !1, + NamedNodeMap: !1, + NodeList: !0, + PaintRequestList: !1, + Plugin: !1, + PluginArray: !1, + SVGLengthList: !1, + SVGNumberList: !1, + SVGPathSegList: !1, + SVGPointList: !1, + SVGStringList: !1, + SVGTransformList: !1, + SourceBufferList: !1, + StyleSheetList: !0, + TextTrackCueList: !1, + TextTrackList: !1, + TouchList: !1, + }, + h = i(p), + v = 0; + v < h.length; + v++ + ) { + var y, + m = h[v], + g = p[m], + b = a[m], + _ = b && b.prototype; + if ( + _ && + (_[f] || s(_, f, d), _[l] || s(_, l, m), (u[m] = d), g) + ) + for (y in r) _[y] || o(_, y, r[y], !0); + } + }, + adf3: function (t, e, n) { + var r = n("560b"); + t.exports = function (t, e) { + var n = []; + return r(t, !1, n.push, n, e), n; + }; + }, + af7e: function (t, e, n) { + n("50e9"); + var r = n("a7d3").Object; + t.exports = function (t, e) { + return r.create(t, e); + }; + }, + b0b4: function (t, e, n) { + "use strict"; + n.d(e, "a", function () { + return a; + }); + var r = n("85f2"), + i = n.n(r); + function o(t, e) { + for (var n = 0; n < e.length; n++) { + var r = e[n]; + (r.enumerable = r.enumerable || !1), + (r.configurable = !0), + "value" in r && (r.writable = !0), + i()(t, r.key, r); + } + } + function a(t, e, n) { + return ( + e && o(t.prototype, e), + n && o(t, n), + i()(t, "prototype", { writable: !1 }), + t + ); + } + }, + b0bc: function (t, e) { + t.exports = function (t, e, n, r) { + if (!(t instanceof e) || (void 0 !== r && r in t)) + throw TypeError(n + ": incorrect invocation!"); + return t; + }; + }, + b0c5: function (t, e, n) { + "use strict"; + var r = n("520a"); + n("5ca1")( + { target: "RegExp", proto: !0, forced: r !== /./.exec }, + { exec: r } + ); + }, + b22a: function (t, e) { + t.exports = {}; + }, + b258: function (t, e, n) { + n("d256"), + n("12fd9"), + n("d127"), + n("d24f"), + (t.exports = n("a7d3").Symbol); + }, + b311: function (t, e, n) { + /*! + * clipboard.js v2.0.10 + * https://clipboardjs.com/ + * + * Licensed MIT © Zeno Rocha + */ + (function (e, n) { + t.exports = n(); + })(0, function () { + return (function () { + var t = { + 686: function (t, e, n) { + "use strict"; + n.d(e, { + default: function () { + return I; + }, + }); + var r = n(279), + i = n.n(r), + o = n(370), + a = n.n(o), + s = n(817), + u = n.n(s); + function c(t) { + try { + return document.execCommand(t); + } catch (e) { + return !1; + } + } + var f = function (t) { + var e = u()(t); + return c("cut"), e; + }, + l = f; + function d(t) { + var e = + "rtl" === + document.documentElement.getAttribute( + "dir" + ), + n = document.createElement("textarea"); + (n.style.fontSize = "12pt"), + (n.style.border = "0"), + (n.style.padding = "0"), + (n.style.margin = "0"), + (n.style.position = "absolute"), + (n.style[e ? "right" : "left"] = + "-9999px"); + var r = + window.pageYOffset || + document.documentElement.scrollTop; + return ( + (n.style.top = "".concat(r, "px")), + n.setAttribute("readonly", ""), + (n.value = t), + n + ); + } + var p = function (t) { + var e = + arguments.length > 1 && + void 0 !== arguments[1] + ? arguments[1] + : { + container: + document.body, + }, + n = ""; + if ("string" === typeof t) { + var r = d(t); + e.container.appendChild(r), + (n = u()(r)), + c("copy"), + r.remove(); + } else (n = u()(t)), c("copy"); + return n; + }, + h = p; + function v(t) { + return ( + (v = + "function" === typeof Symbol && + "symbol" === typeof Symbol.iterator + ? function (t) { + return typeof t; + } + : function (t) { + return t && + "function" === + typeof Symbol && + t.constructor === + Symbol && + t !== Symbol.prototype + ? "symbol" + : typeof t; + }), + v(t) + ); + } + var y = function () { + var t = + arguments.length > 0 && + void 0 !== arguments[0] + ? arguments[0] + : {}, + e = t.action, + n = void 0 === e ? "copy" : e, + r = t.container, + i = t.target, + o = t.text; + if ("copy" !== n && "cut" !== n) + throw new Error( + 'Invalid "action" value, use either "copy" or "cut"' + ); + if (void 0 !== i) { + if ( + !i || + "object" !== v(i) || + 1 !== i.nodeType + ) + throw new Error( + 'Invalid "target" value, use a valid Element' + ); + if ( + "copy" === n && + i.hasAttribute("disabled") + ) + throw new Error( + 'Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute' + ); + if ( + "cut" === n && + (i.hasAttribute("readonly") || + i.hasAttribute("disabled")) + ) + throw new Error( + 'Invalid "target" attribute. You can\'t cut text from elements with "readonly" or "disabled" attributes' + ); + } + return o + ? h(o, { container: r }) + : i + ? "cut" === n + ? l(i) + : h(i, { container: r }) + : void 0; + }, + m = y; + function g(t) { + return ( + (g = + "function" === typeof Symbol && + "symbol" === typeof Symbol.iterator + ? function (t) { + return typeof t; + } + : function (t) { + return t && + "function" === + typeof Symbol && + t.constructor === + Symbol && + t !== Symbol.prototype + ? "symbol" + : typeof t; + }), + g(t) + ); + } + function b(t, e) { + if (!(t instanceof e)) + throw new TypeError( + "Cannot call a class as a function" + ); + } + function _(t, e) { + for (var n = 0; n < e.length; n++) { + var r = e[n]; + (r.enumerable = r.enumerable || !1), + (r.configurable = !0), + "value" in r && (r.writable = !0), + Object.defineProperty(t, r.key, r); + } + } + function w(t, e, n) { + return ( + e && _(t.prototype, e), n && _(t, n), t + ); + } + function x(t, e) { + if ("function" !== typeof e && null !== e) + throw new TypeError( + "Super expression must either be null or a function" + ); + (t.prototype = Object.create( + e && e.prototype, + { + constructor: { + value: t, + writable: !0, + configurable: !0, + }, + } + )), + e && k(t, e); + } + function k(t, e) { + return ( + (k = + Object.setPrototypeOf || + function (t, e) { + return (t.__proto__ = e), t; + }), + k(t, e) + ); + } + function S(t) { + var e = T(); + return function () { + var n, + r = A(t); + if (e) { + var i = A(this).constructor; + n = Reflect.construct( + r, + arguments, + i + ); + } else n = r.apply(this, arguments); + return E(this, n); + }; + } + function E(t, e) { + return !e || + ("object" !== g(e) && + "function" !== typeof e) + ? O(t) + : e; + } + function O(t) { + if (void 0 === t) + throw new ReferenceError( + "this hasn't been initialised - super() hasn't been called" + ); + return t; + } + function T() { + if ( + "undefined" === typeof Reflect || + !Reflect.construct + ) + return !1; + if (Reflect.construct.sham) return !1; + if ("function" === typeof Proxy) return !0; + try { + return ( + Date.prototype.toString.call( + Reflect.construct( + Date, + [], + function () {} + ) + ), + !0 + ); + } catch (t) { + return !1; + } + } + function A(t) { + return ( + (A = Object.setPrototypeOf + ? Object.getPrototypeOf + : function (t) { + return ( + t.__proto__ || + Object.getPrototypeOf(t) + ); + }), + A(t) + ); + } + function C(t, e) { + var n = "data-clipboard-".concat(t); + if (e.hasAttribute(n)) + return e.getAttribute(n); + } + var j = (function (t) { + x(n, t); + var e = S(n); + function n(t, r) { + var i; + return ( + b(this, n), + (i = e.call(this)), + i.resolveOptions(r), + i.listenClick(t), + i + ); + } + return ( + w( + n, + [ + { + key: "resolveOptions", + value: function () { + var t = + arguments.length > + 0 && + void 0 !== + arguments[0] + ? arguments[0] + : {}; + (this.action = + "function" === + typeof t.action + ? t.action + : this + .defaultAction), + (this.target = + "function" === + typeof t.target + ? t.target + : this + .defaultTarget), + (this.text = + "function" === + typeof t.text + ? t.text + : this + .defaultText), + (this.container = + "object" === + g( + t.container + ) + ? t.container + : document.body); + }, + }, + { + key: "listenClick", + value: function (t) { + var e = this; + this.listener = a()( + t, + "click", + function (t) { + return e.onClick( + t + ); + } + ); + }, + }, + { + key: "onClick", + value: function (t) { + var e = + t.delegateTarget || + t.currentTarget, + n = + this.action( + e + ) || "copy", + r = m({ + action: n, + container: + this + .container, + target: this.target( + e + ), + text: this.text( + e + ), + }); + this.emit( + r + ? "success" + : "error", + { + action: n, + text: r, + trigger: e, + clearSelection: + function () { + e && + e.focus(), + document.activeElement.blur(), + window + .getSelection() + .removeAllRanges(); + }, + } + ); + }, + }, + { + key: "defaultAction", + value: function (t) { + return C( + "action", + t + ); + }, + }, + { + key: "defaultTarget", + value: function (t) { + var e = C( + "target", + t + ); + if (e) + return document.querySelector( + e + ); + }, + }, + { + key: "defaultText", + value: function (t) { + return C("text", t); + }, + }, + { + key: "destroy", + value: function () { + this.listener.destroy(); + }, + }, + ], + [ + { + key: "copy", + value: function (t) { + var e = + arguments.length > + 1 && + void 0 !== + arguments[1] + ? arguments[1] + : { + container: + document.body, + }; + return h(t, e); + }, + }, + { + key: "cut", + value: function (t) { + return l(t); + }, + }, + { + key: "isSupported", + value: function () { + var t = + arguments.length > + 0 && + void 0 !== + arguments[0] + ? arguments[0] + : [ + "copy", + "cut", + ], + e = + "string" === + typeof t + ? [t] + : t, + n = + !!document.queryCommandSupported; + return ( + e.forEach( + function ( + t + ) { + n = + n && + !!document.queryCommandSupported( + t + ); + } + ), + n + ); + }, + }, + ] + ), + n + ); + })(i()), + I = j; + }, + 828: function (t) { + var e = 9; + if ( + "undefined" !== typeof Element && + !Element.prototype.matches + ) { + var n = Element.prototype; + n.matches = + n.matchesSelector || + n.mozMatchesSelector || + n.msMatchesSelector || + n.oMatchesSelector || + n.webkitMatchesSelector; + } + function r(t, n) { + while (t && t.nodeType !== e) { + if ( + "function" === typeof t.matches && + t.matches(n) + ) + return t; + t = t.parentNode; + } + } + t.exports = r; + }, + 438: function (t, e, n) { + var r = n(828); + function i(t, e, n, r, i) { + var o = a.apply(this, arguments); + return ( + t.addEventListener(n, o, i), + { + destroy: function () { + t.removeEventListener(n, o, i); + }, + } + ); + } + function o(t, e, n, r, o) { + return "function" === + typeof t.addEventListener + ? i.apply(null, arguments) + : "function" === typeof n + ? i + .bind(null, document) + .apply(null, arguments) + : ("string" === typeof t && + (t = + document.querySelectorAll(t)), + Array.prototype.map.call( + t, + function (t) { + return i(t, e, n, r, o); + } + )); + } + function a(t, e, n, i) { + return function (n) { + (n.delegateTarget = r(n.target, e)), + n.delegateTarget && i.call(t, n); + }; + } + t.exports = o; + }, + 879: function (t, e) { + (e.node = function (t) { + return ( + void 0 !== t && + t instanceof HTMLElement && + 1 === t.nodeType + ); + }), + (e.nodeList = function (t) { + var n = + Object.prototype.toString.call(t); + return ( + void 0 !== t && + ("[object NodeList]" === n || + "[object HTMLCollection]" === + n) && + "length" in t && + (0 === t.length || e.node(t[0])) + ); + }), + (e.string = function (t) { + return ( + "string" === typeof t || + t instanceof String + ); + }), + (e.fn = function (t) { + var e = + Object.prototype.toString.call(t); + return "[object Function]" === e; + }); + }, + 370: function (t, e, n) { + var r = n(879), + i = n(438); + function o(t, e, n) { + if (!t && !e && !n) + throw new Error( + "Missing required arguments" + ); + if (!r.string(e)) + throw new TypeError( + "Second argument must be a String" + ); + if (!r.fn(n)) + throw new TypeError( + "Third argument must be a Function" + ); + if (r.node(t)) return a(t, e, n); + if (r.nodeList(t)) return s(t, e, n); + if (r.string(t)) return u(t, e, n); + throw new TypeError( + "First argument must be a String, HTMLElement, HTMLCollection, or NodeList" + ); + } + function a(t, e, n) { + return ( + t.addEventListener(e, n), + { + destroy: function () { + t.removeEventListener(e, n); + }, + } + ); + } + function s(t, e, n) { + return ( + Array.prototype.forEach.call( + t, + function (t) { + t.addEventListener(e, n); + } + ), + { + destroy: function () { + Array.prototype.forEach.call( + t, + function (t) { + t.removeEventListener( + e, + n + ); + } + ); + }, + } + ); + } + function u(t, e, n) { + return i(document.body, t, e, n); + } + t.exports = o; + }, + 817: function (t) { + function e(t) { + var e; + if ("SELECT" === t.nodeName) + t.focus(), (e = t.value); + else if ( + "INPUT" === t.nodeName || + "TEXTAREA" === t.nodeName + ) { + var n = t.hasAttribute("readonly"); + n || t.setAttribute("readonly", ""), + t.select(), + t.setSelectionRange( + 0, + t.value.length + ), + n || t.removeAttribute("readonly"), + (e = t.value); + } else { + t.hasAttribute("contenteditable") && + t.focus(); + var r = window.getSelection(), + i = document.createRange(); + i.selectNodeContents(t), + r.removeAllRanges(), + r.addRange(i), + (e = r.toString()); + } + return e; + } + t.exports = e; + }, + 279: function (t) { + function e() {} + (e.prototype = { + on: function (t, e, n) { + var r = this.e || (this.e = {}); + return ( + (r[t] || (r[t] = [])).push({ + fn: e, + ctx: n, + }), + this + ); + }, + once: function (t, e, n) { + var r = this; + function i() { + r.off(t, i), e.apply(n, arguments); + } + return (i._ = e), this.on(t, i, n); + }, + emit: function (t) { + var e = [].slice.call(arguments, 1), + n = ( + (this.e || (this.e = {}))[t] || + [] + ).slice(), + r = 0, + i = n.length; + for (r; r < i; r++) + n[r].fn.apply(n[r].ctx, e); + return this; + }, + off: function (t, e) { + var n = this.e || (this.e = {}), + r = n[t], + i = []; + if (r && e) + for ( + var o = 0, a = r.length; + o < a; + o++ + ) + r[o].fn !== e && + r[o].fn._ !== e && + i.push(r[o]); + return ( + i.length ? (n[t] = i) : delete n[t], + this + ); + }, + }), + (t.exports = e), + (t.exports.TinyEmitter = e); + }, + }, + e = {}; + function n(r) { + if (e[r]) return e[r].exports; + var i = (e[r] = { exports: {} }); + return t[r](i, i.exports, n), i.exports; + } + return ( + (function () { + n.n = function (t) { + var e = + t && t.__esModule + ? function () { + return t["default"]; + } + : function () { + return t; + }; + return n.d(e, { a: e }), e; + }; + })(), + (function () { + n.d = function (t, e) { + for (var r in e) + n.o(e, r) && + !n.o(t, r) && + Object.defineProperty(t, r, { + enumerable: !0, + get: e[r], + }); + }; + })(), + (function () { + n.o = function (t, e) { + return Object.prototype.hasOwnProperty.call( + t, + e + ); + }; + })(), + n(686) + ); + })().default; + }); + }, + b347: function (t, e, n) { + n("12fd9"), + n("93c4"), + n("b42c"), + n("edb9"), + n("8747"), + n("8a77"), + n("261e"), + (t.exports = n("a7d3").Map); + }, + b39a: function (t, e, n) { + var r = n("d3f4"); + t.exports = function (t, e) { + if (!r(t) || t._t !== e) + throw TypeError( + "Incompatible receiver, " + e + " required!" + ); + return t; + }; + }, + b3e7: function (t, e) { + t.exports = function () {}; + }, + b3ec: function (t, e, n) { + "use strict"; + var r = n("3adc"), + i = n("f845"); + t.exports = function (t, e, n) { + e in t ? r.f(t, e, i(0, n)) : (t[e] = n); + }; + }, + b42c: function (t, e, n) { + n("fa54"); + for ( + var r = n("da3c"), + i = n("8ce0"), + o = n("b22a"), + a = n("1b55")("toStringTag"), + s = + "CSSRuleList,CSSStyleDeclaration,CSSValueList,ClientRectList,DOMRectList,DOMStringList,DOMTokenList,DataTransferItemList,FileList,HTMLAllCollection,HTMLCollection,HTMLFormElement,HTMLSelectElement,MediaList,MimeTypeArray,NamedNodeMap,NodeList,PaintRequestList,Plugin,PluginArray,SVGLengthList,SVGNumberList,SVGPathSegList,SVGPointList,SVGStringList,SVGTransformList,SourceBufferList,StyleSheetList,TextTrackCueList,TextTrackList,TouchList".split( + "," + ), + u = 0; + u < s.length; + u++ + ) { + var c = s[u], + f = r[c], + l = f && f.prototype; + l && !l[a] && i(l, a, c), (o[c] = o.Array); + } + }, + b457: function (t, e) { + t.exports = !0; + }, + b5aa: function (t, e, n) { + var r = n("6e1f"); + t.exports = + Array.isArray || + function (t) { + return "Array" == r(t); + }; + }, + b5ae: function (t, e, n) { + "use strict"; + Object.defineProperty(e, "__esModule", { value: !0 }), + Object.defineProperty(e, "alpha", { + enumerable: !0, + get: function () { + return r.default; + }, + }), + Object.defineProperty(e, "alphaNum", { + enumerable: !0, + get: function () { + return i.default; + }, + }), + Object.defineProperty(e, "numeric", { + enumerable: !0, + get: function () { + return o.default; + }, + }), + Object.defineProperty(e, "between", { + enumerable: !0, + get: function () { + return a.default; + }, + }), + Object.defineProperty(e, "email", { + enumerable: !0, + get: function () { + return s.default; + }, + }), + Object.defineProperty(e, "ipAddress", { + enumerable: !0, + get: function () { + return u.default; + }, + }), + Object.defineProperty(e, "macAddress", { + enumerable: !0, + get: function () { + return c.default; + }, + }), + Object.defineProperty(e, "maxLength", { + enumerable: !0, + get: function () { + return f.default; + }, + }), + Object.defineProperty(e, "minLength", { + enumerable: !0, + get: function () { + return l.default; + }, + }), + Object.defineProperty(e, "required", { + enumerable: !0, + get: function () { + return d.default; + }, + }), + Object.defineProperty(e, "requiredIf", { + enumerable: !0, + get: function () { + return p.default; + }, + }), + Object.defineProperty(e, "requiredUnless", { + enumerable: !0, + get: function () { + return h.default; + }, + }), + Object.defineProperty(e, "sameAs", { + enumerable: !0, + get: function () { + return v.default; + }, + }), + Object.defineProperty(e, "url", { + enumerable: !0, + get: function () { + return y.default; + }, + }), + Object.defineProperty(e, "or", { + enumerable: !0, + get: function () { + return m.default; + }, + }), + Object.defineProperty(e, "and", { + enumerable: !0, + get: function () { + return g.default; + }, + }), + Object.defineProperty(e, "not", { + enumerable: !0, + get: function () { + return b.default; + }, + }), + Object.defineProperty(e, "minValue", { + enumerable: !0, + get: function () { + return _.default; + }, + }), + Object.defineProperty(e, "maxValue", { + enumerable: !0, + get: function () { + return w.default; + }, + }), + Object.defineProperty(e, "integer", { + enumerable: !0, + get: function () { + return x.default; + }, + }), + Object.defineProperty(e, "decimal", { + enumerable: !0, + get: function () { + return k.default; + }, + }), + (e.helpers = void 0); + var r = O(n("6235")), + i = O(n("3a54")), + o = O(n("45b8")), + a = O(n("ec11")), + s = O(n("5d75")), + u = O(n("c99d")), + c = O(n("91d3")), + f = O(n("2a12")), + l = O(n("5db3")), + d = O(n("d4f4")), + p = O(n("aa82")), + h = O(n("e652")), + v = O(n("b6cb")), + y = O(n("772d")), + m = O(n("d294")), + g = O(n("3360")), + b = O(n("6417")), + _ = O(n("eb66")), + w = O(n("46bc")), + x = O(n("1331e")), + k = O(n("c301")), + S = E(n("78ef")); + function E(t) { + if (t && t.__esModule) return t; + var e = {}; + if (null != t) + for (var n in t) + if (Object.prototype.hasOwnProperty.call(t, n)) { + var r = + Object.defineProperty && + Object.getOwnPropertyDescriptor + ? Object.getOwnPropertyDescriptor(t, n) + : {}; + r.get || r.set + ? Object.defineProperty(e, n, r) + : (e[n] = t[n]); + } + return (e.default = t), e; + } + function O(t) { + return t && t.__esModule ? t : { default: t }; + } + e.helpers = S; + }, + b6cb: function (t, e, n) { + "use strict"; + Object.defineProperty(e, "__esModule", { value: !0 }), + (e.default = void 0); + var r = n("78ef"), + i = function (t) { + return (0, r.withParams)( + { type: "sameAs", eq: t }, + function (e, n) { + return e === (0, r.ref)(t, this, n); + } + ); + }; + e.default = i; + }, + bc25: function (t, e, n) { + var r = n("f2fe"); + t.exports = function (t, e, n) { + if ((r(t), void 0 === e)) return t; + switch (n) { + case 1: + return function (n) { + return t.call(e, n); + }; + case 2: + return function (n, r) { + return t.call(e, n, r); + }; + case 3: + return function (n, r, i) { + return t.call(e, n, r, i); + }; + } + return function () { + return t.apply(e, arguments); + }; + }; + }, + bcaa: function (t, e, n) { + var r = n("cb7c"), + i = n("d3f4"), + o = n("a5b8"); + t.exports = function (t, e) { + if ((r(t), i(e) && e.constructor === t)) return e; + var n = o.f(t), + a = n.resolve; + return a(e), n.promise; + }; + }, + be13: function (t, e) { + t.exports = function (t) { + if (void 0 == t) throw TypeError("Can't call method on " + t); + return t; + }; + }, + bf06: function (t, e, n) { + var r = n("02d7"); + t.exports = function (t, e) { + return new (r(t))(e); + }; + }, + c0d8: function (t, e, n) { + var r = n("3adc").f, + i = n("43c8"), + o = n("1b55")("toStringTag"); + t.exports = function (t, e, n) { + t && + !i((t = n ? t : t.prototype), o) && + r(t, o, { configurable: !0, value: e }); + }; + }, + c165: function (t, e, n) { + var r = n("d13f"), + i = n("a7d3"), + o = n("d782"); + t.exports = function (t, e) { + var n = (i.Object || {})[t] || Object[t], + a = {}; + (a[t] = e(n)), + r( + r.S + + r.F * + o(function () { + n(1); + }), + "Object", + a + ); + }; + }, + c227: function (t, e, n) { + var r = n("b22a"), + i = n("1b55")("iterator"), + o = Array.prototype; + t.exports = function (t) { + return void 0 !== t && (r.Array === t || o[i] === t); + }; + }, + c26b: function (t, e, n) { + "use strict"; + var r = n("86cc").f, + i = n("2aeb"), + o = n("dcbc"), + a = n("9b43"), + s = n("f605"), + u = n("4a59"), + c = n("01f9"), + f = n("d53b"), + l = n("7a56"), + d = n("9e1e"), + p = n("67ab").fastKey, + h = n("b39a"), + v = d ? "_s" : "size", + y = function (t, e) { + var n, + r = p(e); + if ("F" !== r) return t._i[r]; + for (n = t._f; n; n = n.n) if (n.k == e) return n; + }; + t.exports = { + getConstructor: function (t, e, n, c) { + var f = t(function (t, r) { + s(t, f, e, "_i"), + (t._t = e), + (t._i = i(null)), + (t._f = void 0), + (t._l = void 0), + (t[v] = 0), + void 0 != r && u(r, n, t[c], t); + }); + return ( + o(f.prototype, { + clear: function () { + for ( + var t = h(this, e), n = t._i, r = t._f; + r; + r = r.n + ) + (r.r = !0), + r.p && (r.p = r.p.n = void 0), + delete n[r.i]; + (t._f = t._l = void 0), (t[v] = 0); + }, + delete: function (t) { + var n = h(this, e), + r = y(n, t); + if (r) { + var i = r.n, + o = r.p; + delete n._i[r.i], + (r.r = !0), + o && (o.n = i), + i && (i.p = o), + n._f == r && (n._f = i), + n._l == r && (n._l = o), + n[v]--; + } + return !!r; + }, + forEach: function (t) { + h(this, e); + var n, + r = a( + t, + arguments.length > 1 + ? arguments[1] + : void 0, + 3 + ); + while ((n = n ? n.n : this._f)) { + r(n.v, n.k, this); + while (n && n.r) n = n.p; + } + }, + has: function (t) { + return !!y(h(this, e), t); + }, + }), + d && + r(f.prototype, "size", { + get: function () { + return h(this, e)[v]; + }, + }), + f + ); + }, + def: function (t, e, n) { + var r, + i, + o = y(t, e); + return ( + o + ? (o.v = n) + : ((t._l = o = + { + i: (i = p(e, !0)), + k: e, + v: n, + p: (r = t._l), + n: void 0, + r: !1, + }), + t._f || (t._f = o), + r && (r.n = o), + t[v]++, + "F" !== i && (t._i[i] = o)), + t + ); + }, + getEntry: y, + setStrong: function (t, e, n) { + c( + t, + e, + function (t, n) { + (this._t = h(t, e)), + (this._k = n), + (this._l = void 0); + }, + function () { + var t = this, + e = t._k, + n = t._l; + while (n && n.r) n = n.p; + return t._t && (t._l = n = n ? n.n : t._t._f) + ? f( + 0, + "keys" == e + ? n.k + : "values" == e + ? n.v + : [n.k, n.v] + ) + : ((t._t = void 0), f(1)); + }, + n ? "entries" : "values", + !n, + !0 + ), + l(e); + }, + }; + }, + c301: function (t, e, n) { + "use strict"; + Object.defineProperty(e, "__esModule", { value: !0 }), + (e.default = void 0); + var r = n("78ef"), + i = (0, r.regex)("decimal", /^[-]?\d*(\.\d+)?$/); + e.default = i; + }, + c366: function (t, e, n) { + var r = n("6821"), + i = n("9def"), + o = n("77f1"); + t.exports = function (t) { + return function (e, n, a) { + var s, + u = r(e), + c = i(u.length), + f = o(a, c); + if (t && n != n) { + while (c > f) if (((s = u[f++]), s != s)) return !0; + } else + for (; c > f; f++) + if ((t || f in u) && u[f] === n) return t || f || 0; + return !t && -1; + }; + }; + }, + c437: function (t, e, n) { + var r, + i, + o = n("e1f4"), + a = n("2366"), + s = 0, + u = 0; + function c(t, e, n) { + var c = (e && n) || 0, + f = e || []; + t = t || {}; + var l = t.node || r, + d = void 0 !== t.clockseq ? t.clockseq : i; + if (null == l || null == d) { + var p = o(); + null == l && + (l = r = [1 | p[0], p[1], p[2], p[3], p[4], p[5]]), + null == d && (d = i = 16383 & ((p[6] << 8) | p[7])); + } + var h = void 0 !== t.msecs ? t.msecs : new Date().getTime(), + v = void 0 !== t.nsecs ? t.nsecs : u + 1, + y = h - s + (v - u) / 1e4; + if ( + (y < 0 && void 0 === t.clockseq && (d = (d + 1) & 16383), + (y < 0 || h > s) && void 0 === t.nsecs && (v = 0), + v >= 1e4) + ) + throw new Error( + "uuid.v1(): Can't create more than 10M uuids/sec" + ); + (s = h), (u = v), (i = d), (h += 122192928e5); + var m = (1e4 * (268435455 & h) + v) % 4294967296; + (f[c++] = (m >>> 24) & 255), + (f[c++] = (m >>> 16) & 255), + (f[c++] = (m >>> 8) & 255), + (f[c++] = 255 & m); + var g = ((h / 4294967296) * 1e4) & 268435455; + (f[c++] = (g >>> 8) & 255), + (f[c++] = 255 & g), + (f[c++] = ((g >>> 24) & 15) | 16), + (f[c++] = (g >>> 16) & 255), + (f[c++] = (d >>> 8) | 128), + (f[c++] = 255 & d); + for (var b = 0; b < 6; ++b) f[c + b] = l[b]; + return e || a(f); + } + t.exports = c; + }, + c64e: function (t, e, n) { + var r = n("e1f4"), + i = n("2366"); + function o(t, e, n) { + var o = (e && n) || 0; + "string" == typeof t && + ((e = "binary" === t ? new Array(16) : null), (t = null)), + (t = t || {}); + var a = t.random || (t.rng || r)(); + if (((a[6] = (15 & a[6]) | 64), (a[8] = (63 & a[8]) | 128), e)) + for (var s = 0; s < 16; ++s) e[o + s] = a[s]; + return e || i(a); + } + t.exports = o; + }, + c69a: function (t, e, n) { + t.exports = + !n("9e1e") && + !n("79e5")(function () { + return ( + 7 != + Object.defineProperty(n("230e")("div"), "a", { + get: function () { + return 7; + }, + }).a + ); + }); + }, + c8ba: function (t, e) { + var n; + n = (function () { + return this; + })(); + try { + n = n || new Function("return this")(); + } catch (r) { + "object" === typeof window && (n = window); + } + t.exports = n; + }, + c99d: function (t, e, n) { + "use strict"; + Object.defineProperty(e, "__esModule", { value: !0 }), + (e.default = void 0); + var r = n("78ef"), + i = (0, r.withParams)({ type: "ipAddress" }, function (t) { + if (!(0, r.req)(t)) return !0; + if ("string" !== typeof t) return !1; + var e = t.split("."); + return 4 === e.length && e.every(o); + }); + e.default = i; + var o = function (t) { + if (t.length > 3 || 0 === t.length) return !1; + if ("0" === t[0] && "0" !== t) return !1; + if (!t.match(/^\d+$/)) return !1; + var e = 0 | +t; + return e >= 0 && e <= 255; + }; + }, + ca5a: function (t, e) { + var n = 0, + r = Math.random(); + t.exports = function (t) { + return "Symbol(".concat( + void 0 === t ? "" : t, + ")_", + (++n + r).toString(36) + ); + }; + }, + cadf: function (t, e, n) { + "use strict"; + var r = n("9c6c"), + i = n("d53b"), + o = n("84f2"), + a = n("6821"); + (t.exports = n("01f9")( + Array, + "Array", + function (t, e) { + (this._t = a(t)), (this._i = 0), (this._k = e); + }, + function () { + var t = this._t, + e = this._k, + n = this._i++; + return !t || n >= t.length + ? ((this._t = void 0), i(1)) + : i( + 0, + "keys" == e ? n : "values" == e ? t[n] : [n, t[n]] + ); + }, + "values" + )), + (o.Arguments = o.Array), + r("keys"), + r("values"), + r("entries"); + }, + cb69: function (t, e, n) { + "use strict"; + (function (t) { + function n(t) { + return ( + (n = + "function" === typeof Symbol && + "symbol" === typeof Symbol.iterator + ? function (t) { + return typeof t; + } + : function (t) { + return t && + "function" === typeof Symbol && + t.constructor === Symbol && + t !== Symbol.prototype + ? "symbol" + : typeof t; + }), + n(t) + ); + } + Object.defineProperty(e, "__esModule", { value: !0 }), + (e.withParams = void 0); + var r = + "undefined" !== typeof window + ? window + : "undefined" !== typeof t + ? t + : {}, + i = function (t, e) { + return "object" === n(t) && void 0 !== e + ? e + : t(function () {}); + }, + o = r.vuelidate ? r.vuelidate.withParams : i; + e.withParams = o; + }.call(this, n("c8ba"))); + }, + cb7c: function (t, e, n) { + var r = n("d3f4"); + t.exports = function (t) { + if (!r(t)) throw TypeError(t + " is not an object!"); + return t; + }; + }, + cd1c: function (t, e, n) { + var r = n("e853"); + t.exports = function (t, e) { + return new (r(t))(e); + }; + }, + ce10: function (t, e, n) { + var r = n("69a8"), + i = n("6821"), + o = n("c366")(!1), + a = n("613b")("IE_PROTO"); + t.exports = function (t, e) { + var n, + s = i(t), + u = 0, + c = []; + for (n in s) n != a && r(s, n) && c.push(n); + while (e.length > u) + r(s, (n = e[u++])) && (~o(c, n) || c.push(n)); + return c; + }; + }, + d127: function (t, e, n) { + n("0a0a")("asyncIterator"); + }, + d13f: function (t, e, n) { + var r = n("da3c"), + i = n("a7d3"), + o = n("bc25"), + a = n("8ce0"), + s = n("43c8"), + u = "prototype", + c = function (t, e, n) { + var f, + l, + d, + p = t & c.F, + h = t & c.G, + v = t & c.S, + y = t & c.P, + m = t & c.B, + g = t & c.W, + b = h ? i : i[e] || (i[e] = {}), + _ = b[u], + w = h ? r : v ? r[e] : (r[e] || {})[u]; + for (f in (h && (n = e), n)) + (l = !p && w && void 0 !== w[f]), + (l && s(b, f)) || + ((d = l ? w[f] : n[f]), + (b[f] = + h && "function" != typeof w[f] + ? n[f] + : m && l + ? o(d, r) + : g && w[f] == d + ? (function (t) { + var e = function (e, n, r) { + if (this instanceof t) { + switch ( + arguments.length + ) { + case 0: + return new t(); + case 1: + return new t(e); + case 2: + return new t( + e, + n + ); + } + return new t(e, n, r); + } + return t.apply( + this, + arguments + ); + }; + return (e[u] = t[u]), e; + })(d) + : y && "function" == typeof d + ? o(Function.call, d) + : d), + y && + (((b.virtual || (b.virtual = {}))[f] = d), + t & c.R && _ && !_[f] && a(_, f, d))); + }; + (c.F = 1), + (c.G = 2), + (c.S = 4), + (c.P = 8), + (c.B = 16), + (c.W = 32), + (c.U = 64), + (c.R = 128), + (t.exports = c); + }, + d225: function (t, e, n) { + "use strict"; + function r(t, e) { + if (!(t instanceof e)) + throw new TypeError("Cannot call a class as a function"); + } + n.d(e, "a", function () { + return r; + }); + }, + d24f: function (t, e, n) { + n("0a0a")("observable"); + }, + d256: function (t, e, n) { + "use strict"; + var r = n("da3c"), + i = n("43c8"), + o = n("7d95"), + a = n("d13f"), + s = n("2312"), + u = n("6277").KEY, + c = n("d782"), + f = n("7772"), + l = n("c0d8"), + d = n("7b00"), + p = n("1b55"), + h = n("fda1"), + v = n("0a0a"), + y = n("d2d6"), + m = n("b5aa"), + g = n("0f89"), + b = n("6f8a"), + _ = n("0185"), + w = n("6a9b"), + x = n("2ea1"), + k = n("f845"), + S = n("7108"), + E = n("565d"), + O = n("626e"), + T = n("31c2"), + A = n("3adc"), + C = n("7633"), + j = O.f, + I = A.f, + M = E.f, + L = r.Symbol, + P = r.JSON, + N = P && P.stringify, + R = "prototype", + $ = p("_hidden"), + D = p("toPrimitive"), + F = {}.propertyIsEnumerable, + z = f("symbol-registry"), + U = f("symbols"), + B = f("op-symbols"), + V = Object[R], + H = "function" == typeof L && !!T.f, + q = r.QObject, + W = !q || !q[R] || !q[R].findChild, + G = + o && + c(function () { + return ( + 7 != + S( + I({}, "a", { + get: function () { + return I(this, "a", { value: 7 }).a; + }, + }) + ).a + ); + }) + ? function (t, e, n) { + var r = j(V, e); + r && delete V[e], + I(t, e, n), + r && t !== V && I(V, e, r); + } + : I, + Z = function (t) { + var e = (U[t] = S(L[R])); + return (e._k = t), e; + }, + J = + H && "symbol" == typeof L.iterator + ? function (t) { + return "symbol" == typeof t; + } + : function (t) { + return t instanceof L; + }, + K = function (t, e, n) { + return ( + t === V && K(B, e, n), + g(t), + (e = x(e, !0)), + g(n), + i(U, e) + ? (n.enumerable + ? (i(t, $) && t[$][e] && (t[$][e] = !1), + (n = S(n, { enumerable: k(0, !1) }))) + : (i(t, $) || I(t, $, k(1, {})), + (t[$][e] = !0)), + G(t, e, n)) + : I(t, e, n) + ); + }, + X = function (t, e) { + g(t); + var n, + r = y((e = w(e))), + i = 0, + o = r.length; + while (o > i) K(t, (n = r[i++]), e[n]); + return t; + }, + Y = function (t, e) { + return void 0 === e ? S(t) : X(S(t), e); + }, + Q = function (t) { + var e = F.call(this, (t = x(t, !0))); + return ( + !(this === V && i(U, t) && !i(B, t)) && + (!( + e || + !i(this, t) || + !i(U, t) || + (i(this, $) && this[$][t]) + ) || + e) + ); + }, + tt = function (t, e) { + if ( + ((t = w(t)), + (e = x(e, !0)), + t !== V || !i(U, e) || i(B, e)) + ) { + var n = j(t, e); + return ( + !n || + !i(U, e) || + (i(t, $) && t[$][e]) || + (n.enumerable = !0), + n + ); + } + }, + et = function (t) { + var e, + n = M(w(t)), + r = [], + o = 0; + while (n.length > o) + i(U, (e = n[o++])) || e == $ || e == u || r.push(e); + return r; + }, + nt = function (t) { + var e, + n = t === V, + r = M(n ? B : w(t)), + o = [], + a = 0; + while (r.length > a) + !i(U, (e = r[a++])) || (n && !i(V, e)) || o.push(U[e]); + return o; + }; + H || + ((L = function () { + if (this instanceof L) + throw TypeError("Symbol is not a constructor!"); + var t = d(arguments.length > 0 ? arguments[0] : void 0), + e = function (n) { + this === V && e.call(B, n), + i(this, $) && + i(this[$], t) && + (this[$][t] = !1), + G(this, t, k(1, n)); + }; + return ( + o && W && G(V, t, { configurable: !0, set: e }), Z(t) + ); + }), + s(L[R], "toString", function () { + return this._k; + }), + (O.f = tt), + (A.f = K), + (n("d876").f = E.f = et), + (n("d74e").f = Q), + (T.f = nt), + o && !n("b457") && s(V, "propertyIsEnumerable", Q, !0), + (h.f = function (t) { + return Z(p(t)); + })), + a(a.G + a.W + a.F * !H, { Symbol: L }); + for ( + var rt = + "hasInstance,isConcatSpreadable,iterator,match,replace,search,species,split,toPrimitive,toStringTag,unscopables".split( + "," + ), + it = 0; + rt.length > it; + + ) + p(rt[it++]); + for (var ot = C(p.store), at = 0; ot.length > at; ) v(ot[at++]); + a(a.S + a.F * !H, "Symbol", { + for: function (t) { + return i(z, (t += "")) ? z[t] : (z[t] = L(t)); + }, + keyFor: function (t) { + if (!J(t)) throw TypeError(t + " is not a symbol!"); + for (var e in z) if (z[e] === t) return e; + }, + useSetter: function () { + W = !0; + }, + useSimple: function () { + W = !1; + }, + }), + a(a.S + a.F * !H, "Object", { + create: Y, + defineProperty: K, + defineProperties: X, + getOwnPropertyDescriptor: tt, + getOwnPropertyNames: et, + getOwnPropertySymbols: nt, + }); + var st = c(function () { + T.f(1); + }); + a(a.S + a.F * st, "Object", { + getOwnPropertySymbols: function (t) { + return T.f(_(t)); + }, + }), + P && + a( + a.S + + a.F * + (!H || + c(function () { + var t = L(); + return ( + "[null]" != N([t]) || + "{}" != N({ a: t }) || + "{}" != N(Object(t)) + ); + })), + "JSON", + { + stringify: function (t) { + var e, + n, + r = [t], + i = 1; + while (arguments.length > i) + r.push(arguments[i++]); + if ( + ((n = e = r[1]), + (b(e) || void 0 !== t) && !J(t)) + ) + return ( + m(e) || + (e = function (t, e) { + if ( + ("function" == typeof n && + (e = n.call( + this, + t, + e + )), + !J(e)) + ) + return e; + }), + (r[1] = e), + N.apply(P, r) + ); + }, + } + ), + L[R][D] || n("8ce0")(L[R], D, L[R].valueOf), + l(L, "Symbol"), + l(Math, "Math", !0), + l(r.JSON, "JSON", !0); + }, + d294: function (t, e, n) { + "use strict"; + Object.defineProperty(e, "__esModule", { value: !0 }), + (e.default = void 0); + var r = n("78ef"), + i = function () { + for ( + var t = arguments.length, e = new Array(t), n = 0; + n < t; + n++ + ) + e[n] = arguments[n]; + return (0, r.withParams)({ type: "or" }, function () { + for ( + var t = this, + n = arguments.length, + r = new Array(n), + i = 0; + i < n; + i++ + ) + r[i] = arguments[i]; + return ( + e.length > 0 && + e.reduce(function (e, n) { + return e || n.apply(t, r); + }, !1) + ); + }); + }; + e.default = i; + }, + d2c8: function (t, e, n) { + var r = n("aae3"), + i = n("be13"); + t.exports = function (t, e, n) { + if (r(e)) + throw TypeError("String#" + n + " doesn't accept regex!"); + return String(i(t)); + }; + }, + d2d6: function (t, e, n) { + var r = n("7633"), + i = n("31c2"), + o = n("d74e"); + t.exports = function (t) { + var e = r(t), + n = i.f; + if (n) { + var a, + s = n(t), + u = o.f, + c = 0; + while (s.length > c) u.call(t, (a = s[c++])) && e.push(a); + } + return e; + }; + }, + d3f4: function (t, e) { + t.exports = function (t) { + return "object" === typeof t + ? null !== t + : "function" === typeof t; + }; + }, + d4f4: function (t, e, n) { + "use strict"; + Object.defineProperty(e, "__esModule", { value: !0 }), + (e.default = void 0); + var r = n("78ef"), + i = (0, r.withParams)({ type: "required" }, r.req); + e.default = i; + }, + d53b: function (t, e) { + t.exports = function (t, e) { + return { value: e, done: !!t }; + }; + }, + d604: function (t, e, n) { + n("1938"), (t.exports = n("a7d3").Array.isArray); + }, + d74e: function (t, e) { + e.f = {}.propertyIsEnumerable; + }, + d782: function (t, e) { + t.exports = function (t) { + try { + return !!t(); + } catch (e) { + return !0; + } + }; + }, + d876: function (t, e, n) { + var r = n("2695"), + i = n("0029").concat("length", "prototype"); + e.f = + Object.getOwnPropertyNames || + function (t) { + return r(t, i); + }; + }, + d8e8: function (t, e) { + t.exports = function (t) { + if ("function" != typeof t) + throw TypeError(t + " is not a function!"); + return t; + }; + }, + da3c: function (t, e) { + var n = (t.exports = + "undefined" != typeof window && window.Math == Math + ? window + : "undefined" != typeof self && self.Math == Math + ? self + : Function("return this")()); + "number" == typeof __g && (__g = n); + }, + dcbc: function (t, e, n) { + var r = n("2aba"); + t.exports = function (t, e, n) { + for (var i in e) r(t, i, e[i], n); + return t; + }; + }, + e0b8: function (t, e, n) { + "use strict"; + var r = n("7726"), + i = n("5ca1"), + o = n("2aba"), + a = n("dcbc"), + s = n("67ab"), + u = n("4a59"), + c = n("f605"), + f = n("d3f4"), + l = n("79e5"), + d = n("5cc5"), + p = n("7f20"), + h = n("5dbc"); + t.exports = function (t, e, n, v, y, m) { + var g = r[t], + b = g, + _ = y ? "set" : "add", + w = b && b.prototype, + x = {}, + k = function (t) { + var e = w[t]; + o( + w, + t, + "delete" == t || "has" == t + ? function (t) { + return ( + !(m && !f(t)) && + e.call(this, 0 === t ? 0 : t) + ); + } + : "get" == t + ? function (t) { + return m && !f(t) + ? void 0 + : e.call(this, 0 === t ? 0 : t); + } + : "add" == t + ? function (t) { + return ( + e.call(this, 0 === t ? 0 : t), this + ); + } + : function (t, n) { + return ( + e.call(this, 0 === t ? 0 : t, n), this + ); + } + ); + }; + if ( + "function" == typeof b && + (m || + (w.forEach && + !l(function () { + new b().entries().next(); + }))) + ) { + var S = new b(), + E = S[_](m ? {} : -0, 1) != S, + O = l(function () { + S.has(1); + }), + T = d(function (t) { + new b(t); + }), + A = + !m && + l(function () { + var t = new b(), + e = 5; + while (e--) t[_](e, e); + return !t.has(-0); + }); + T || + ((b = e(function (e, n) { + c(e, b, t); + var r = h(new g(), e, b); + return void 0 != n && u(n, y, r[_], r), r; + })), + (b.prototype = w), + (w.constructor = b)), + (O || A) && (k("delete"), k("has"), y && k("get")), + (A || E) && k(_), + m && w.clear && delete w.clear; + } else + (b = v.getConstructor(e, t, y, _)), + a(b.prototype, n), + (s.NEED = !0); + return ( + p(b, t), + (x[t] = b), + i(i.G + i.W + i.F * (b != g), x), + m || v.setStrong(b, t, y), + b + ); + }; + }, + e11e: function (t, e) { + t.exports = + "constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split( + "," + ); + }, + e1f4: function (t, e) { + var n = + ("undefined" != typeof crypto && + crypto.getRandomValues && + crypto.getRandomValues.bind(crypto)) || + ("undefined" != typeof msCrypto && + "function" == typeof window.msCrypto.getRandomValues && + msCrypto.getRandomValues.bind(msCrypto)); + if (n) { + var r = new Uint8Array(16); + t.exports = function () { + return n(r), r; + }; + } else { + var i = new Array(16); + t.exports = function () { + for (var t, e = 0; e < 16; e++) + 0 === (3 & e) && (t = 4294967296 * Math.random()), + (i[e] = (t >>> ((3 & e) << 3)) & 255); + return i; + }; + } + }, + e341: function (t, e, n) { + var r = n("d13f"); + r(r.S + r.F * !n("7d95"), "Object", { + defineProperty: n("3adc").f, + }); + }, + e4a9: function (t, e, n) { + "use strict"; + var r = n("b457"), + i = n("d13f"), + o = n("2312"), + a = n("8ce0"), + s = n("b22a"), + u = n("5ce7"), + c = n("c0d8"), + f = n("ff0c"), + l = n("1b55")("iterator"), + d = !([].keys && "next" in [].keys()), + p = "@@iterator", + h = "keys", + v = "values", + y = function () { + return this; + }; + t.exports = function (t, e, n, m, g, b, _) { + u(n, e, m); + var w, + x, + k, + S = function (t) { + if (!d && t in A) return A[t]; + switch (t) { + case h: + return function () { + return new n(this, t); + }; + case v: + return function () { + return new n(this, t); + }; + } + return function () { + return new n(this, t); + }; + }, + E = e + " Iterator", + O = g == v, + T = !1, + A = t.prototype, + C = A[l] || A[p] || (g && A[g]), + j = C || S(g), + I = g ? (O ? S("entries") : j) : void 0, + M = ("Array" == e && A.entries) || C; + if ( + (M && + ((k = f(M.call(new t()))), + k !== Object.prototype && + k.next && + (c(k, E, !0), + r || "function" == typeof k[l] || a(k, l, y))), + O && + C && + C.name !== v && + ((T = !0), + (j = function () { + return C.call(this); + })), + (r && !_) || (!d && !T && A[l]) || a(A, l, j), + (s[e] = j), + (s[E] = y), + g) + ) + if ( + ((w = { + values: O ? j : S(v), + keys: b ? j : S(h), + entries: I, + }), + _) + ) + for (x in w) x in A || o(A, x, w[x]); + else i(i.P + i.F * (d || T), e, w); + return w; + }; + }, + e5fa: function (t, e) { + t.exports = function (t) { + if (void 0 == t) throw TypeError("Can't call method on " + t); + return t; + }; + }, + e652: function (t, e, n) { + "use strict"; + Object.defineProperty(e, "__esModule", { value: !0 }), + (e.default = void 0); + var r = n("78ef"), + i = function (t) { + return (0, r.withParams)( + { type: "requiredUnless", prop: t }, + function (e, n) { + return !!(0, r.ref)(t, this, n) || (0, r.req)(e); + } + ); + }; + e.default = i; + }, + e853: function (t, e, n) { + var r = n("d3f4"), + i = n("1169"), + o = n("2b4c")("species"); + t.exports = function (t) { + var e; + return ( + i(t) && + ((e = t.constructor), + "function" != typeof e || + (e !== Array && !i(e.prototype)) || + (e = void 0), + r(e) && ((e = e[o]), null === e && (e = void 0))), + void 0 === e ? Array : e + ); + }; + }, + eb46: function (t, e, n) { + var r = n("bc25"), + i = n("8bab"), + o = n("0185"), + a = n("a5ab"), + s = n("bf06"); + t.exports = function (t, e) { + var n = 1 == t, + u = 2 == t, + c = 3 == t, + f = 4 == t, + l = 6 == t, + d = 5 == t || l, + p = e || s; + return function (e, s, h) { + for ( + var v, + y, + m = o(e), + g = i(m), + b = r(s, h, 3), + _ = a(g.length), + w = 0, + x = n ? p(e, _) : u ? p(e, 0) : void 0; + _ > w; + w++ + ) + if ((d || w in g) && ((v = g[w]), (y = b(v, w, m)), t)) + if (n) x[w] = y; + else if (y) + switch (t) { + case 3: + return !0; + case 5: + return v; + case 6: + return w; + case 2: + x.push(v); + } + else if (f) return !1; + return l ? -1 : c || f ? f : x; + }; + }; + }, + eb66: function (t, e, n) { + "use strict"; + Object.defineProperty(e, "__esModule", { value: !0 }), + (e.default = void 0); + var r = n("78ef"), + i = function (t) { + return (0, r.withParams)( + { type: "minValue", min: t }, + function (e) { + return ( + !(0, r.req)(e) || + ((!/\s/.test(e) || e instanceof Date) && + +e >= +t) + ); + } + ); + }; + e.default = i; + }, + ebd6: function (t, e, n) { + var r = n("cb7c"), + i = n("d8e8"), + o = n("2b4c")("species"); + t.exports = function (t, e) { + var n, + a = r(t).constructor; + return void 0 === a || void 0 == (n = r(a)[o]) ? e : i(n); + }; + }, + ec11: function (t, e, n) { + "use strict"; + Object.defineProperty(e, "__esModule", { value: !0 }), + (e.default = void 0); + var r = n("78ef"), + i = function (t, e) { + return (0, r.withParams)( + { type: "between", min: t, max: e }, + function (n) { + return ( + !(0, r.req)(n) || + ((!/\s/.test(n) || n instanceof Date) && + +t <= +n && + +e >= +n) + ); + } + ); + }; + e.default = i; + }, + ec5b: function (t, e, n) { + n("e341"); + var r = n("a7d3").Object; + t.exports = function (t, e, n) { + return r.defineProperty(t, e, n); + }; + }, + edb9: function (t, e, n) { + "use strict"; + var r = n("0780"), + i = n("1fca"), + o = "Map"; + t.exports = n("f2f6")( + o, + function (t) { + return function () { + return t( + this, + arguments.length > 0 ? arguments[0] : void 0 + ); + }; + }, + { + get: function (t) { + var e = r.getEntry(i(this, o), t); + return e && e.v; + }, + set: function (t, e) { + return r.def(i(this, o), 0 === t ? 0 : t, e); + }, + }, + r, + !0 + ); + }, + f0c1: function (t, e, n) { + "use strict"; + var r = n("d8e8"), + i = n("d3f4"), + o = n("31f4"), + a = [].slice, + s = {}, + u = function (t, e, n) { + if (!(e in s)) { + for (var r = [], i = 0; i < e; i++) + r[i] = "a[" + i + "]"; + s[e] = Function( + "F,a", + "return new F(" + r.join(",") + ")" + ); + } + return s[e](t, n); + }; + t.exports = + Function.bind || + function (t) { + var e = r(this), + n = a.call(arguments, 1), + s = function () { + var r = n.concat(a.call(arguments)); + return this instanceof s + ? u(e, r.length, r) + : o(e, r, t); + }; + return i(e.prototype) && (s.prototype = e.prototype), s; + }; + }, + f159: function (t, e, n) { + var r = n("7d8a"), + i = n("1b55")("iterator"), + o = n("b22a"); + t.exports = n("a7d3").getIteratorMethod = function (t) { + if (void 0 != t) return t[i] || t["@@iterator"] || o[r(t)]; + }; + }, + f28b: function (t, e, n) { + "use strict"; + var r = n("2d7d"), + i = n.n(r), + o = n("4aa6"), + a = n.n(o), + s = n("6bb5"), + u = n("54b6"); + function c(t) { + return ( + -1 !== Function.toString.call(t).indexOf("[native code]") + ); + } + var f = n("a5b2"), + l = n.n(f); + function d() { + if ("undefined" === typeof Reflect || !l.a) return !1; + if (l.a.sham) return !1; + if ("function" === typeof Proxy) return !0; + try { + return ( + Boolean.prototype.valueOf.call( + l()(Boolean, [], function () {}) + ), + !0 + ); + } catch (t) { + return !1; + } + } + function p(t, e, n) { + return ( + (p = d() + ? l.a + : function (t, e, n) { + var r = [null]; + r.push.apply(r, e); + var i = Function.bind.apply(t, r), + o = new i(); + return n && Object(u["a"])(o, n.prototype), o; + }), + p.apply(null, arguments) + ); + } + function h(t) { + var e = "function" === typeof i.a ? new i.a() : void 0; + return ( + (h = function (t) { + if (null === t || !c(t)) return t; + if ("function" !== typeof t) + throw new TypeError( + "Super expression must either be null or a function" + ); + if ("undefined" !== typeof e) { + if (e.has(t)) return e.get(t); + e.set(t, n); + } + function n() { + return p( + t, + arguments, + Object(s["a"])(this).constructor + ); + } + return ( + (n.prototype = a()(t.prototype, { + constructor: { + value: n, + enumerable: !1, + writable: !0, + configurable: !0, + }, + })), + Object(u["a"])(n, t) + ); + }), + h(t) + ); + } + n.d(e, "a", function () { + return h; + }); + }, + f28c: function (t, e) { + var n, + r, + i = (t.exports = {}); + function o() { + throw new Error("setTimeout has not been defined"); + } + function a() { + throw new Error("clearTimeout has not been defined"); + } + function s(t) { + if (n === setTimeout) return setTimeout(t, 0); + if ((n === o || !n) && setTimeout) + return (n = setTimeout), setTimeout(t, 0); + try { + return n(t, 0); + } catch (e) { + try { + return n.call(null, t, 0); + } catch (e) { + return n.call(this, t, 0); + } + } + } + function u(t) { + if (r === clearTimeout) return clearTimeout(t); + if ((r === a || !r) && clearTimeout) + return (r = clearTimeout), clearTimeout(t); + try { + return r(t); + } catch (e) { + try { + return r.call(null, t); + } catch (e) { + return r.call(this, t); + } + } + } + (function () { + try { + n = "function" === typeof setTimeout ? setTimeout : o; + } catch (t) { + n = o; + } + try { + r = "function" === typeof clearTimeout ? clearTimeout : a; + } catch (t) { + r = a; + } + })(); + var c, + f = [], + l = !1, + d = -1; + function p() { + l && + c && + ((l = !1), + c.length ? (f = c.concat(f)) : (d = -1), + f.length && h()); + } + function h() { + if (!l) { + var t = s(p); + l = !0; + var e = f.length; + while (e) { + (c = f), (f = []); + while (++d < e) c && c[d].run(); + (d = -1), (e = f.length); + } + (c = null), (l = !1), u(t); + } + } + function v(t, e) { + (this.fun = t), (this.array = e); + } + function y() {} + (i.nextTick = function (t) { + var e = new Array(arguments.length - 1); + if (arguments.length > 1) + for (var n = 1; n < arguments.length; n++) + e[n - 1] = arguments[n]; + f.push(new v(t, e)), 1 !== f.length || l || s(h); + }), + (v.prototype.run = function () { + this.fun.apply(null, this.array); + }), + (i.title = "browser"), + (i.browser = !0), + (i.env = {}), + (i.argv = []), + (i.version = ""), + (i.versions = {}), + (i.on = y), + (i.addListener = y), + (i.once = y), + (i.off = y), + (i.removeListener = y), + (i.removeAllListeners = y), + (i.emit = y), + (i.prependListener = y), + (i.prependOnceListener = y), + (i.listeners = function (t) { + return []; + }), + (i.binding = function (t) { + throw new Error("process.binding is not supported"); + }), + (i.cwd = function () { + return "/"; + }), + (i.chdir = function (t) { + throw new Error("process.chdir is not supported"); + }), + (i.umask = function () { + return 0; + }); + }, + f2f6: function (t, e, n) { + "use strict"; + var r = n("da3c"), + i = n("d13f"), + o = n("6277"), + a = n("d782"), + s = n("8ce0"), + u = n("3904"), + c = n("560b"), + f = n("b0bc"), + l = n("6f8a"), + d = n("c0d8"), + p = n("3adc").f, + h = n("eb46")(0), + v = n("7d95"); + t.exports = function (t, e, n, y, m, g) { + var b = r[t], + _ = b, + w = m ? "set" : "add", + x = _ && _.prototype, + k = {}; + return ( + v && + "function" == typeof _ && + (g || + (x.forEach && + !a(function () { + new _().entries().next(); + }))) + ? ((_ = e(function (e, n) { + f(e, _, t, "_c"), + (e._c = new b()), + void 0 != n && c(n, m, e[w], e); + })), + h( + "add,clear,delete,forEach,get,has,set,keys,values,entries,toJSON".split( + "," + ), + function (t) { + var e = "add" == t || "set" == t; + !(t in x) || + (g && "clear" == t) || + s(_.prototype, t, function (n, r) { + if ((f(this, _, t), !e && g && !l(n))) + return "get" == t && void 0; + var i = this._c[t]( + 0 === n ? 0 : n, + r + ); + return e ? this : i; + }); + } + ), + g || + p(_.prototype, "size", { + get: function () { + return this._c.size; + }, + })) + : ((_ = y.getConstructor(e, t, m, w)), + u(_.prototype, n), + (o.NEED = !0)), + d(_, t), + (k[t] = _), + i(i.G + i.W + i.F, k), + g || y.setStrong(_, t, m), + _ + ); + }; + }, + f2fe: function (t, e) { + t.exports = function (t) { + if ("function" != typeof t) + throw TypeError(t + " is not a function!"); + return t; + }; + }, + f400: function (t, e, n) { + "use strict"; + var r = n("c26b"), + i = n("b39a"), + o = "Map"; + t.exports = n("e0b8")( + o, + function (t) { + return function () { + return t( + this, + arguments.length > 0 ? arguments[0] : void 0 + ); + }; + }, + { + get: function (t) { + var e = r.getEntry(i(this, o), t); + return e && e.v; + }, + set: function (t, e) { + return r.def(i(this, o), 0 === t ? 0 : t, e); + }, + }, + r, + !0 + ); + }, + f4bb: function (t, e, n) { + n("2498"), (t.exports = n("a7d3").Reflect.construct); + }, + f559: function (t, e, n) { + "use strict"; + var r = n("5ca1"), + i = n("9def"), + o = n("d2c8"), + a = "startsWith", + s = ""[a]; + r(r.P + r.F * n("5147")(a), "String", { + startsWith: function (t) { + var e = o(this, t, a), + n = i( + Math.min( + arguments.length > 1 ? arguments[1] : void 0, + e.length + ) + ), + r = String(t); + return s ? s.call(e, r, n) : e.slice(n, n + r.length) === r; + }, + }); + }, + f568: function (t, e, n) { + var r = n("3adc"), + i = n("0f89"), + o = n("7633"); + t.exports = n("7d95") + ? Object.defineProperties + : function (t, e) { + i(t); + var n, + a = o(e), + s = a.length, + u = 0; + while (s > u) r.f(t, (n = a[u++]), e[n]); + return t; + }; + }, + f605: function (t, e) { + t.exports = function (t, e, n, r) { + if (!(t instanceof e) || (void 0 !== r && r in t)) + throw TypeError(n + ": incorrect invocation!"); + return t; + }; + }, + f751: function (t, e, n) { + var r = n("5ca1"); + r(r.S + r.F, "Object", { assign: n("7333") }); + }, + f845: function (t, e) { + t.exports = function (t, e) { + return { + enumerable: !(1 & t), + configurable: !(2 & t), + writable: !(4 & t), + value: e, + }; + }; + }, + fa54: function (t, e, n) { + "use strict"; + var r = n("b3e7"), + i = n("245b"), + o = n("b22a"), + a = n("6a9b"); + (t.exports = n("e4a9")( + Array, + "Array", + function (t, e) { + (this._t = a(t)), (this._i = 0), (this._k = e); + }, + function () { + var t = this._t, + e = this._k, + n = this._i++; + return !t || n >= t.length + ? ((this._t = void 0), i(1)) + : i( + 0, + "keys" == e ? n : "values" == e ? t[n] : [n, t[n]] + ); + }, + "values" + )), + (o.Arguments = o.Array), + r("keys"), + r("values"), + r("entries"); + }, + fa5b: function (t, e, n) { + t.exports = n("5537")( + "native-function-to-string", + Function.toString + ); + }, + fab2: function (t, e, n) { + var r = n("7726").document; + t.exports = r && r.documentElement; + }, + fbf4: function (t, e, n) { + "use strict"; + function r(t) { + return null === t || void 0 === t; + } + function i(t) { + return null !== t && void 0 !== t; + } + function o(t, e) { + return e.tag === t.tag && e.key === t.key; + } + function a(t) { + var e = t.tag; + t.vm = new e({ data: t.args }); + } + function s(t) { + for (var e = Object.keys(t.args), n = 0; n < e.length; n++) + e.forEach(function (e) { + t.vm[e] = t.args[e]; + }); + } + function u(t, e, n) { + var r, + o, + a = {}; + for (r = e; r <= n; ++r) (o = t[r].key), i(o) && (a[o] = r); + return a; + } + function c(t, e) { + var n, + s, + c, + p = 0, + h = 0, + v = t.length - 1, + y = t[0], + m = t[v], + g = e.length - 1, + b = e[0], + _ = e[g]; + while (p <= v && h <= g) + r(y) + ? (y = t[++p]) + : r(m) + ? (m = t[--v]) + : o(y, b) + ? (d(y, b), (y = t[++p]), (b = e[++h])) + : o(m, _) + ? (d(m, _), (m = t[--v]), (_ = e[--g])) + : o(y, _) + ? (d(y, _), (y = t[++p]), (_ = e[--g])) + : o(m, b) + ? (d(m, b), (m = t[--v]), (b = e[++h])) + : (r(n) && (n = u(t, p, v)), + (s = i(b.key) ? n[b.key] : null), + r(s) + ? (a(b), (b = e[++h])) + : ((c = t[s]), + o(c, b) + ? (d(c, b), (t[s] = void 0), (b = e[++h])) + : (a(b), (b = e[++h])))); + p > v ? f(e, h, g) : h > g && l(t, p, v); + } + function f(t, e, n) { + for (; e <= n; ++e) a(t[e]); + } + function l(t, e, n) { + for (; e <= n; ++e) { + var r = t[e]; + i(r) && (r.vm.$destroy(), (r.vm = null)); + } + } + function d(t, e) { + t !== e && ((e.vm = t.vm), s(e)); + } + function p(t, e) { + i(t) && i(e) + ? t !== e && c(t, e) + : i(e) + ? f(e, 0, e.length - 1) + : i(t) && l(t, 0, t.length - 1); + } + function h(t, e, n) { + return { tag: t, key: e, args: n }; + } + Object.defineProperty(e, "__esModule", { value: !0 }), + (e.patchChildren = p), + (e.h = h); + }, + fda1: function (t, e, n) { + e.f = n("1b55"); + }, + ff0c: function (t, e, n) { + var r = n("43c8"), + i = n("0185"), + o = n("5d8f")("IE_PROTO"), + a = Object.prototype; + t.exports = + Object.getPrototypeOf || + function (t) { + return ( + (t = i(t)), + r(t, o) + ? t[o] + : "function" == typeof t.constructor && + t instanceof t.constructor + ? t.constructor.prototype + : t instanceof Object + ? a + : null + ); + }; + }, + }, +]); diff --git a/queries/js/IoT_Embla/Sonos/connect_sonos.js b/queries/js/IoT_Embla/Sonos/connect_sonos.js new file mode 100644 index 00000000..e69de29b From b0e8d1f3cf6612a333ae82ef51821106cf5b2b3f Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Wed, 22 Jun 2022 15:19:38 +0000 Subject: [PATCH 053/371] starting something... --- queries/js/IoT_Embla/Sonos/groups.js | 18 ++++++++++++++++++ queries/js/IoT_Embla/Sonos/household.js | 15 +++++++++++++++ queries/js/IoT_Embla/Sonos/playback.js | 19 +++++++++++++++++++ 3 files changed, 52 insertions(+) create mode 100644 queries/js/IoT_Embla/Sonos/groups.js create mode 100644 queries/js/IoT_Embla/Sonos/household.js create mode 100644 queries/js/IoT_Embla/Sonos/playback.js diff --git a/queries/js/IoT_Embla/Sonos/groups.js b/queries/js/IoT_Embla/Sonos/groups.js new file mode 100644 index 00000000..d0d946dc --- /dev/null +++ b/queries/js/IoT_Embla/Sonos/groups.js @@ -0,0 +1,18 @@ +function getGroups() { + let myHeaders = new Headers(); + myHeaders.append("Authorization", "Bearer fhFVRX5CX0Zo8pRI7s366IbRRUQ0"); + + let requestOptions = { + method: "GET", + headers: myHeaders, + redirect: "follow", + }; + + fetch( + "https://api.ws.sonos.com/control/api/v1/households/Sonos_2qmmZYj1IfZpziI3yTZT2AdYkP.LzZPKytb_zgm6t3fVIv7/groups", + requestOptions + ) + .then((response) => response.text()) + .then((result) => console.log(result)) + .catch((error) => console.log("error", error)); +} diff --git a/queries/js/IoT_Embla/Sonos/household.js b/queries/js/IoT_Embla/Sonos/household.js new file mode 100644 index 00000000..4c3dfc25 --- /dev/null +++ b/queries/js/IoT_Embla/Sonos/household.js @@ -0,0 +1,15 @@ +function getHousehold() { + let myHeaders = new Headers(); + myHeaders.append("Authorization", "Bearer fhFVRX5CX0Zo8pRI7s366IbRRUQ0"); + + let requestOptions = { + method: "GET", + headers: myHeaders, + redirect: "follow", + }; + + fetch("https://api.ws.sonos.com/control/api/v2/households", requestOptions) + .then((response) => response.text()) + .then((result) => console.log(result)) + .catch((error) => console.log("error", error)); +} diff --git a/queries/js/IoT_Embla/Sonos/playback.js b/queries/js/IoT_Embla/Sonos/playback.js new file mode 100644 index 00000000..2a47390e --- /dev/null +++ b/queries/js/IoT_Embla/Sonos/playback.js @@ -0,0 +1,19 @@ +function togglePlayPause() { + var myHeaders = new Headers(); + myHeaders.append("Content-Type", "application/json"); + myHeaders.append("Authorization", "Bearer fhFVRX5CX0Zo8pRI7s366IbRRUQ0"); + + var requestOptions = { + method: "POST", + headers: myHeaders, + redirect: "follow", + }; + + fetch( + "https://api.ws.sonos.com/control/api/v1/groups/RINCON_542A1B599FF201400:2388243335/playback/togglePlayPause", + requestOptions + ) + .then((response) => response.text()) + .then((result) => console.log(result)) + .catch((error) => console.log("error", error)); +} From 88a4c6f746594aea83ac69a89983b119fadae886 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Wed, 22 Jun 2022 15:26:17 +0000 Subject: [PATCH 054/371] Fruitmanager generalized to work without specifying fruits --- queries/fruit_seller/fruitseller.yaml | 4 +- queries/fruit_seller/fruitstate.py | 24 ++++------- queries/fruit_seller/resource.py | 26 +++++++++++- queries/fruitseller.py | 59 ++++++++++++++++----------- 4 files changed, 70 insertions(+), 43 deletions(-) diff --git a/queries/fruit_seller/fruitseller.yaml b/queries/fruit_seller/fruitseller.yaml index a62870a2..4738e8a8 100644 --- a/queries/fruit_seller/fruitseller.yaml +++ b/queries/fruit_seller/fruitseller.yaml @@ -10,8 +10,8 @@ resources: type: "ListResource" repeatable: true # verification_function: "check_fruits" - confirm_prompt: "Pöntunin samanstendur af {curr_order}. Viltu staðfesta pöntunina?" - repeat_prompt: "Pöntunin samanstendur af {curr_order}. Verður það eitthvað fleira?" + confirm_prompt: "Pöntunin samanstendur af {list_items}. Viltu staðfesta pöntunina?" + repeat_prompt: "Pöntunin samanstendur af {list_items}. Verður það eitthvað fleira?" - name: "Date" type: "DatetimeResource" # verification_function: "check_date" diff --git a/queries/fruit_seller/fruitstate.py b/queries/fruit_seller/fruitstate.py index 118ff195..578d5ea0 100644 --- a/queries/fruit_seller/fruitstate.py +++ b/queries/fruit_seller/fruitstate.py @@ -45,22 +45,14 @@ def __init__( self.resourceState: Optional[Resource] = None self.ans: Optional[str] = None - def initialize_resources(self, dialogue: str) -> None: - # Order here is the priority of each resource - obj = load_dialogue_structure("fruit_seller/fruitseller.yaml") - print("Resources: ", obj["resources"]) - # print(obj["resources"]) - # TODO: parse yaml, add resources from yaml file - - self.resources.append(FruitState(prompt="Hvaða ávexti má bjóða þér?")) - self.resources.append(OrderReceivedState()) - self.updateState(dialogue) - - def generate_answer(self, type: str) -> None: - if self.resourceState is not None: - self.ans = self.resourceState.generate_answer(type) - else: - self.ans = "Kom upp villa, reyndu aftur." + def generate_answer(self, result: Result) -> str: + for resource in self.resources: + if resource.required and resource.state is not ResourceState.CONFIRMED: + if "callbacks" in result: + for cb in result.callbacks: + cb(resource, result) + return resource.generate_answer() + return "Upp kom villa, reyndu aftur." def updateState(self, type: str) -> None: for resource in self.resources: diff --git a/queries/fruit_seller/resource.py b/queries/fruit_seller/resource.py index dbfc75c9..ea695114 100644 --- a/queries/fruit_seller/resource.py +++ b/queries/fruit_seller/resource.py @@ -4,11 +4,19 @@ from datetime import datetime from dataclasses import dataclass -from queries import ResourceType +from reynir import NounPhrase +from queries import ResourceType, natlang_seq, sing_or_plur BaseResourceTypes = Union[str, int, float, bool, datetime, None] ListResourceType = List[BaseResourceTypes] +def _list_items(items: Any) -> str: + item_list: List[str] = [] + for num, name in items: + # TODO: get general plural form + plural_name: str = NounPhrase(name).dative or name + item_list.append(sing_or_plur(num, name, plural_name)) + return natlang_seq(item_list) class ResourceState(Enum): UNFULFILLED = auto() @@ -35,7 +43,7 @@ class Resource: def next_action(self) -> Any: raise NotImplementedError() - def generate_answer(self, type: str) -> str: + def generate_answer(self) -> str: raise NotImplementedError() def update(self, new_data: Optional[ResourceType]) -> None: @@ -49,6 +57,20 @@ class ListResource(Resource): def list_available_options(self) -> str: raise NotImplementedError() + def generate_answer(self) -> str: + ans: str = "" + if self.state is ResourceState.UNFULFILLED: + if self._repeat_count == 0 or not self.repeatable: + ans = self.prompt + if self.state is ResourceState.PARTIALLY_FULFILLED: + if self.repeat_prompt: + ans = f"{self.repeat_prompt.format(list_items = _list_items(self.data))}" + if self.state is ResourceState.FULFILLED: + if self.confirm_prompt: + ans = f"{self.confirm_prompt.format(list_items = _list_items(self.data))}" + return ans + + # TODO: # ExactlyOneResource (choose one resource from options) # SetResource (a set of resources)? diff --git a/queries/fruitseller.py b/queries/fruitseller.py index 3784d426..0d0c25e9 100644 --- a/queries/fruitseller.py +++ b/queries/fruitseller.py @@ -1,4 +1,4 @@ -from typing import Optional, cast +from typing import List, Literal, Optional, Tuple, cast import logging @@ -6,6 +6,7 @@ from tree import Result, Node from queries import DialogueStructureType, gen_answer, parse_num from queries.fruit_seller.fruitstate import DialogueStateManager +from queries.fruit_seller.resource import Resource, ResourceState # Indicate that this module wants to handle parse trees for queries, # as opposed to simple literal text strings @@ -109,18 +110,23 @@ """ _START_CONVERSATION_QTYPE = "QFruitStartQuery" -_DIALOGUE_NAME = "fruit_seller" - +_DIALOGUE_NAME = "fruitseller" def QFruitStartQuery(node: Node, params: QueryStateDict, result: Result): result.qtype = _START_CONVERSATION_QTYPE def QAddFruitQuery(node: Node, params: QueryStateDict, result: Result): + if "callbacks" not in result: + result["callbacks"] = [] + result.callbacks.append(_add_fruit) result.qtype = "QAddFruitQuery" def QRemoveFruitQuery(node: Node, params: QueryStateDict, result: Result): + if "callbacks" not in result: + result["callbacks"] = [] + result.callbacks.append(_remove_fruit) result.qtype = "QRemoveFruitQuery" @@ -142,11 +148,11 @@ def QNo(node: Node, params: QueryStateDict, result: Result): def QNumOfFruit(node: Node, params: QueryStateDict, result: Result): if "queryfruits" not in result: - result.queryfruits = dict() + result["queryfruits"] = [] if "fruitnumber" not in result: - result.queryfruits[result.fruit] = 1 + result.queryfruits.append((1, result.fruit)) else: - result.queryfruits[result.fruit] = result.fruitnumber + result.queryfruits.append(result.fruitnumber, result.fruit) def QNum(node: Node, params: QueryStateDict, result: Result): @@ -162,6 +168,21 @@ def QFruit(node: Node, params: QueryStateDict, result: Result): if fruit is not None: result.fruit = fruit +def _remove_fruit(resource: Resource, result: Result) -> None: + if resource.data is not None: + for fruitnum, fruitname in result.queryfruits: + for number, name in resource.data: + if name == fruitname: + resource.data.remove((number, name)) + break + +def _add_fruit(resource: Resource, result: Result) -> None: + if resource.data is None: + resource.data = [] + for number, name in result.queryfruits: + resource.data.append((number, name)) + resource.state = ResourceState.PARTIALLY_FULFILLED + def sentence(state: QueryStateDict, result: Result) -> None: """Called when sentence processing is complete""" @@ -169,36 +190,28 @@ def sentence(state: QueryStateDict, result: Result) -> None: dialogue_state = q.get_dialogue_state() or {} qt = result.get("qtype") + print("Dialogue state: ", dialogue_state) # checka hvort user se i samtali med q.client_data if qt != _START_CONVERSATION_QTYPE and not ( - dialogue_state and dialogue_state.get("in_dialogue") == _DIALOGUE_NAME + dialogue_state and dialogue_state.get("dialogue_name") == _DIALOGUE_NAME ): q.set_error("E_QUERY_NOT_UNDERSTOOD") return - + print("Fyrir dsm") dsm = DialogueStateManager("fruit_seller/fruitseller.yaml", q.get_dialogue_state()) - + print("Eftir dms") # Successfully matched a query type try: if result.qtype == _START_CONVERSATION_QTYPE: - q.set_dialogue_state() + q.set_dialogue_state({"dialogue_name": "fruitseller", "resources": [], "variables": None}) else: + print("Í else") if dialogue_state is None: q.set_error("E_QUERY_NOT_UNDERSTOOD") return - fruitmanager_serialized: Optional[str] = cast( - Optional[str], dialogue_state.get("state") - ) - if not fruitmanager_serialized: - q.set_error("E_QUERY_NOT_UNDERSTOOD") - return - fruitStateManager = DialogueStateManager.deserialize( - fruitmanager_serialized - ) - - fruitStateManager.stateMethod(result.qtype, result) - - ans = fruitStateManager.generate_answer() + print("fyrir generate") + ans = dsm.generate_answer(result) + print("eftir generate") if result.qtype == "OrderComplete" or result.qtype == "CancelOrder": q.end_dialogue() From ca5e3f440ebb83f677da974d96f65503d0bbfe21 Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Wed, 22 Jun 2022 15:27:15 +0000 Subject: [PATCH 055/371] made html --- queries/js/IoT_Embla/Sonos/sonos.html | 70 +++++++++++++++++++ .../IoT_Embla/{main.html => philips_hue.html} | 0 2 files changed, 70 insertions(+) create mode 100644 queries/js/IoT_Embla/Sonos/sonos.html rename queries/js/IoT_Embla/{main.html => philips_hue.html} (100%) diff --git a/queries/js/IoT_Embla/Sonos/sonos.html b/queries/js/IoT_Embla/Sonos/sonos.html new file mode 100644 index 00000000..44f84a6e --- /dev/null +++ b/queries/js/IoT_Embla/Sonos/sonos.html @@ -0,0 +1,70 @@ + + + + + + JS Hue test + + + + + +
+

Testing

+ + +

+ +
+

Brightness

+ +
+ +
+

Color

+ + + + + +

+
+ +
+ +
+
+ + +
+
+ +
+
+

Brightness increase/decrease

+ + + + +

+
+
+ +
+
+
+ + + + + + + +
+ + +
+ + + diff --git a/queries/js/IoT_Embla/main.html b/queries/js/IoT_Embla/philips_hue.html similarity index 100% rename from queries/js/IoT_Embla/main.html rename to queries/js/IoT_Embla/philips_hue.html From 54ea0505247966853a7a3597ea6e1f140ae10ecc Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Wed, 22 Jun 2022 15:55:00 +0000 Subject: [PATCH 056/371] added to html --- queries/js/IoT_Embla/Sonos/playback.js | 7 ++++-- queries/js/IoT_Embla/Sonos/sonos.html | 31 ++++++-------------------- 2 files changed, 12 insertions(+), 26 deletions(-) diff --git a/queries/js/IoT_Embla/Sonos/playback.js b/queries/js/IoT_Embla/Sonos/playback.js index 2a47390e..c9a2019b 100644 --- a/queries/js/IoT_Embla/Sonos/playback.js +++ b/queries/js/IoT_Embla/Sonos/playback.js @@ -1,9 +1,10 @@ function togglePlayPause() { - var myHeaders = new Headers(); + let myHeaders = new Headers(); myHeaders.append("Content-Type", "application/json"); myHeaders.append("Authorization", "Bearer fhFVRX5CX0Zo8pRI7s366IbRRUQ0"); + myHeaders.append("Allow-Control-Allow-Origin", "https://api.ws.sonos.com"); - var requestOptions = { + let requestOptions = { method: "POST", headers: myHeaders, redirect: "follow", @@ -17,3 +18,5 @@ function togglePlayPause() { .then((result) => console.log(result)) .catch((error) => console.log("error", error)); } + +togglePlayPause(); \ No newline at end of file diff --git a/queries/js/IoT_Embla/Sonos/sonos.html b/queries/js/IoT_Embla/Sonos/sonos.html index 44f84a6e..81179f2e 100644 --- a/queries/js/IoT_Embla/Sonos/sonos.html +++ b/queries/js/IoT_Embla/Sonos/sonos.html @@ -12,38 +12,21 @@

Testing

-

-

Brightness

+

Volume

-
-

Color

- - - - - -

-
- -
- +
+
-
- - +
+
-
- -
-
+ @@ -61,7 +44,7 @@

Testing

-
+
-->
From 80387998739be4f953eba9ca1d30814680d925aa Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Wed, 22 Jun 2022 16:03:48 +0000 Subject: [PATCH 057/371] fixed issues with html --- queries/js/IoT_Embla/Sonos/connect_sonos.js | 0 queries/js/IoT_Embla/Sonos/groups.js | 4 ++-- queries/js/IoT_Embla/Sonos/household.js | 2 +- queries/js/IoT_Embla/Sonos/playback.js | 6 +++--- queries/js/IoT_Embla/Sonos/sonos.html | 3 ++- queries/js/IoT_Embla/Sonos/sonos.js | 3 +++ 6 files changed, 11 insertions(+), 7 deletions(-) delete mode 100644 queries/js/IoT_Embla/Sonos/connect_sonos.js create mode 100644 queries/js/IoT_Embla/Sonos/sonos.js diff --git a/queries/js/IoT_Embla/Sonos/connect_sonos.js b/queries/js/IoT_Embla/Sonos/connect_sonos.js deleted file mode 100644 index e69de29b..00000000 diff --git a/queries/js/IoT_Embla/Sonos/groups.js b/queries/js/IoT_Embla/Sonos/groups.js index d0d946dc..3b8957f6 100644 --- a/queries/js/IoT_Embla/Sonos/groups.js +++ b/queries/js/IoT_Embla/Sonos/groups.js @@ -1,6 +1,6 @@ function getGroups() { let myHeaders = new Headers(); - myHeaders.append("Authorization", "Bearer fhFVRX5CX0Zo8pRI7s366IbRRUQ0"); + myHeaders.append("Authorization", `Bearer ${bearerToken}`); let requestOptions = { method: "GET", @@ -9,7 +9,7 @@ function getGroups() { }; fetch( - "https://api.ws.sonos.com/control/api/v1/households/Sonos_2qmmZYj1IfZpziI3yTZT2AdYkP.LzZPKytb_zgm6t3fVIv7/groups", + `https://api.ws.sonos.com/control/api/v1/households/${sonosHouseholdID}/groups`, requestOptions ) .then((response) => response.text()) diff --git a/queries/js/IoT_Embla/Sonos/household.js b/queries/js/IoT_Embla/Sonos/household.js index 4c3dfc25..09ee0027 100644 --- a/queries/js/IoT_Embla/Sonos/household.js +++ b/queries/js/IoT_Embla/Sonos/household.js @@ -1,6 +1,6 @@ function getHousehold() { let myHeaders = new Headers(); - myHeaders.append("Authorization", "Bearer fhFVRX5CX0Zo8pRI7s366IbRRUQ0"); + myHeaders.append("Authorization", `Bearer ${bearerToken}`); let requestOptions = { method: "GET", diff --git a/queries/js/IoT_Embla/Sonos/playback.js b/queries/js/IoT_Embla/Sonos/playback.js index c9a2019b..9a4e8a99 100644 --- a/queries/js/IoT_Embla/Sonos/playback.js +++ b/queries/js/IoT_Embla/Sonos/playback.js @@ -1,7 +1,7 @@ function togglePlayPause() { let myHeaders = new Headers(); myHeaders.append("Content-Type", "application/json"); - myHeaders.append("Authorization", "Bearer fhFVRX5CX0Zo8pRI7s366IbRRUQ0"); + myHeaders.append("Authorization", `Bearer ${bearerToken}`); myHeaders.append("Allow-Control-Allow-Origin", "https://api.ws.sonos.com"); let requestOptions = { @@ -11,7 +11,7 @@ function togglePlayPause() { }; fetch( - "https://api.ws.sonos.com/control/api/v1/groups/RINCON_542A1B599FF201400:2388243335/playback/togglePlayPause", + `https://api.ws.sonos.com/control/api/v1/groups/${sonosGroupID}/playback/togglePlayPause`, requestOptions ) .then((response) => response.text()) @@ -19,4 +19,4 @@ function togglePlayPause() { .catch((error) => console.log("error", error)); } -togglePlayPause(); \ No newline at end of file +// togglePlayPause(); diff --git a/queries/js/IoT_Embla/Sonos/sonos.html b/queries/js/IoT_Embla/Sonos/sonos.html index 81179f2e..a960f90c 100644 --- a/queries/js/IoT_Embla/Sonos/sonos.html +++ b/queries/js/IoT_Embla/Sonos/sonos.html @@ -3,7 +3,8 @@ - JS Hue test + Sonos Test + diff --git a/queries/js/IoT_Embla/Sonos/sonos.js b/queries/js/IoT_Embla/Sonos/sonos.js new file mode 100644 index 00000000..d2e7e057 --- /dev/null +++ b/queries/js/IoT_Embla/Sonos/sonos.js @@ -0,0 +1,3 @@ +var bearerToken = "fhFVRX5CX0Zo8pRI7s366IbRRUQ0"; +var sonosGroupID = "RINCON_542A1B599FF201400:2388243335"; +var sonosHouseholdID = "Sonos_2qmmZYj1IfZpziI3yTZT2AdYkP.LzZPKytb_zgm6t3fVIv7"; From f39615753e2fbdefff0ed44e6969ce8b4b79402c Mon Sep 17 00:00:00 2001 From: Logi E Date: Wed, 22 Jun 2022 16:24:10 +0000 Subject: [PATCH 058/371] quick (sloppy) fixes to make removing fruits work --- queries/__init__.py | 51 ++++++++++++------------------ queries/fruit_seller/fruitstate.py | 3 +- queries/fruit_seller/resource.py | 39 +++++++++++++++++------ queries/fruitseller.py | 29 +++++++++++------ query.py | 2 +- 5 files changed, 73 insertions(+), 51 deletions(-) diff --git a/queries/__init__.py b/queries/__init__.py index 168616b3..e278bc98 100755 --- a/queries/__init__.py +++ b/queries/__init__.py @@ -36,7 +36,6 @@ Union, cast, ) -from typing_extensions import TypedDict from tree import Node import logging @@ -708,39 +707,31 @@ def join_grammar_files(folder: str) -> str: return "\n".join(grammar) -class ResourceType(TypedDict, total=False): - """ - Representation of a single resource in a dialogue. - """ +# class ResourceType(TypedDict, total=False): +# """ +# Representation of a single resource in a dialogue. +# """ - name: str - prompt: str - type: str - repeat_prompt: Optional[str] - required: bool - repeatable: bool - # verification_function: "check_fruits" - confirm_prompt: Optional[str] - data: Any - # state: int # TODO: wrong type? - cancel_prompt: Optional[str] - # next_states: List[Any] - # - QNo: #"verification_prompt" - # - QYes: "Date" - # - QNo: "repeat_prompt" - # - QCancel: *Cancel - -class DialogueStructureType(TypedDict): - """ - A dialogue structure is a list of resources used in a dialogue. - """ +# name: str +# prompt: str +# type: str +# repeat_prompt: Optional[str] +# required: bool +# repeatable: bool +# # verification_function: "check_fruits" +# confirm_prompt: Optional[str] +# data: Any +# # state: int # TODO: wrong type? +# cancel_prompt: Optional[str] +# # next_states: List[Any] +# # - QNo: #"verification_prompt" +# # - QYes: "Date" +# # - QNo: "repeat_prompt" +# # - QCancel: *Cancel - dialogue_name: str - variables: Optional[List[Any]] - resources: List[ResourceType] -def load_dialogue_structure(filename: str) -> DialogueStructureType: +def load_dialogue_structure(filename: str) -> Any: """Loads dialogue structure from YAML file.""" basepath, _ = os.path.split(os.path.realpath(__file__)) fpath = os.path.join(basepath, filename) diff --git a/queries/fruit_seller/fruitstate.py b/queries/fruit_seller/fruitstate.py index 578d5ea0..532177c1 100644 --- a/queries/fruit_seller/fruitstate.py +++ b/queries/fruit_seller/fruitstate.py @@ -39,7 +39,8 @@ def __init__( else: newResource = DatetimeResource(**resource) if saved_state and i < len(saved_state["resources"]): - newResource.update(saved_state["resources"][i]) + newResource.__dict__.update(saved_state["resources"][i]) + newResource.state = ResourceState(saved_state["resources"][i]["state"]) self.resources.append(newResource) self.resourceState: Optional[Resource] = None diff --git a/queries/fruit_seller/resource.py b/queries/fruit_seller/resource.py index ea695114..8e1eb61b 100644 --- a/queries/fruit_seller/resource.py +++ b/queries/fruit_seller/resource.py @@ -1,15 +1,17 @@ -from typing import Any, Union, List, Optional +from typing import Any, Dict, Union, List, Optional +from typing_extensions import TypedDict -from enum import Enum, auto +from enum import IntEnum, auto from datetime import datetime from dataclasses import dataclass from reynir import NounPhrase -from queries import ResourceType, natlang_seq, sing_or_plur +from queries import natlang_seq, sing_or_plur BaseResourceTypes = Union[str, int, float, bool, datetime, None] ListResourceType = List[BaseResourceTypes] + def _list_items(items: Any) -> str: item_list: List[str] = [] for num, name in items: @@ -18,7 +20,18 @@ def _list_items(items: Any) -> str: item_list.append(sing_or_plur(num, name, plural_name)) return natlang_seq(item_list) -class ResourceState(Enum): + +class DialogueStructureType(TypedDict): + """ + A dialogue structure is a list of resources used in a dialogue. + """ + + dialogue_name: str + variables: Optional[List[Any]] + resources: List[Dict[Any, Any]] + + +class ResourceState(IntEnum): UNFULFILLED = auto() PARTIALLY_FULFILLED = auto() FULFILLED = auto() @@ -42,13 +55,14 @@ class Resource: def next_action(self) -> Any: raise NotImplementedError() - + def generate_answer(self) -> str: raise NotImplementedError() - def update(self, new_data: Optional[ResourceType]) -> None: + def update(self, new_data: Optional["Resource"]) -> None: if new_data: - self.__dict__.update(new_data) + self.__dict__.update(new_data.__dict__) + class ListResource(Resource): data: ListResourceType = [] @@ -64,18 +78,23 @@ def generate_answer(self) -> str: ans = self.prompt if self.state is ResourceState.PARTIALLY_FULFILLED: if self.repeat_prompt: - ans = f"{self.repeat_prompt.format(list_items = _list_items(self.data))}" + ans = ( + f"{self.repeat_prompt.format(list_items = _list_items(self.data))}" + ) if self.state is ResourceState.FULFILLED: if self.confirm_prompt: - ans = f"{self.confirm_prompt.format(list_items = _list_items(self.data))}" + ans = ( + f"{self.confirm_prompt.format(list_items = _list_items(self.data))}" + ) return ans - + # TODO: # ExactlyOneResource (choose one resource from options) # SetResource (a set of resources)? # ... + class YesNoResource(Resource): data: Optional[bool] = None diff --git a/queries/fruitseller.py b/queries/fruitseller.py index 0d0c25e9..10e8d26b 100644 --- a/queries/fruitseller.py +++ b/queries/fruitseller.py @@ -1,10 +1,8 @@ -from typing import List, Literal, Optional, Tuple, cast - import logging -from query import DIALOGUE_DATA_KEY, Query, QueryStateDict +from query import Query, QueryStateDict from tree import Result, Node -from queries import DialogueStructureType, gen_answer, parse_num +from queries import gen_answer, parse_num from queries.fruit_seller.fruitstate import DialogueStateManager from queries.fruit_seller.resource import Resource, ResourceState @@ -112,6 +110,7 @@ _START_CONVERSATION_QTYPE = "QFruitStartQuery" _DIALOGUE_NAME = "fruitseller" + def QFruitStartQuery(node: Node, params: QueryStateDict, result: Result): result.qtype = _START_CONVERSATION_QTYPE @@ -150,9 +149,9 @@ def QNumOfFruit(node: Node, params: QueryStateDict, result: Result): if "queryfruits" not in result: result["queryfruits"] = [] if "fruitnumber" not in result: - result.queryfruits.append((1, result.fruit)) + result.queryfruits.append([1, result.fruit]) else: - result.queryfruits.append(result.fruitnumber, result.fruit) + result.queryfruits.append([result.fruitnumber, result.fruit]) def QNum(node: Node, params: QueryStateDict, result: Result): @@ -168,14 +167,16 @@ def QFruit(node: Node, params: QueryStateDict, result: Result): if fruit is not None: result.fruit = fruit + def _remove_fruit(resource: Resource, result: Result) -> None: if resource.data is not None: - for fruitnum, fruitname in result.queryfruits: + for _, fruitname in result.queryfruits: for number, name in resource.data: if name == fruitname: - resource.data.remove((number, name)) + resource.data.remove([number, name]) break + def _add_fruit(resource: Resource, result: Result) -> None: if resource.data is None: resource.data = [] @@ -203,7 +204,9 @@ def sentence(state: QueryStateDict, result: Result) -> None: # Successfully matched a query type try: if result.qtype == _START_CONVERSATION_QTYPE: - q.set_dialogue_state({"dialogue_name": "fruitseller", "resources": [], "variables": None}) + q.set_dialogue_state( + {"dialogue_name": "fruitseller", "resources": [], "variables": None} + ) else: print("Í else") if dialogue_state is None: @@ -212,6 +215,14 @@ def sentence(state: QueryStateDict, result: Result) -> None: print("fyrir generate") ans = dsm.generate_answer(result) print("eftir generate") + q.set_dialogue_state( + { + "dialogue_name": "fruitseller", + "resources": [r.__dict__ for r in dsm.resources], + "variables": None, + } + ) + print("woohoo") if result.qtype == "OrderComplete" or result.qtype == "CancelOrder": q.end_dialogue() diff --git a/query.py b/query.py index 4b5ffd7f..dff8d9a6 100755 --- a/query.py +++ b/query.py @@ -74,7 +74,7 @@ from islenska.bindb import BinFilterFunc from tree import Tree, TreeStateDict, Node -from queries import DialogueStructureType +from queries.fruit_seller.resource import DialogueStructureType # from nertokenizer import recognize_entities from images import get_image_url From 0afd4489e436e74aa3bd36972aef8c0b0aa8dc0a Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Wed, 22 Jun 2022 16:26:45 +0000 Subject: [PATCH 059/371] volume slider, play pause, variables --- queries/js/IoT_Embla/Sonos/groups.js | 2 +- queries/js/IoT_Embla/Sonos/household.js | 2 +- queries/js/IoT_Embla/Sonos/playback.js | 28 ++++++++++++++++++++++++- queries/js/IoT_Embla/Sonos/sonos.html | 2 +- queries/js/IoT_Embla/Sonos/sonos.js | 2 +- 5 files changed, 31 insertions(+), 5 deletions(-) diff --git a/queries/js/IoT_Embla/Sonos/groups.js b/queries/js/IoT_Embla/Sonos/groups.js index 3b8957f6..c4db0989 100644 --- a/queries/js/IoT_Embla/Sonos/groups.js +++ b/queries/js/IoT_Embla/Sonos/groups.js @@ -1,6 +1,6 @@ function getGroups() { let myHeaders = new Headers(); - myHeaders.append("Authorization", `Bearer ${bearerToken}`); + myHeaders.append("Authorization", `Bearer ${sonosBearerToken}`); let requestOptions = { method: "GET", diff --git a/queries/js/IoT_Embla/Sonos/household.js b/queries/js/IoT_Embla/Sonos/household.js index 09ee0027..454cfa9e 100644 --- a/queries/js/IoT_Embla/Sonos/household.js +++ b/queries/js/IoT_Embla/Sonos/household.js @@ -1,6 +1,6 @@ function getHousehold() { let myHeaders = new Headers(); - myHeaders.append("Authorization", `Bearer ${bearerToken}`); + myHeaders.append("Authorization", `Bearer ${sonosBearerToken}`); let requestOptions = { method: "GET", diff --git a/queries/js/IoT_Embla/Sonos/playback.js b/queries/js/IoT_Embla/Sonos/playback.js index 9a4e8a99..4f46f250 100644 --- a/queries/js/IoT_Embla/Sonos/playback.js +++ b/queries/js/IoT_Embla/Sonos/playback.js @@ -1,7 +1,7 @@ function togglePlayPause() { let myHeaders = new Headers(); myHeaders.append("Content-Type", "application/json"); - myHeaders.append("Authorization", `Bearer ${bearerToken}`); + myHeaders.append("Authorization", `Bearer ${sonosBearerToken}`); myHeaders.append("Allow-Control-Allow-Origin", "https://api.ws.sonos.com"); let requestOptions = { @@ -19,4 +19,30 @@ function togglePlayPause() { .catch((error) => console.log("error", error)); } +function setVolume() { + let volume = document.getElementById("volume_slider").value; + let myHeaders = new Headers(); + myHeaders.append("Content-Type", "application/json"); + myHeaders.append("Authorization", `Bearer ${sonosBearerToken}`); + + let raw = JSON.stringify({ + volume: volume, + }); + + let requestOptions = { + method: "POST", + headers: myHeaders, + body: raw, + redirect: "follow", + }; + + fetch( + `https://api.ws.sonos.com/control/api/v1/groups/${sonosGroupID}/groupVolume`, + requestOptions + ) + .then((response) => response.text()) + .then((result) => console.log(result)) + .catch((error) => console.log("error", error)); +} + // togglePlayPause(); diff --git a/queries/js/IoT_Embla/Sonos/sonos.html b/queries/js/IoT_Embla/Sonos/sonos.html index a960f90c..e95d02b5 100644 --- a/queries/js/IoT_Embla/Sonos/sonos.html +++ b/queries/js/IoT_Embla/Sonos/sonos.html @@ -17,7 +17,7 @@

Testing

Volume

- +
diff --git a/queries/js/IoT_Embla/Sonos/sonos.js b/queries/js/IoT_Embla/Sonos/sonos.js index d2e7e057..9275c5ae 100644 --- a/queries/js/IoT_Embla/Sonos/sonos.js +++ b/queries/js/IoT_Embla/Sonos/sonos.js @@ -1,3 +1,3 @@ -var bearerToken = "fhFVRX5CX0Zo8pRI7s366IbRRUQ0"; +var sonosBearerToken = "fhFVRX5CX0Zo8pRI7s366IbRRUQ0"; var sonosGroupID = "RINCON_542A1B599FF201400:2388243335"; var sonosHouseholdID = "Sonos_2qmmZYj1IfZpziI3yTZT2AdYkP.LzZPKytb_zgm6t3fVIv7"; From a4aef5a4f79ac026d57df56d0c55bd51d50dd70e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Wed, 22 Jun 2022 16:42:04 +0000 Subject: [PATCH 060/371] Added _parse_no and _parse_yes --- queries/fruit_seller/resource.py | 3 +++ queries/fruitseller.py | 26 ++++++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/queries/fruit_seller/resource.py b/queries/fruit_seller/resource.py index 8e1eb61b..2eeffd67 100644 --- a/queries/fruit_seller/resource.py +++ b/queries/fruit_seller/resource.py @@ -72,6 +72,7 @@ def list_available_options(self) -> str: raise NotImplementedError() def generate_answer(self) -> str: + print("State: ", self.state) ans: str = "" if self.state is ResourceState.UNFULFILLED: if self._repeat_count == 0 or not self.repeatable: @@ -86,6 +87,8 @@ def generate_answer(self) -> str: ans = ( f"{self.confirm_prompt.format(list_items = _list_items(self.data))}" ) + if self.state is ResourceState.CONFIRMED: + ans = "Pöntunin er staðfest." return ans diff --git a/queries/fruitseller.py b/queries/fruitseller.py index 10e8d26b..653ce0a2 100644 --- a/queries/fruitseller.py +++ b/queries/fruitseller.py @@ -138,10 +138,16 @@ def QFruitOptionsQuery(node: Node, params: QueryStateDict, result: Result): def QYes(node: Node, params: QueryStateDict, result: Result): + if "callbacks" not in result: + result["callbacks"] = [] + result.callbacks.append(_parse_yes) result.qtype = "QYes" def QNo(node: Node, params: QueryStateDict, result: Result): + if "callbacks" not in result: + result["callbacks"] = [] + result.callbacks.append(_parse_no) result.qtype = "QNo" @@ -175,6 +181,10 @@ def _remove_fruit(resource: Resource, result: Result) -> None: if name == fruitname: resource.data.remove([number, name]) break + if len(resource.data) == 0: + resource.state = ResourceState.UNFULFILLED + else: + resource.state = ResourceState.PARTIALLY_FULFILLED def _add_fruit(resource: Resource, result: Result) -> None: @@ -184,6 +194,22 @@ def _add_fruit(resource: Resource, result: Result) -> None: resource.data.append((number, name)) resource.state = ResourceState.PARTIALLY_FULFILLED +def _parse_no(resource: Resource, result: Result) -> None: + print("No callback") + if resource.name == "Fruits": + if resource.state == ResourceState.PARTIALLY_FULFILLED: + print("State is PARTIALLY_FULFILLED") + resource.state = ResourceState.FULFILLED + print("State after setting to FULFILLED") + elif resource.state == ResourceState.FULFILLED: + print("State is FULFILLED") + resource.state = ResourceState.PARTIALLY_FULFILLED + print("State after setting to PARTIALLY_FULFILLED") + +def _parse_yes(resource: Resource, result: Result) -> None: + if resource.name == "Fruits": + if resource.state == ResourceState.FULFILLED: + resource.state = ResourceState.CONFIRMED def sentence(state: QueryStateDict, result: Result) -> None: """Called when sentence processing is complete""" From cd3172dd0c075c9d3a2d056690ab5f725e72ad4c Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Wed, 22 Jun 2022 16:59:36 +0000 Subject: [PATCH 061/371] added initial query param functionality --- queries/js/IoT_Embla/Sonos/connect.js | 5 +++++ queries/js/IoT_Embla/Sonos/sonos.html | 5 +++++ routes/api.py | 21 +++++++++++++++++++-- 3 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 queries/js/IoT_Embla/Sonos/connect.js diff --git a/queries/js/IoT_Embla/Sonos/connect.js b/queries/js/IoT_Embla/Sonos/connect.js new file mode 100644 index 00000000..a8e78a57 --- /dev/null +++ b/queries/js/IoT_Embla/Sonos/connect.js @@ -0,0 +1,5 @@ +function openYouTubeInNewTab() { + var url = "https://www.youtube.com/watch?v=dQw4w9WgXcQ"; + var win = window.open(url, '_blank'); + win.focus(); +} \ No newline at end of file diff --git a/queries/js/IoT_Embla/Sonos/sonos.html b/queries/js/IoT_Embla/Sonos/sonos.html index e95d02b5..f9cb3625 100644 --- a/queries/js/IoT_Embla/Sonos/sonos.html +++ b/queries/js/IoT_Embla/Sonos/sonos.html @@ -12,6 +12,11 @@

Testing

+ +
+ +
+

diff --git a/routes/api.py b/routes/api.py index b6d9be4c..803f6ecb 100755 --- a/routes/api.py +++ b/routes/api.py @@ -687,8 +687,8 @@ def register_query_data_api(version: int = 1) -> Response: _WAV_MIMETYPES = frozenset(("audio/wav", "audio/x-wav")) -@routes.route("/upload_speech_audio.api", methods=["POST"]) -@routes.route("/upload_speech_audio.api/v", methods=["POST"]) +@routes.route("/upload_speech_audio.api", methods=["GET"]) +@routes.route("/upload_speech_audio.api/v", methods=["GET"]) def upload_speech_audio(version: int = 1) -> Response: """Receives uploaded speech audio for a query.""" @@ -716,3 +716,20 @@ def upload_speech_audio(version: int = 1) -> Response: # return better_jsonify(valid=False, reason="Error reading file") # return better_jsonify(valid=True, msg="Audio data received") + + +@routes.route("/connect_sonos.api", methods=["GET"]) +@routes.route("/connect_sonos.api/v", methods=["GET", "POST"]) +def sonos_code(version: int = 1) -> Response: + print("ahkklklfsklhkfl") + args = request.args + client_id = args.get("state") + code = args.get("code") + if client_id and code: + success = QueryObject.store_query_data( + client_id, "sonos_code", code + ) + if success: + return better_jsonify(valid=True, msg="Registered sonos code") + + return better_jsonify(valid=False, errmsg="Error registering sonos code.") \ No newline at end of file From 87d41d8160148cc26483696e3f8dec0a50e25033 Mon Sep 17 00:00:00 2001 From: Logi E Date: Thu, 23 Jun 2022 09:31:23 +0000 Subject: [PATCH 062/371] moved resource classes to dialogue.py --- queries/dialogue.py | 122 +++++++++++++++++++++++++++++ queries/fruit_seller/fruitstate.py | 4 +- queries/fruit_seller/resource.py | 122 ----------------------------- queries/fruitseller.py | 2 +- query.py | 2 +- 5 files changed, 126 insertions(+), 126 deletions(-) create mode 100644 queries/dialogue.py diff --git a/queries/dialogue.py b/queries/dialogue.py new file mode 100644 index 00000000..2eeffd67 --- /dev/null +++ b/queries/dialogue.py @@ -0,0 +1,122 @@ +from typing import Any, Dict, Union, List, Optional +from typing_extensions import TypedDict + +from enum import IntEnum, auto +from datetime import datetime +from dataclasses import dataclass + +from reynir import NounPhrase +from queries import natlang_seq, sing_or_plur + +BaseResourceTypes = Union[str, int, float, bool, datetime, None] +ListResourceType = List[BaseResourceTypes] + + +def _list_items(items: Any) -> str: + item_list: List[str] = [] + for num, name in items: + # TODO: get general plural form + plural_name: str = NounPhrase(name).dative or name + item_list.append(sing_or_plur(num, name, plural_name)) + return natlang_seq(item_list) + + +class DialogueStructureType(TypedDict): + """ + A dialogue structure is a list of resources used in a dialogue. + """ + + dialogue_name: str + variables: Optional[List[Any]] + resources: List[Dict[Any, Any]] + + +class ResourceState(IntEnum): + UNFULFILLED = auto() + PARTIALLY_FULFILLED = auto() + FULFILLED = auto() + CONFIRMED = auto() + # SKIPPED = auto() + + +@dataclass +class Resource: + name: str = "" + required: bool = True + data: Any = None + state: ResourceState = ResourceState.UNFULFILLED + prompt: str = "" + type: str = "" + repeatable: bool = False + repeat_prompt: Optional[str] = None + confirm_prompt: Optional[str] = None + cancel_prompt: Optional[str] = None + _repeat_count: int = 0 + + def next_action(self) -> Any: + raise NotImplementedError() + + def generate_answer(self) -> str: + raise NotImplementedError() + + def update(self, new_data: Optional["Resource"]) -> None: + if new_data: + self.__dict__.update(new_data.__dict__) + + +class ListResource(Resource): + data: ListResourceType = [] + available_options: Optional[ListResourceType] = None + + def list_available_options(self) -> str: + raise NotImplementedError() + + def generate_answer(self) -> str: + print("State: ", self.state) + ans: str = "" + if self.state is ResourceState.UNFULFILLED: + if self._repeat_count == 0 or not self.repeatable: + ans = self.prompt + if self.state is ResourceState.PARTIALLY_FULFILLED: + if self.repeat_prompt: + ans = ( + f"{self.repeat_prompt.format(list_items = _list_items(self.data))}" + ) + if self.state is ResourceState.FULFILLED: + if self.confirm_prompt: + ans = ( + f"{self.confirm_prompt.format(list_items = _list_items(self.data))}" + ) + if self.state is ResourceState.CONFIRMED: + ans = "Pöntunin er staðfest." + return ans + + +# TODO: +# ExactlyOneResource (choose one resource from options) +# SetResource (a set of resources)? +# ... + + +class YesNoResource(Resource): + data: Optional[bool] = None + + +class DatetimeResource(Resource): + data: Optional[datetime] = None + + +class NumberResource(Resource): + data: Optional[int] = None + + +""" Three classes implemented for each resource + class DataState(): + pass + + class PartiallyFulfilledState(): + pass + + class FulfillState(DataState): + pass +""" diff --git a/queries/fruit_seller/fruitstate.py b/queries/fruit_seller/fruitstate.py index 532177c1..e316951a 100644 --- a/queries/fruit_seller/fruitstate.py +++ b/queries/fruit_seller/fruitstate.py @@ -4,12 +4,12 @@ import base64 from tree import Result -from query import DialogueStructureType -from queries.fruit_seller.resource import ( +from queries.dialogue import ( DatetimeResource, ListResource, Resource, ResourceState, + DialogueStructureType, ) from reynir import NounPhrase from queries import natlang_seq, sing_or_plur, load_dialogue_structure diff --git a/queries/fruit_seller/resource.py b/queries/fruit_seller/resource.py index 2eeffd67..e69de29b 100644 --- a/queries/fruit_seller/resource.py +++ b/queries/fruit_seller/resource.py @@ -1,122 +0,0 @@ -from typing import Any, Dict, Union, List, Optional -from typing_extensions import TypedDict - -from enum import IntEnum, auto -from datetime import datetime -from dataclasses import dataclass - -from reynir import NounPhrase -from queries import natlang_seq, sing_or_plur - -BaseResourceTypes = Union[str, int, float, bool, datetime, None] -ListResourceType = List[BaseResourceTypes] - - -def _list_items(items: Any) -> str: - item_list: List[str] = [] - for num, name in items: - # TODO: get general plural form - plural_name: str = NounPhrase(name).dative or name - item_list.append(sing_or_plur(num, name, plural_name)) - return natlang_seq(item_list) - - -class DialogueStructureType(TypedDict): - """ - A dialogue structure is a list of resources used in a dialogue. - """ - - dialogue_name: str - variables: Optional[List[Any]] - resources: List[Dict[Any, Any]] - - -class ResourceState(IntEnum): - UNFULFILLED = auto() - PARTIALLY_FULFILLED = auto() - FULFILLED = auto() - CONFIRMED = auto() - # SKIPPED = auto() - - -@dataclass -class Resource: - name: str = "" - required: bool = True - data: Any = None - state: ResourceState = ResourceState.UNFULFILLED - prompt: str = "" - type: str = "" - repeatable: bool = False - repeat_prompt: Optional[str] = None - confirm_prompt: Optional[str] = None - cancel_prompt: Optional[str] = None - _repeat_count: int = 0 - - def next_action(self) -> Any: - raise NotImplementedError() - - def generate_answer(self) -> str: - raise NotImplementedError() - - def update(self, new_data: Optional["Resource"]) -> None: - if new_data: - self.__dict__.update(new_data.__dict__) - - -class ListResource(Resource): - data: ListResourceType = [] - available_options: Optional[ListResourceType] = None - - def list_available_options(self) -> str: - raise NotImplementedError() - - def generate_answer(self) -> str: - print("State: ", self.state) - ans: str = "" - if self.state is ResourceState.UNFULFILLED: - if self._repeat_count == 0 or not self.repeatable: - ans = self.prompt - if self.state is ResourceState.PARTIALLY_FULFILLED: - if self.repeat_prompt: - ans = ( - f"{self.repeat_prompt.format(list_items = _list_items(self.data))}" - ) - if self.state is ResourceState.FULFILLED: - if self.confirm_prompt: - ans = ( - f"{self.confirm_prompt.format(list_items = _list_items(self.data))}" - ) - if self.state is ResourceState.CONFIRMED: - ans = "Pöntunin er staðfest." - return ans - - -# TODO: -# ExactlyOneResource (choose one resource from options) -# SetResource (a set of resources)? -# ... - - -class YesNoResource(Resource): - data: Optional[bool] = None - - -class DatetimeResource(Resource): - data: Optional[datetime] = None - - -class NumberResource(Resource): - data: Optional[int] = None - - -""" Three classes implemented for each resource - class DataState(): - pass - - class PartiallyFulfilledState(): - pass - - class FulfillState(DataState): - pass -""" diff --git a/queries/fruitseller.py b/queries/fruitseller.py index 653ce0a2..92d51884 100644 --- a/queries/fruitseller.py +++ b/queries/fruitseller.py @@ -4,7 +4,7 @@ from tree import Result, Node from queries import gen_answer, parse_num from queries.fruit_seller.fruitstate import DialogueStateManager -from queries.fruit_seller.resource import Resource, ResourceState +from queries.dialogue import Resource, ResourceState # Indicate that this module wants to handle parse trees for queries, # as opposed to simple literal text strings diff --git a/query.py b/query.py index dff8d9a6..19b9b0e2 100755 --- a/query.py +++ b/query.py @@ -74,7 +74,7 @@ from islenska.bindb import BinFilterFunc from tree import Tree, TreeStateDict, Node -from queries.fruit_seller.resource import DialogueStructureType +from queries.dialogue import DialogueStructureType # from nertokenizer import recognize_entities from images import get_image_url From 1f510a1fe2e56aaf4004de504c098bb6226c6e5b Mon Sep 17 00:00:00 2001 From: Logi E Date: Thu, 23 Jun 2022 09:38:28 +0000 Subject: [PATCH 063/371] refactoring, removed unnecessary code and fixed imports --- queries/dialogue.py | 42 ++++- queries/fruit_seller/fruitstate.py | 244 ----------------------------- queries/fruit_seller/resource.py | 0 queries/fruit_seller/resources.py | 40 +++++ queries/fruitseller.py | 3 +- 5 files changed, 77 insertions(+), 252 deletions(-) delete mode 100644 queries/fruit_seller/fruitstate.py delete mode 100644 queries/fruit_seller/resource.py create mode 100644 queries/fruit_seller/resources.py diff --git a/queries/dialogue.py b/queries/dialogue.py index 2eeffd67..5f075cf2 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -6,13 +6,14 @@ from dataclasses import dataclass from reynir import NounPhrase -from queries import natlang_seq, sing_or_plur +from tree import Result +from queries import natlang_seq, sing_or_plur, load_dialogue_structure BaseResourceTypes = Union[str, int, float, bool, datetime, None] ListResourceType = List[BaseResourceTypes] -def _list_items(items: Any) -> str: +def list_items(items: Any) -> str: item_list: List[str] = [] for num, name in items: # TODO: get general plural form @@ -39,6 +40,37 @@ class ResourceState(IntEnum): # SKIPPED = auto() +class DialogueStateManager: + def __init__( + self, yaml_file: str, saved_state: Optional[DialogueStructureType] = None + ): + obj = load_dialogue_structure(yaml_file) + print(obj) + self.resources: List[Resource] = [] + for i, resource in enumerate(obj["resources"]): + newResource: Resource + if resource.get("type") == "ListResource": + newResource = ListResource(**resource) + else: + newResource = DatetimeResource(**resource) + if saved_state and i < len(saved_state["resources"]): + newResource.__dict__.update(saved_state["resources"][i]) + newResource.state = ResourceState(saved_state["resources"][i]["state"]) + self.resources.append(newResource) + + self.resourceState: Optional[Resource] = None + self.ans: Optional[str] = None + + def generate_answer(self, result: Result) -> str: + for resource in self.resources: + if resource.required and resource.state is not ResourceState.CONFIRMED: + if "callbacks" in result: + for cb in result.callbacks: + cb(resource, result) + return resource.generate_answer() + return "Upp kom villa, reyndu aftur." + + @dataclass class Resource: name: str = "" @@ -79,13 +111,11 @@ def generate_answer(self) -> str: ans = self.prompt if self.state is ResourceState.PARTIALLY_FULFILLED: if self.repeat_prompt: - ans = ( - f"{self.repeat_prompt.format(list_items = _list_items(self.data))}" - ) + ans = f"{self.repeat_prompt.format(list_items = list_items(self.data))}" if self.state is ResourceState.FULFILLED: if self.confirm_prompt: ans = ( - f"{self.confirm_prompt.format(list_items = _list_items(self.data))}" + f"{self.confirm_prompt.format(list_items = list_items(self.data))}" ) if self.state is ResourceState.CONFIRMED: ans = "Pöntunin er staðfest." diff --git a/queries/fruit_seller/fruitstate.py b/queries/fruit_seller/fruitstate.py deleted file mode 100644 index e316951a..00000000 --- a/queries/fruit_seller/fruitstate.py +++ /dev/null @@ -1,244 +0,0 @@ -from typing import Any, Optional, List - -import pickle -import base64 - -from tree import Result -from queries.dialogue import ( - DatetimeResource, - ListResource, - Resource, - ResourceState, - DialogueStructureType, -) -from reynir import NounPhrase -from queries import natlang_seq, sing_or_plur, load_dialogue_structure - - -def _list_items(items: Any) -> str: - item_list: List[str] = [] - for name in items.keys(): - number: int = items[name] - # TODO: get general plural form - plural_name: str = NounPhrase(name).dative or name - item_list.append(sing_or_plur(number, name, plural_name)) - return natlang_seq(item_list) - - -class DialogueStateManager: - def __init__( - self, yaml_file: str, saved_state: Optional[DialogueStructureType] = None - ): - obj = load_dialogue_structure(yaml_file) - print(obj) - self.resources: List[Resource] = [] - for i, resource in enumerate(obj["resources"]): - newResource: Resource - if resource.get("type") == "ListResource": - newResource = ListResource(**resource) - else: - newResource = DatetimeResource(**resource) - if saved_state and i < len(saved_state["resources"]): - newResource.__dict__.update(saved_state["resources"][i]) - newResource.state = ResourceState(saved_state["resources"][i]["state"]) - self.resources.append(newResource) - - self.resourceState: Optional[Resource] = None - self.ans: Optional[str] = None - - def generate_answer(self, result: Result) -> str: - for resource in self.resources: - if resource.required and resource.state is not ResourceState.CONFIRMED: - if "callbacks" in result: - for cb in result.callbacks: - cb(resource, result) - return resource.generate_answer() - return "Upp kom villa, reyndu aftur." - - def updateState(self, type: str) -> None: - for resource in self.resources: - if resource.required and resource.state is not ResourceState.FULFILLED: - if resource.data is None: - if self.resourceState is not resource: - self.resourceState = resource - resource.state = resource.DataState(resource.data) - break - elif resource.state is not ResourceState.PARTIALLY_FULFILLED: - resource.state = resource.PartiallyFulfilledState(resource.data) - break - elif resource.state is ResourceState.PARTIALLY_FULFILLED: - resource.state = resource.FulfilledState(resource.data) - break - self.generate_answer(type) - print("Current state: ", self.resourceState.state) - - def stateMethod(self, methodName: str, result: Result): - try: - if self.resourceState is not None: - method = getattr(self.resourceState.state, methodName) - if method is not None: - (self.resourceState.data, self.resourceState.state) = method(result) - self.updateState(result.qtype) - else: - self.ans = "Kom upp villa, reyndu aftur." - except Exception as e: - print("Error: ", e) - result.qtype = "FruitMethodNotFound" - - @classmethod - def serialize(cls, instance: "DialogueStateManager") -> str: - return base64.b64encode(pickle.dumps(instance)).decode("utf-8") - - @classmethod - def deserialize(cls, serialized: str) -> "DialogueStateManager": - return pickle.loads(base64.b64decode(serialized.encode("utf-8"))) - - -class FruitState(ListResource): - def generate_answer(self, type: str) -> str: - ans: str = "" - if type == "QFruitStartQuery": - ans = "Hvaða ávexti má bjóða þér?" - elif type == "ListFruit": - if len(self.data) != 0: - ans = "Komið! Pöntunin samanstendur af " - ans += _list_items(self.data) - ans += ". Var það eitthvað fleira?" - else: - ans = "Komið! Karfan er núna tóm. Hvaða ávexti má bjóða þér?" - - elif type == "FruitOrderNotFinished": - ans = "Hverju viltu að bæta við pöntunina?" - elif type == "FruitsFulfilled": - ans = "Frábært! Á ég að staðfesta pöntunina?" - elif type == "FruitMethodNotFound": - self.ans = "Ég get ekki tekið við þessari beiðni strax." - elif type == "OrderComplete": - ans = "Frábært, pöntunin er staðfest!" - elif type == "OrderWrong": - ans = "Leitt að heyra, viltu hætta við pöntunina eða breyta henni?" - elif type == "CancelOrder": - ans = "Móttekið. Hætti við pöntunina." - elif type == "FruitOptions": - ans = "Hægt er að panta appelsínur, banana, epli og perur." - elif type == "FruitRemoved": - ans = "Karfan hefur verið uppfærð. Var það eitthvað fleira?" - elif type == "NoFruitMatched": - ans = "Enginn ávöxtur í körfunni passaði við beiðnina á undan." - elif type == "NoFruitToRemove": - ans = "Engir ávextir eru í körfunni til að fjarlægja." - return ans - - class DataState: - def __init__(self, data: Any): - self.data = data - - # Add fruits to array and switch to OrderReceived state - def QAddFruitQuery(self, result: Result): - if self.data is None: - self.data = result.queryfruits - else: - for fruitname in result.queryfruits.keys(): - self.data[fruitname] = result.queryfruits[fruitname] - result.fruits = self.data - result.qtype = "ListFruit" - return (self.data, ResourceState.PARTIALLY_FULFILLED) - - # Remove fruits from array - def QRemoveFruitQuery(self, result: Result): - result.qtype = "" - if self.data is None: - result.qtype = "NoFruitToRemove" - else: - for fruitname in result.queryfruits.keys(): - removedValue = self.data.pop(fruitname, "NoFruitMatched") - if removedValue == "NoFruitMatched": - result.qtype = "NoFruitMatched" - if result.qtype != "NoFruitMatched": - result.qtype = "ListFruit" - result.fruits = self.data - return ( - self.data, - ResourceState.PARTIALLY_FULFILLED - if len(self.data) != 0 - else ResourceState.UNFULFILLED, - ) - - # Change the fruits array - def QChangeFruitQuery(self, result: Result): - pass - - # Inform what fruits are available - def QFruitOptionsQuery(self, result: Result): - result.qtype = "FruitOptions" - return ( - self.data, - ResourceState.UNFULFILLED - if self.data is None - else ResourceState.PARTIALLY_FULFILLED, - ) - - # User wants to stop conversation - def QCancelOrder(self, result: Result): - result.qtype = "CancelOrder" - return (self.data, ResourceState.UNFULFILLED) - - class PartiallyFulfilledState(DataState): - def __init__(self, data: Any): - super().__init__(data) - - # User is happy with the order, switch to confirm state - def QNo(self, result: Result): - result.qtype = "FruitsFulfilled" - return (self.data, ResourceState.FULFILLED) - - # User wants to add more to the order, ask what - def QYes(self, result: Result): - result.qtype = "FruitOrderNotFinished" - return (self.data, ResourceState.PARTIALLY_FULFILLED) - - class FulfilledState(DataState): - def __init__(self, data: Any): - super().__init__(data) - - # The order is correct, say the order is confirmed - def QYes(self, result: Result): - result.qtype = "OrderComplete" - return (self.data, ResourceState.CONFIRMED) - - # Order was wrong, ask the user to start again - def QNo(self, result: Result): - result.qtype = "OrderWrong" - return (self.data, ResourceState.FULFILLED) - - -class OrderReceivedState(FruitState): - def __init__(self, required: bool = True): - super().__init__(required) - - # User is happy with the order, switch to confirm state - def QNo(self, result: Result): - result.qtype = "FruitsFulfilled" - self.fulfilled = True - return ConfirmOrderState(self.fruits, self.date) - - # User wants to add more to the order, ask what - def QYes(self, result: Result): - result.qtype = "FruitOrderNotFinished" - return FruitState(self.fruits, self.date) - - -class ConfirmOrderState(FruitState): - def __init__(self, fruits, date): - self.fruits = fruits - self.date = date - - # The order is correct, say the order is confirmed - def QYes(self, result: Result): - result.qtype = "OrderComplete" - - # Order was wrong, ask the user to start again - def QNo(self, result: Result): - result.qtype = "OrderWrong" - self.fruits.fulfilled = False - return FruitState(self.fruits, self.date) diff --git a/queries/fruit_seller/resource.py b/queries/fruit_seller/resource.py deleted file mode 100644 index e69de29b..00000000 diff --git a/queries/fruit_seller/resources.py b/queries/fruit_seller/resources.py new file mode 100644 index 00000000..2c1d195e --- /dev/null +++ b/queries/fruit_seller/resources.py @@ -0,0 +1,40 @@ +from queries.dialogue import ( + ListResource, +) +from queries.dialogue import list_items + + +class FruitState(ListResource): + def generate_answer(self, type: str) -> str: + ans: str = "" + if type == "QFruitStartQuery": + ans = "Hvaða ávexti má bjóða þér?" + elif type == "ListFruit": + if len(self.data) != 0: + ans = "Komið! Pöntunin samanstendur af " + ans += list_items(self.data) + ans += ". Var það eitthvað fleira?" + else: + ans = "Komið! Karfan er núna tóm. Hvaða ávexti má bjóða þér?" + + elif type == "FruitOrderNotFinished": + ans = "Hverju viltu að bæta við pöntunina?" + elif type == "FruitsFulfilled": + ans = "Frábært! Á ég að staðfesta pöntunina?" + elif type == "FruitMethodNotFound": + self.ans = "Ég get ekki tekið við þessari beiðni strax." + elif type == "OrderComplete": + ans = "Frábært, pöntunin er staðfest!" + elif type == "OrderWrong": + ans = "Leitt að heyra, viltu hætta við pöntunina eða breyta henni?" + elif type == "CancelOrder": + ans = "Móttekið. Hætti við pöntunina." + elif type == "FruitOptions": + ans = "Hægt er að panta appelsínur, banana, epli og perur." + elif type == "FruitRemoved": + ans = "Karfan hefur verið uppfærð. Var það eitthvað fleira?" + elif type == "NoFruitMatched": + ans = "Enginn ávöxtur í körfunni passaði við beiðnina á undan." + elif type == "NoFruitToRemove": + ans = "Engir ávextir eru í körfunni til að fjarlægja." + return ans diff --git a/queries/fruitseller.py b/queries/fruitseller.py index 92d51884..2c262416 100644 --- a/queries/fruitseller.py +++ b/queries/fruitseller.py @@ -3,8 +3,7 @@ from query import Query, QueryStateDict from tree import Result, Node from queries import gen_answer, parse_num -from queries.fruit_seller.fruitstate import DialogueStateManager -from queries.dialogue import Resource, ResourceState +from queries.dialogue import Resource, ResourceState, DialogueStateManager # Indicate that this module wants to handle parse trees for queries, # as opposed to simple literal text strings From f97a2c1b337f853888c977133566d2aed6b61baf Mon Sep 17 00:00:00 2001 From: Logi E Date: Thu, 23 Jun 2022 10:22:31 +0000 Subject: [PATCH 064/371] added delivery date/time to grammar for fruitseller --- queries/{ => disabled}/time.py | 0 queries/fruitseller.py | 79 +++++++++++++++++++++++++++++++++- 2 files changed, 77 insertions(+), 2 deletions(-) rename queries/{ => disabled}/time.py (100%) diff --git a/queries/time.py b/queries/disabled/time.py similarity index 100% rename from queries/time.py rename to queries/disabled/time.py diff --git a/queries/fruitseller.py b/queries/fruitseller.py index 2c262416..29b7601c 100644 --- a/queries/fruitseller.py +++ b/queries/fruitseller.py @@ -1,7 +1,10 @@ +import json import logging +import datetime +from typing import cast from query import Query, QueryStateDict -from tree import Result, Node +from tree import Result, Node, TerminalNode from queries import gen_answer, parse_num from queries.dialogue import Resource, ResourceState, DialogueStateManager @@ -19,7 +22,7 @@ QFruitSeller '?'? QFruitSeller → - QFruitStartQuery | QFruitQuery + QFruitStartQuery | QFruitQuery | QFruitDateQuery QFruitStartQuery → "ávöxtur" @@ -104,6 +107,21 @@ | "ég" "vil" "hætta" "við" "pöntunina" | "ég" "vill" "hætta" "við" "pöntunina" +QFruitDateQuery → + QFruitDateTime + | QFruitDate + | QFruitTime + +QFruitDateTime → + tímapunkturafs + +QFruitDate → + dagsafs + | dagsföst + +QFruitTime → + "klukkan"? tími + """ _START_CONVERSATION_QTYPE = "QFruitStartQuery" @@ -173,6 +191,60 @@ def QFruit(node: Node, params: QueryStateDict, result: Result): result.fruit = fruit +def QFruitDateTime(node: Node, params: QueryStateDict, result: Result) -> None: + datetimenode = node.first_child(lambda n: True) + assert isinstance(datetimenode, TerminalNode) + print(datetimenode.aux) + now = datetime.datetime.now() + y, m, d, h, min, _ = (i if i != 0 else None for i in json.loads(datetimenode.aux)) + if y is None: + y = now.year + if m is None: + m = now.month + if d is None: + d = now.day + if h is None: + h = 12 + if min is None: + min = 0 + result["delivery_time"] = datetime.time(h, min) + result["delivery_date"] = datetime.date(y, m, d) + print("DATE:", result["delivery_date"]) + print("TIME:", result["delivery_time"]) + + +def QFruitDate(node: Node, params: QueryStateDict, result: Result) -> None: + datenode = node.first_child(lambda n: True) + assert isinstance(datenode, TerminalNode) + cdate = datenode.contained_date + if cdate: + y, m, d = cdate + now = datetime.datetime.utcnow() + + # This is a date that contains at least month & mday + if d and m: + if not y: + y = now.year + # Bump year if month/day in the past + if m < now.month or (m == now.month and d < now.day): + y += 1 + result["delivery_date"] = datetime.date(day=d, month=m, year=y) + print("DELIVERY DATE:", result["delivery_date"]) + return + raise ValueError("No date in {0}".format(str(datenode))) + + +def QFruitTime(node: Node, params: QueryStateDict, result: Result): + # Extract time from time terminal nodes + tnode = cast(TerminalNode, node.first_child(lambda n: n.has_t_base("tími"))) + if tnode: + aux_str = tnode.aux.strip("[]") + hour, minute, _ = (int(i) for i in aux_str.split(", ")) + + result["delivery_time"] = datetime.time(hour, minute) + print("TIME IS: ", result["delivery_time"]) + + def _remove_fruit(resource: Resource, result: Result) -> None: if resource.data is not None: for _, fruitname in result.queryfruits: @@ -193,6 +265,7 @@ def _add_fruit(resource: Resource, result: Result) -> None: resource.data.append((number, name)) resource.state = ResourceState.PARTIALLY_FULFILLED + def _parse_no(resource: Resource, result: Result) -> None: print("No callback") if resource.name == "Fruits": @@ -205,11 +278,13 @@ def _parse_no(resource: Resource, result: Result) -> None: resource.state = ResourceState.PARTIALLY_FULFILLED print("State after setting to PARTIALLY_FULFILLED") + def _parse_yes(resource: Resource, result: Result) -> None: if resource.name == "Fruits": if resource.state == ResourceState.FULFILLED: resource.state = ResourceState.CONFIRMED + def sentence(state: QueryStateDict, result: Result) -> None: """Called when sentence processing is complete""" q: Query = state["query"] From 173c6ebd4613a8ef05c2951ff237b027ccd377eb Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Thu, 23 Jun 2022 10:30:51 +0000 Subject: [PATCH 065/371] IoTConnect Sonos CODE functionality --- queries/iot_connect.py | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/queries/iot_connect.py b/queries/iot_connect.py index f5511b45..899fb988 100644 --- a/queries/iot_connect.py +++ b/queries/iot_connect.py @@ -63,7 +63,7 @@ def help_text(lemma: str) -> str: "Tengdu ljósin" "Tengdu hátalarann" ) - ) + ) ) @@ -88,14 +88,17 @@ def help_text(lemma: str) -> str: QIoTConnect → QIoTConnectLights | QIoTConnectHub + | QIoTConnectSpeaker - QIoTConnectLights → "tengdu" "ljósin" QIoTConnectHub → "tengdu" "miðstöðina" +QIoTConnectSpeaker → + "tengdu" "hátalarann" + """ def QIoTConnectLights(node: Node, params: QueryStateDict, result: Result) -> None: @@ -107,6 +110,10 @@ def QIoTConnectHub(node: Node, params: QueryStateDict, result: Result) -> None: result.qtype = "connect_hub" result.action = "connect_hub" +def QIoTConnectSpeaker(node: Node, params: QueryStateDict, result: Result) -> None: + print("Connect Speaker") + result.qtype = "connect_speaker" + result.action = "connect_speaker" def sentence(state: QueryStateDict, result: Result) -> None: """Called when sentence processing is complete""" @@ -126,7 +133,6 @@ def sentence(state: QueryStateDict, result: Result) -> None: if result.qtype == "connect_lights": host = str(flask.request.host) print("host: ", host) - smartdevice_type = "smartlight" client_id = str(q.client_id) print("client_id:", client_id) js = read_jsfile("IoT_Embla/Philips_Hue/hub.js") @@ -140,7 +146,6 @@ def sentence(state: QueryStateDict, result: Result) -> None: elif result.qtype == "connect_hub": host = str(flask.request.host) print("host: ", host) - smartdevice_type = "smarthub" client_id = str(q.client_id) print("client_id:", client_id) js = read_jsfile("IoT_Embla/Smart_Things/st_connecthub.js") @@ -151,6 +156,17 @@ def sentence(state: QueryStateDict, result: Result) -> None: q.set_answer(response, answer, voice_answer) q.set_command(js) return + elif result.qtype == "connect_speaker": + # host = str(flask.request.host) + print("Connect speaker sentence") + client_id = str(q.client_id) + answer = "Skráðu þig inn hjá Sonos" + voice_answer = answer + response = dict(answer=answer) + q.set_answer(response, answer, voice_answer) + q.set_url(f"https://api.sonos.com/login/v3/oauth?client_id=74436dd6-476a-4470-ada3-3a9da4642dec&response_type=code&state={client_id}&scope=playback-control-all&redirect_uri=http://192.168.1.69:5000/connect_sonos.api") + + # smartdevice_type = "smartlights" From dffc4cfece1967a1d756e2c06723e214e078e53b Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Thu, 23 Jun 2022 10:38:37 +0000 Subject: [PATCH 066/371] return statement in iotconnect --- queries/iot_connect.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/queries/iot_connect.py b/queries/iot_connect.py index 899fb988..aa52413a 100644 --- a/queries/iot_connect.py +++ b/queries/iot_connect.py @@ -165,7 +165,7 @@ def sentence(state: QueryStateDict, result: Result) -> None: response = dict(answer=answer) q.set_answer(response, answer, voice_answer) q.set_url(f"https://api.sonos.com/login/v3/oauth?client_id=74436dd6-476a-4470-ada3-3a9da4642dec&response_type=code&state={client_id}&scope=playback-control-all&redirect_uri=http://192.168.1.69:5000/connect_sonos.api") - + return From 3e9c58f6ad2a05f9219751499677856ae6e2de0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Thu, 23 Jun 2022 10:49:20 +0000 Subject: [PATCH 067/371] Added state handling for delivery date --- queries/dialogue.py | 31 ++++++++++++++++++++++----- queries/fruit_seller/fruitseller.yaml | 4 +++- queries/fruit_seller/resources.py | 11 +++++----- 3 files changed, 34 insertions(+), 12 deletions(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index 5f075cf2..ac30f840 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -1,15 +1,16 @@ +from ssl import ALERT_DESCRIPTION_PROTOCOL_VERSION from typing import Any, Dict, Union, List, Optional from typing_extensions import TypedDict from enum import IntEnum, auto -from datetime import datetime +import datetime from dataclasses import dataclass from reynir import NounPhrase from tree import Result from queries import natlang_seq, sing_or_plur, load_dialogue_structure -BaseResourceTypes = Union[str, int, float, bool, datetime, None] +BaseResourceTypes = Union[str, int, float, bool, datetime.datetime, None] ListResourceType = List[BaseResourceTypes] @@ -67,6 +68,8 @@ def generate_answer(self, result: Result) -> str: if "callbacks" in result: for cb in result.callbacks: cb(resource, result) + if resource.state is ResourceState.CONFIRMED and resource != self.resources[-1]: + continue return resource.generate_answer() return "Upp kom villa, reyndu aftur." @@ -117,8 +120,6 @@ def generate_answer(self) -> str: ans = ( f"{self.confirm_prompt.format(list_items = list_items(self.data))}" ) - if self.state is ResourceState.CONFIRMED: - ans = "Pöntunin er staðfest." return ans @@ -133,7 +134,27 @@ class YesNoResource(Resource): class DatetimeResource(Resource): - data: Optional[datetime] = None + data: Optional[List[Union[Optional[datetime.date], Optional[datetime.time]]]] = None + date_fulfilled_prompt: Optional[str] = None + time_fulfilled_prompt: Optional[str] = None + + def generate_answer(self) -> str: + ans = "" + if self.state is ResourceState.UNFULFILLED: + if self._repeat_count == 0 or not self.repeatable: + ans = self.prompt + if self.state is ResourceState.PARTIALLY_FULFILLED: + if self.data: + if self.data[0] and self.date_fulfilled_prompt: + ans = self.date_fulfilled_prompt.format(date = self.data[0].strftime("%Y/%m/%d")) + if self.data[1] and self.time_fulfilled_prompt: + ans = self.time_fulfilled_prompt.format(time = self.data[1].strftime("%H:%M")) + if self.state is ResourceState.FULFILLED: + if self.data and self.confirm_prompt and self.data[0] and self.data[1]: + ans = self.confirm_prompt.format(date_time = datetime.datetime.combine(self.data[0], self.data[1]).strftime("%Y/%m/%d %H:%M")) + if self.state is ResourceState.CONFIRMED: + ans = "Pöntunin er staðfest." + return ans class NumberResource(Resource): diff --git a/queries/fruit_seller/fruitseller.yaml b/queries/fruit_seller/fruitseller.yaml index 4738e8a8..9d5019e9 100644 --- a/queries/fruit_seller/fruitseller.yaml +++ b/queries/fruit_seller/fruitseller.yaml @@ -16,4 +16,6 @@ resources: type: "DatetimeResource" # verification_function: "check_date" prompt: "Hvenær viltu fá ávextina?" - confirm_prompt: "Afhending pöntunar er {dt}. Viltu staðfesta afhendingartímann?" + time_fulfilled_prompt: "Afhendingin verður klukkan {time}. Hvaða dagsetningu viltu fá ávextina?" + date_fulfilled_prompt: "Afhendingin verður {date}. Klukkan hvað viltu fá ávextina?" + confirm_prompt: "Afhending pöntunar er {date_time}. Viltu staðfesta afhendingartímann?" diff --git a/queries/fruit_seller/resources.py b/queries/fruit_seller/resources.py index 2c1d195e..df3ceb8b 100644 --- a/queries/fruit_seller/resources.py +++ b/queries/fruit_seller/resources.py @@ -1,12 +1,11 @@ from queries.dialogue import ( ListResource, ) -from queries.dialogue import list_items - -class FruitState(ListResource): - def generate_answer(self, type: str) -> str: - ans: str = "" +class FruitResource(ListResource): + def generate_answer(self) -> str: + ans = super().generate_answer() + """ans: str = "" if type == "QFruitStartQuery": ans = "Hvaða ávexti má bjóða þér?" elif type == "ListFruit": @@ -36,5 +35,5 @@ def generate_answer(self, type: str) -> str: elif type == "NoFruitMatched": ans = "Enginn ávöxtur í körfunni passaði við beiðnina á undan." elif type == "NoFruitToRemove": - ans = "Engir ávextir eru í körfunni til að fjarlægja." + ans = "Engir ávextir eru í körfunni til að fjarlægja." """ return ans From 613cc541455b183887730eb33b0945989786428a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Thu, 23 Jun 2022 11:06:41 +0000 Subject: [PATCH 068/371] Changed confirm_prompt for fruits --- queries/fruit_seller/fruitseller.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/queries/fruit_seller/fruitseller.yaml b/queries/fruit_seller/fruitseller.yaml index 9d5019e9..c364fa1a 100644 --- a/queries/fruit_seller/fruitseller.yaml +++ b/queries/fruit_seller/fruitseller.yaml @@ -10,7 +10,7 @@ resources: type: "ListResource" repeatable: true # verification_function: "check_fruits" - confirm_prompt: "Pöntunin samanstendur af {list_items}. Viltu staðfesta pöntunina?" + confirm_prompt: "Viltu staðfesta ávextina {list_items}?" repeat_prompt: "Pöntunin samanstendur af {list_items}. Verður það eitthvað fleira?" - name: "Date" type: "DatetimeResource" From 17ba1cc4a01ec3a0ec7844e48dc558264c2502cd Mon Sep 17 00:00:00 2001 From: Logi E Date: Thu, 23 Jun 2022 11:30:08 +0000 Subject: [PATCH 069/371] added callbacks for date/time/datetime --- queries/dialogue.py | 42 ++++++++++++++----- queries/fruit_seller/fruitseller.yaml | 2 +- queries/fruitseller.py | 58 +++++++++++++++++++++++++-- 3 files changed, 88 insertions(+), 14 deletions(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index ac30f840..c7090266 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -1,5 +1,4 @@ -from ssl import ALERT_DESCRIPTION_PROTOCOL_VERSION -from typing import Any, Dict, Union, List, Optional +from typing import Any, Dict, Union, List, Optional, cast from typing_extensions import TypedDict from enum import IntEnum, auto @@ -53,6 +52,7 @@ def __init__( if resource.get("type") == "ListResource": newResource = ListResource(**resource) else: + print(resource) newResource = DatetimeResource(**resource) if saved_state and i < len(saved_state["resources"]): newResource.__dict__.update(saved_state["resources"][i]) @@ -68,7 +68,10 @@ def generate_answer(self, result: Result) -> str: if "callbacks" in result: for cb in result.callbacks: cb(resource, result) - if resource.state is ResourceState.CONFIRMED and resource != self.resources[-1]: + if ( + resource.state is ResourceState.CONFIRMED + and resource != self.resources[-1] + ): continue return resource.generate_answer() return "Upp kom villa, reyndu aftur." @@ -99,8 +102,9 @@ def update(self, new_data: Optional["Resource"]) -> None: self.__dict__.update(new_data.__dict__) +@dataclass class ListResource(Resource): - data: ListResourceType = [] + data: Optional[ListResourceType] = None available_options: Optional[ListResourceType] = None def list_available_options(self) -> str: @@ -129,10 +133,12 @@ def generate_answer(self) -> str: # ... +@dataclass class YesNoResource(Resource): data: Optional[bool] = None +@dataclass class DatetimeResource(Resource): data: Optional[List[Union[Optional[datetime.date], Optional[datetime.time]]]] = None date_fulfilled_prompt: Optional[str] = None @@ -145,18 +151,34 @@ def generate_answer(self) -> str: ans = self.prompt if self.state is ResourceState.PARTIALLY_FULFILLED: if self.data: - if self.data[0] and self.date_fulfilled_prompt: - ans = self.date_fulfilled_prompt.format(date = self.data[0].strftime("%Y/%m/%d")) - if self.data[1] and self.time_fulfilled_prompt: - ans = self.time_fulfilled_prompt.format(time = self.data[1].strftime("%H:%M")) + if len(self.data) > 0 and self.data[0] and self.date_fulfilled_prompt: + ans = self.date_fulfilled_prompt.format( + date=self.data[0].strftime("%Y/%m/%d") + ) + if len(self.data) > 1 and self.data[1] and self.time_fulfilled_prompt: + ans = self.time_fulfilled_prompt.format( + time=self.data[1].strftime("%H:%M") + ) if self.state is ResourceState.FULFILLED: - if self.data and self.confirm_prompt and self.data[0] and self.data[1]: - ans = self.confirm_prompt.format(date_time = datetime.datetime.combine(self.data[0], self.data[1]).strftime("%Y/%m/%d %H:%M")) + if ( + self.data + and self.confirm_prompt + and len(self.data) == 2 + and self.data[0] + and self.data[1] + ): + ans = self.confirm_prompt.format( + date_time=datetime.datetime.combine( + cast(datetime.date, self.data[0]), + cast(datetime.time, self.data[1]), + ).strftime("%Y/%m/%d %H:%M") + ) if self.state is ResourceState.CONFIRMED: ans = "Pöntunin er staðfest." return ans +@dataclass class NumberResource(Resource): data: Optional[int] = None diff --git a/queries/fruit_seller/fruitseller.yaml b/queries/fruit_seller/fruitseller.yaml index c364fa1a..2e678f45 100644 --- a/queries/fruit_seller/fruitseller.yaml +++ b/queries/fruit_seller/fruitseller.yaml @@ -18,4 +18,4 @@ resources: prompt: "Hvenær viltu fá ávextina?" time_fulfilled_prompt: "Afhendingin verður klukkan {time}. Hvaða dagsetningu viltu fá ávextina?" date_fulfilled_prompt: "Afhendingin verður {date}. Klukkan hvað viltu fá ávextina?" - confirm_prompt: "Afhending pöntunar er {date_time}. Viltu staðfesta afhendingartímann?" + confirm_prompt: "Afhending pöntunar er {date_time}. Viltu staðfesta afhendinguna?" diff --git a/queries/fruitseller.py b/queries/fruitseller.py index 29b7601c..6da7bb9a 100644 --- a/queries/fruitseller.py +++ b/queries/fruitseller.py @@ -6,7 +6,12 @@ from query import Query, QueryStateDict from tree import Result, Node, TerminalNode from queries import gen_answer, parse_num -from queries.dialogue import Resource, ResourceState, DialogueStateManager +from queries.dialogue import ( + DatetimeResource, + Resource, + ResourceState, + DialogueStateManager, +) # Indicate that this module wants to handle parse trees for queries, # as opposed to simple literal text strings @@ -192,6 +197,7 @@ def QFruit(node: Node, params: QueryStateDict, result: Result): def QFruitDateTime(node: Node, params: QueryStateDict, result: Result) -> None: + result.qtype = "bull" datetimenode = node.first_child(lambda n: True) assert isinstance(datetimenode, TerminalNode) print(datetimenode.aux) @@ -209,11 +215,22 @@ def QFruitDateTime(node: Node, params: QueryStateDict, result: Result) -> None: min = 0 result["delivery_time"] = datetime.time(h, min) result["delivery_date"] = datetime.date(y, m, d) - print("DATE:", result["delivery_date"]) - print("TIME:", result["delivery_time"]) + + def _dt_callback(resource: DatetimeResource, result: Result) -> None: + if resource.data is None: + resource.data = [] + print("DATETIME SHOULD BE FULFILLED NOW") + resource.data.append(result["delivery_date"]) + resource.data.append(result["delivery_time"]) + resource.state = ResourceState.FULFILLED + + if "callbacks" not in result: + result["callbacks"] = [] + result.callbacks.append(_dt_callback) def QFruitDate(node: Node, params: QueryStateDict, result: Result) -> None: + result.qtype = "bull" datenode = node.first_child(lambda n: True) assert isinstance(datenode, TerminalNode) cdate = datenode.contained_date @@ -230,11 +247,27 @@ def QFruitDate(node: Node, params: QueryStateDict, result: Result) -> None: y += 1 result["delivery_date"] = datetime.date(day=d, month=m, year=y) print("DELIVERY DATE:", result["delivery_date"]) + + def _dt_callback(resource: DatetimeResource, result: Result) -> None: + if resource.data is None: + resource.data = [] + + resource.data.append(result["delivery_date"]) + if isinstance(resource.data[0], datetime.time): + resource.data.reverse() + resource.state = ResourceState.FULFILLED + else: + resource.state = ResourceState.PARTIALLY_FULFILLED + + if "callbacks" not in result: + result["callbacks"] = [] + result.callbacks.append(_dt_callback) return raise ValueError("No date in {0}".format(str(datenode))) def QFruitTime(node: Node, params: QueryStateDict, result: Result): + result.qtype = "bull" # Extract time from time terminal nodes tnode = cast(TerminalNode, node.first_child(lambda n: n.has_t_base("tími"))) if tnode: @@ -244,6 +277,19 @@ def QFruitTime(node: Node, params: QueryStateDict, result: Result): result["delivery_time"] = datetime.time(hour, minute) print("TIME IS: ", result["delivery_time"]) + def _dt_callback(resource: DatetimeResource, result: Result) -> None: + if resource.data is None: + resource.data = [] + resource.data.append(result["delivery_time"]) + if isinstance(resource.data[0], datetime.date): + resource.state = ResourceState.FULFILLED + else: + resource.state = ResourceState.PARTIALLY_FULFILLED + + if "callbacks" not in result: + result["callbacks"] = [] + result.callbacks.append(_dt_callback) + def _remove_fruit(resource: Resource, result: Result) -> None: if resource.data is not None: @@ -283,6 +329,12 @@ def _parse_yes(resource: Resource, result: Result) -> None: if resource.name == "Fruits": if resource.state == ResourceState.FULFILLED: resource.state = ResourceState.CONFIRMED + if resource.name == "Date": + print("CONFIRMING DATETIME...", resource) + if resource.state == ResourceState.FULFILLED: + print("CONFIRMING DATETIME... 2") + resource.state = ResourceState.CONFIRMED + print("DATETIME CONFIRMED") def sentence(state: QueryStateDict, result: Result) -> None: From f57c80a5e3c4dc3683d87704d5b6a1017cd59e49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Thu, 23 Jun 2022 13:32:01 +0000 Subject: [PATCH 070/371] Added DialogueJsonEncoder to be able to serialize unserializable datatypes --- queries/dialogue.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/queries/dialogue.py b/queries/dialogue.py index c7090266..3837a3f1 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -1,3 +1,4 @@ +import json from typing import Any, Dict, Union, List, Optional, cast from typing_extensions import TypedDict @@ -76,6 +77,20 @@ def generate_answer(self, result: Result) -> str: return resource.generate_answer() return "Upp kom villa, reyndu aftur." +class DialogueJSONEncoder(json.JSONEncoder): + # TODO: check resource state + def default(self, o: Any) -> Any: + if isinstance(o, DatetimeResource): + serializable_dict = o.__dict__.copy() + if o.data and isinstance(o.data[0], datetime.date): + #encode date as string + serializable_dict["data"][0] = o.data[0].strftime("%Y-%m-%d") + if o.data[1] is not None and isinstance(o.data[1], datetime.time): + serializable_dict["data"][1] = o.data[1].strftime("%H:%M") + elif o.data and isinstance(o.data[0], datetime.time): + serializable_dict["data"][0] = o.data[0].strftime("%H:%M") + return serializable_dict + return json.JSONEncoder.default(self, o) @dataclass class Resource: From 901ba1c4073f40caa77fd044a039b2e140462ac0 Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Thu, 23 Jun 2022 13:47:07 +0000 Subject: [PATCH 071/371] iot connect updated --- queries/iot_connect.py | 61 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/queries/iot_connect.py b/queries/iot_connect.py index aa52413a..f7203f10 100644 --- a/queries/iot_connect.py +++ b/queries/iot_connect.py @@ -31,10 +31,13 @@ import random import json import flask +import requests +import time from query import Query, QueryStateDict, AnswerTuple from queries import gen_answer, read_jsfile, read_grammar_file from tree import Result, Node +from routes import better_jsonify class SmartLights(TypedDict): @@ -89,6 +92,7 @@ def help_text(lemma: str) -> str: QIoTConnectLights | QIoTConnectHub | QIoTConnectSpeaker + | QIoTCreateSpeakerToken QIoTConnectLights → "tengdu" "ljósin" @@ -99,6 +103,9 @@ def help_text(lemma: str) -> str: QIoTConnectSpeaker → "tengdu" "hátalarann" +QIoTCreateSpeakerToken → + "skapaðu" "tóka" + """ def QIoTConnectLights(node: Node, params: QueryStateDict, result: Result) -> None: @@ -115,6 +122,12 @@ def QIoTConnectSpeaker(node: Node, params: QueryStateDict, result: Result) -> No result.qtype = "connect_speaker" result.action = "connect_speaker" +def QIoTCreateSpeakerToken(node: Node, params: QueryStateDict, result: Result) -> None: + print("Create Token") + result.qtype = "create_speaker_token" + result.action = "create_speaker_token" + + def sentence(state: QueryStateDict, result: Result) -> None: """Called when sentence processing is complete""" q: Query = state["query"] @@ -166,6 +179,54 @@ def sentence(state: QueryStateDict, result: Result) -> None: q.set_answer(response, answer, voice_answer) q.set_url(f"https://api.sonos.com/login/v3/oauth?client_id=74436dd6-476a-4470-ada3-3a9da4642dec&response_type=code&state={client_id}&scope=playback-control-all&redirect_uri=http://192.168.1.69:5000/connect_sonos.api") return + elif result.qtype == "create_speaker_token": + answer = "Ég bjó til tóka frá Sonos" + voice_answer = answer + response = dict(answer=answer) + code = str(q.client_data("sonos_code")) + print(code) + q.set_answer(response, answer, voice_answer) + q.set_url(f"https://google.com/") + + + + + + url = f"https://api.sonos.com/login/v3/oauth/access?grant_type=authorization_code&code={code}&redirect_uri=http://192.168.1.69:5000/connect_sonos.api" + + payload={} + headers = { + 'Authorization': 'Basic NzQ0MzZkZDYtNDc2YS00NDcwLWFkYTMtM2E5ZGE0NjQyZGVjOjZlNmRhOGUzLWQ1MWEtNGFhMS1hZDg5LTFmZTY4OGVkZTI3ZA==', + 'Cookie': 'JSESSIONID=2DEFC02D2184D987F4CCAD5E45196948; AWSELB=69BFEFC914A689BF6DC8E4652748D7B501ED60290D5EA56F2E543ABD7CF357A5F65186AEBC76E6A16196350947ED84835621A185D1BF63900D4B3E7BC7FE3CF19CCF26B78C; AWSELBCORS=69BFEFC914A689BF6DC8E4652748D7B501ED60290D5EA56F2E543ABD7CF357A5F65186AEBC76E6A16196350947ED84835621A185D1BF63900D4B3E7BC7FE3CF19CCF26B78C' + } + + response = requests.request("POST", url, headers=headers, data=payload) + if response.status_code != 200: + print("Error:", response.status_code) + print(response.text) + return + response_json = response.json() + sonos_access_token = response_json["access_token"] + sonos_refresh_token = response_json["refresh_token"] + + # print("access token :", response.access_token) + # print("refresh token :", response.refresh_token) + current_time = time.time() + print("current time :", current_time) + # q.set_client_data("sonos_access_token", response.access_token) + # q.set_client_data("sonos_token_time", current_time) + + + + # if client_id and code: + # success = QueryObject.store_query_data( + # client_id, "sonos_code", code + # ) + # if success: + # return better_jsonify(valid=True, msg="Registered Sonos token") + + # return better_jsonify(valid=False, errmsg="Error registering Sonos token.") + From 75b4463fd6319c33b77db24f32dfaf7f424187bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Thu, 23 Jun 2022 14:18:19 +0000 Subject: [PATCH 072/371] Dialogue json encoder and decoder added for datetime and resources --- queries/dialogue.py | 67 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 58 insertions(+), 9 deletions(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index 3837a3f1..8759c3b4 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -80,18 +80,67 @@ def generate_answer(self, result: Result) -> str: class DialogueJSONEncoder(json.JSONEncoder): # TODO: check resource state def default(self, o: Any) -> Any: + if isinstance(o, ListResource): + d = o.__dict__.copy() + d["__type__"] = "ListResource" + return d + if isinstance(o, YesNoResource): + d = o.__dict__.copy() + d["__type__"] = "YesNoResource" + return d if isinstance(o, DatetimeResource): - serializable_dict = o.__dict__.copy() - if o.data and isinstance(o.data[0], datetime.date): - #encode date as string - serializable_dict["data"][0] = o.data[0].strftime("%Y-%m-%d") - if o.data[1] is not None and isinstance(o.data[1], datetime.time): - serializable_dict["data"][1] = o.data[1].strftime("%H:%M") - elif o.data and isinstance(o.data[0], datetime.time): - serializable_dict["data"][0] = o.data[0].strftime("%H:%M") - return serializable_dict + d = o.__dict__.copy() + d["__type__"] = "DatetimeResource" + return d + if isinstance(o, NumberResource): + d = o.__dict__.copy() + d["__type__"] = "NumberResource" + return d + if isinstance(o, datetime.date): + return { + '__type__' : 'date', + 'year' : o.year, + 'month' : o.month, + 'day' : o.day, + } + if isinstance(o, datetime.time): + return { + '__type__' : 'time', + 'hour' : o.hour, + 'minute' : o.minute, + 'second' : o.second, + 'microsecond' : o.microsecond, + } + if isinstance(o, Resource): + d = o.__dict__.copy() + d["__type__"] = "Resource" + return d return json.JSONEncoder.default(self, o) +class DialogueJSONDecoder(json.JSONDecoder): + + def __init__(self, *args: Any, **kwargs: Any): + json.JSONDecoder.__init__(self, object_hook=self.dialogue_decoding, *args, **kwargs) + + def dialogue_decoding(self, d: Dict[Any, Any]) -> Any: + if '__type__' not in d: + return d + t = d.pop('__type__') + if t == "ListResource": + return ListResource(**d) + if t == "YesNoResource": + return YesNoResource(**d) + if t == "DatetimeResource": + return DatetimeResource(**d) + if t == "NumberResource": + return NumberResource(**d) + if t == "date": + return datetime.date(**d) + if t == "time": + return datetime.time(**d) + if t == "Resource": + return Resource(**d) + @dataclass class Resource: name: str = "" From c9e08cc438504362f51c68b47f375b2a9e273f65 Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Thu, 23 Jun 2022 14:47:37 +0000 Subject: [PATCH 073/371] iot create token --- queries/iot_connect.py | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/queries/iot_connect.py b/queries/iot_connect.py index f7203f10..58204425 100644 --- a/queries/iot_connect.py +++ b/queries/iot_connect.py @@ -38,7 +38,7 @@ from queries import gen_answer, read_jsfile, read_grammar_file from tree import Result, Node from routes import better_jsonify - +from util import read_api_key class SmartLights(TypedDict): selected_light: str @@ -170,16 +170,21 @@ def sentence(state: QueryStateDict, result: Result) -> None: q.set_command(js) return elif result.qtype == "connect_speaker": - # host = str(flask.request.host) + sonos_key = read_api_key("SonosKey") + host = str(flask.request.host) print("Connect speaker sentence") client_id = str(q.client_id) answer = "Skráðu þig inn hjá Sonos" voice_answer = answer response = dict(answer=answer) q.set_answer(response, answer, voice_answer) - q.set_url(f"https://api.sonos.com/login/v3/oauth?client_id=74436dd6-476a-4470-ada3-3a9da4642dec&response_type=code&state={client_id}&scope=playback-control-all&redirect_uri=http://192.168.1.69:5000/connect_sonos.api") + print("sonos_key :", sonos_key) + print("host :", host) + q.set_url(f"https://api.sonos.com/login/v3/oauth?client_id={sonos_key}&response_type=code&state={client_id}&scope=playback-control-all&redirect_uri=http://{host}/connect_sonos.api") return elif result.qtype == "create_speaker_token": + sonos_encoded_credentials = read_api_key("SonosEncodedCredentials") + print("credientials :", sonos_encoded_credentials) answer = "Ég bjó til tóka frá Sonos" voice_answer = answer response = dict(answer=answer) @@ -187,16 +192,12 @@ def sentence(state: QueryStateDict, result: Result) -> None: print(code) q.set_answer(response, answer, voice_answer) q.set_url(f"https://google.com/") - - - - - - url = f"https://api.sonos.com/login/v3/oauth/access?grant_type=authorization_code&code={code}&redirect_uri=http://192.168.1.69:5000/connect_sonos.api" + host = str(flask.request.host) + url = f"https://api.sonos.com/login/v3/oauth/access?grant_type=authorization_code&code={code}&redirect_uri=http://{host}/connect_sonos.api" payload={} headers = { - 'Authorization': 'Basic NzQ0MzZkZDYtNDc2YS00NDcwLWFkYTMtM2E5ZGE0NjQyZGVjOjZlNmRhOGUzLWQ1MWEtNGFhMS1hZDg5LTFmZTY4OGVkZTI3ZA==', + 'Authorization': f'Basic {sonos_encoded_credentials}', 'Cookie': 'JSESSIONID=2DEFC02D2184D987F4CCAD5E45196948; AWSELB=69BFEFC914A689BF6DC8E4652748D7B501ED60290D5EA56F2E543ABD7CF357A5F65186AEBC76E6A16196350947ED84835621A185D1BF63900D4B3E7BC7FE3CF19CCF26B78C; AWSELBCORS=69BFEFC914A689BF6DC8E4652748D7B501ED60290D5EA56F2E543ABD7CF357A5F65186AEBC76E6A16196350947ED84835621A185D1BF63900D4B3E7BC7FE3CF19CCF26B78C' } @@ -208,13 +209,16 @@ def sentence(state: QueryStateDict, result: Result) -> None: response_json = response.json() sonos_access_token = response_json["access_token"] sonos_refresh_token = response_json["refresh_token"] + print(response.text) # print("access token :", response.access_token) # print("refresh token :", response.refresh_token) current_time = time.time() print("current time :", current_time) - # q.set_client_data("sonos_access_token", response.access_token) - # q.set_client_data("sonos_token_time", current_time) + + q.set_client_data("sonos_access_token", sonos_access_token) + q.set_client_data("sonos_refresh_token", sonos_refresh_token) + q.set_client_data("sonos_token_time", current_time) From 37278d70de21508b8e8a7d557fb72b92e5ad43e1 Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Thu, 23 Jun 2022 14:50:29 +0000 Subject: [PATCH 074/371] created iot_speakers --- queries/iot_speakers.py | 43 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 queries/iot_speakers.py diff --git a/queries/iot_speakers.py b/queries/iot_speakers.py new file mode 100644 index 00000000..3838e698 --- /dev/null +++ b/queries/iot_speakers.py @@ -0,0 +1,43 @@ +def getHouseholds(): + """ + Returns the list of households of the user + """ + url = "https://api.ws.sonos.com/control/api/v1/households" + + payload = {} + headers = {"Authorization": f"Bearer {token}"} + + response = requests.request("GET", url, headers=headers, data=payload) + + print(response.text) + + +def getGroups(): + """ + Returns the list of groups of the user + """ + url = "https://api.ws.sonos.com/control/api/v1/households/Sonos_2qmmZYj1IfZpziI3yTZT2AdYkP.LzZPKytb_zgm6t3fVIv7/groups" + + payload = {} + headers = {"Authorization": f"Bearer {token}"} + + response = requests.request("GET", url, headers=headers, data=payload) + + print(response.text) + + +def createToken(code): + """ + Creates a token given a code + """ + url = f"https://api.sonos.com/login/v3/oauth/access?grant_type=authorization_code&code={code}&redirect_uri=http://localhost:5000/connect_sonos.api" + + payload = {} + headers = { + "Authorization": f"Basic {sonos_encoded_credentials}", + "Cookie": "JSESSIONID=F710019AF0A3B7126A8702577C883B5F; AWSELB=69BFEFC914A689BF6DC8E4652748D7B501ED60290D5EA56F2E543ABD7CF357A5F65186AEBCFB059E28075D83A700FD504C030A53CC28683B515BE3DCA3CC587AFAF606E171; AWSELBCORS=69BFEFC914A689BF6DC8E4652748D7B501ED60290D5EA56F2E543ABD7CF357A5F65186AEBCFB059E28075D83A700FD504C030A53CC28683B515BE3DCA3CC587AFAF606E171", + } + + response = requests.request("POST", url, headers=headers, data=payload) + + print(response.text) From 30efd967e04fb1ec5d8d5f2f3a8bed1dbe015a92 Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Thu, 23 Jun 2022 14:52:12 +0000 Subject: [PATCH 075/371] style fix --- queries/iot_connect.py | 31 ++++++++++++------------------- routes/api.py | 6 ++---- 2 files changed, 14 insertions(+), 23 deletions(-) diff --git a/queries/iot_connect.py b/queries/iot_connect.py index 58204425..e8615f61 100644 --- a/queries/iot_connect.py +++ b/queries/iot_connect.py @@ -40,6 +40,7 @@ from routes import better_jsonify from util import read_api_key + class SmartLights(TypedDict): selected_light: str philips_hue: Dict[str, str] @@ -60,17 +61,10 @@ def help_text(lemma: str) -> str: """Help text to return when query.py is unable to parse a query but one of the above lemmas is found in it""" return "Ég skil þig ef þú segir til dæmis: {0}.".format( - random.choice( - ( - "Tengu miðstöðina", - "Tengdu ljósin" - "Tengdu hátalarann" - ) - ) + random.choice(("Tengu miðstöðina", "Tengdu ljósin" "Tengdu hátalarann")) ) - # This module wants to handle parse trees for queries HANDLE_TREE = True @@ -108,20 +102,24 @@ def help_text(lemma: str) -> str: """ + def QIoTConnectLights(node: Node, params: QueryStateDict, result: Result) -> None: result.qtype = "connect_lights" result.action = "connect_lights" + def QIoTConnectHub(node: Node, params: QueryStateDict, result: Result) -> None: print("Connect Hub") result.qtype = "connect_hub" result.action = "connect_hub" + def QIoTConnectSpeaker(node: Node, params: QueryStateDict, result: Result) -> None: print("Connect Speaker") result.qtype = "connect_speaker" result.action = "connect_speaker" + def QIoTCreateSpeakerToken(node: Node, params: QueryStateDict, result: Result) -> None: print("Create Token") result.qtype = "create_speaker_token" @@ -180,7 +178,9 @@ def sentence(state: QueryStateDict, result: Result) -> None: q.set_answer(response, answer, voice_answer) print("sonos_key :", sonos_key) print("host :", host) - q.set_url(f"https://api.sonos.com/login/v3/oauth?client_id={sonos_key}&response_type=code&state={client_id}&scope=playback-control-all&redirect_uri=http://{host}/connect_sonos.api") + q.set_url( + f"https://api.sonos.com/login/v3/oauth?client_id={sonos_key}&response_type=code&state={client_id}&scope=playback-control-all&redirect_uri=http://{host}/connect_sonos.api" + ) return elif result.qtype == "create_speaker_token": sonos_encoded_credentials = read_api_key("SonosEncodedCredentials") @@ -195,10 +195,10 @@ def sentence(state: QueryStateDict, result: Result) -> None: host = str(flask.request.host) url = f"https://api.sonos.com/login/v3/oauth/access?grant_type=authorization_code&code={code}&redirect_uri=http://{host}/connect_sonos.api" - payload={} + payload = {} headers = { - 'Authorization': f'Basic {sonos_encoded_credentials}', - 'Cookie': 'JSESSIONID=2DEFC02D2184D987F4CCAD5E45196948; AWSELB=69BFEFC914A689BF6DC8E4652748D7B501ED60290D5EA56F2E543ABD7CF357A5F65186AEBC76E6A16196350947ED84835621A185D1BF63900D4B3E7BC7FE3CF19CCF26B78C; AWSELBCORS=69BFEFC914A689BF6DC8E4652748D7B501ED60290D5EA56F2E543ABD7CF357A5F65186AEBC76E6A16196350947ED84835621A185D1BF63900D4B3E7BC7FE3CF19CCF26B78C' + "Authorization": f"Basic {sonos_encoded_credentials}", + "Cookie": "JSESSIONID=2DEFC02D2184D987F4CCAD5E45196948; AWSELB=69BFEFC914A689BF6DC8E4652748D7B501ED60290D5EA56F2E543ABD7CF357A5F65186AEBC76E6A16196350947ED84835621A185D1BF63900D4B3E7BC7FE3CF19CCF26B78C; AWSELBCORS=69BFEFC914A689BF6DC8E4652748D7B501ED60290D5EA56F2E543ABD7CF357A5F65186AEBC76E6A16196350947ED84835621A185D1BF63900D4B3E7BC7FE3CF19CCF26B78C", } response = requests.request("POST", url, headers=headers, data=payload) @@ -219,9 +219,7 @@ def sentence(state: QueryStateDict, result: Result) -> None: q.set_client_data("sonos_access_token", sonos_access_token) q.set_client_data("sonos_refresh_token", sonos_refresh_token) q.set_client_data("sonos_token_time", current_time) - - # if client_id and code: # success = QueryObject.store_query_data( # client_id, "sonos_code", code @@ -231,9 +229,6 @@ def sentence(state: QueryStateDict, result: Result) -> None: # return better_jsonify(valid=False, errmsg="Error registering Sonos token.") - - - # smartdevice_type = "smartlights" # client_id = str(q.client_id) # print("client_id:", client_id) @@ -264,6 +259,4 @@ def sentence(state: QueryStateDict, result: Result) -> None: # print("selected light :", selected_light) # print("hue credentials :", hue_credentials) - - # f"var BRIDGE_IP = '192.168.1.68';var USERNAME = 'p3obluiXT13IbHMpp4X63ZvZnpNRdbqqMt723gy2';" diff --git a/routes/api.py b/routes/api.py index 803f6ecb..c45f418f 100755 --- a/routes/api.py +++ b/routes/api.py @@ -726,10 +726,8 @@ def sonos_code(version: int = 1) -> Response: client_id = args.get("state") code = args.get("code") if client_id and code: - success = QueryObject.store_query_data( - client_id, "sonos_code", code - ) + success = QueryObject.store_query_data(client_id, "sonos_code", code) if success: return better_jsonify(valid=True, msg="Registered sonos code") - return better_jsonify(valid=False, errmsg="Error registering sonos code.") \ No newline at end of file + return better_jsonify(valid=False, errmsg="Error registering sonos code.") From b8b7775b04b64540e339b054fa5ac8680b24a340 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Thu, 23 Jun 2022 15:03:50 +0000 Subject: [PATCH 076/371] toml file for fruitseller resources --- queries/fruit_seller/fruitseller.toml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 queries/fruit_seller/fruitseller.toml diff --git a/queries/fruit_seller/fruitseller.toml b/queries/fruit_seller/fruitseller.toml new file mode 100644 index 00000000..89be4954 --- /dev/null +++ b/queries/fruit_seller/fruitseller.toml @@ -0,0 +1,22 @@ +dialogue_name = "fruitseller" + +[[resources]] +name = "Fruits" +prompt = "Hvaða ávexti má bjóða þér?" +# resource_nonterminal: "QFruitList" +type = "ListResource" +repeatable = true +# verification_function: "check_fruits" +confirm_prompt = "Viltu staðfesta ávextina {list_items}?" +repeat_prompt = "Pöntunin samanstendur af {list_items}. Verður það eitthvað fleira?" + +[[resources]] +name = "Date" +type = "DatetimeResource" +# verification_function: "check_date" +prompt = "Hvenær viltu fá ávextina?" +time_fulfilled_prompt = "Afhendingin verður klukkan {time}. Hvaða dagsetningu viltu fá ávextina?" +date_fulfilled_prompt = "Afhendingin verður {date}. Klukkan hvað viltu fá ávextina?" +confirm_prompt = "Afhending pöntunar er {date_time}. Viltu staðfesta afhendinguna?" + + From d56a72555e063bb2b25cd14cca039f1571dc96ef Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Thu, 23 Jun 2022 15:04:29 +0000 Subject: [PATCH 077/371] Added basic functions to iot_speakers.py --- queries/iot_speakers.py | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/queries/iot_speakers.py b/queries/iot_speakers.py index 3838e698..492e0ba1 100644 --- a/queries/iot_speakers.py +++ b/queries/iot_speakers.py @@ -1,4 +1,4 @@ -def getHouseholds(): +def getHouseholds(token): """ Returns the list of households of the user """ @@ -9,24 +9,24 @@ def getHouseholds(): response = requests.request("GET", url, headers=headers, data=payload) - print(response.text) + return response -def getGroups(): +def getGroups(houshold_id, token): """ Returns the list of groups of the user """ - url = "https://api.ws.sonos.com/control/api/v1/households/Sonos_2qmmZYj1IfZpziI3yTZT2AdYkP.LzZPKytb_zgm6t3fVIv7/groups" + url = "https://api.ws.sonos.com/control/api/v1/households/{household_id}/groups" payload = {} headers = {"Authorization": f"Bearer {token}"} response = requests.request("GET", url, headers=headers, data=payload) - print(response.text) + return response -def createToken(code): +def createToken(code, sonos_encoded_credentials): """ Creates a token given a code """ @@ -40,4 +40,20 @@ def createToken(code): response = requests.request("POST", url, headers=headers, data=payload) - print(response.text) + return response + + +def togglePlayPause(group_id, token): + """ + Toggles the play/pause of a group + """ + url = ( + f"https://api.ws.sonos.com/control/api/v1/groups/{group_id}/playback/playPause" + ) + + payload = {} + headers = {"Content-Type": "application/json", "Authorization": f"Bearer {token}"} + + response = requests.request("POST", url, headers=headers, data=payload) + + return response From 20f15db4cf028bcc40f462d0ecb9a680985964bc Mon Sep 17 00:00:00 2001 From: Logi E Date: Thu, 23 Jun 2022 15:05:11 +0000 Subject: [PATCH 078/371] refactoring so dialogue functionality is kept in dialogue.py (doesn't work at the moment) --- queries/__init__.py | 10 -- queries/dialogue.py | 149 +++++++++++++----- queries/fruitseller.py | 23 ++- .../{fruit_seller => fruitseller}/__init__.py | 0 .../fruitseller.toml | 0 .../fruitseller.yaml | 0 .../resources.py | 0 query.py | 19 +-- requirements.txt | 1 + 9 files changed, 122 insertions(+), 80 deletions(-) rename queries/{fruit_seller => fruitseller}/__init__.py (100%) rename queries/{fruit_seller => fruitseller}/fruitseller.toml (100%) rename queries/{fruit_seller => fruitseller}/fruitseller.yaml (100%) rename queries/{fruit_seller => fruitseller}/resources.py (100%) diff --git a/queries/__init__.py b/queries/__init__.py index e278bc98..80bf94ec 100755 --- a/queries/__init__.py +++ b/queries/__init__.py @@ -729,13 +729,3 @@ def join_grammar_files(folder: str) -> str: # # - QNo: "repeat_prompt" # # - QCancel: *Cancel - - -def load_dialogue_structure(filename: str) -> Any: - """Loads dialogue structure from YAML file.""" - basepath, _ = os.path.split(os.path.realpath(__file__)) - fpath = os.path.join(basepath, filename) - obj = None - with open(fpath, mode="r") as file: - obj = yaml.safe_load(file) - return obj diff --git a/queries/dialogue.py b/queries/dialogue.py index 8759c3b4..5c13ea4e 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -2,38 +2,41 @@ from typing import Any, Dict, Union, List, Optional, cast from typing_extensions import TypedDict -from enum import IntEnum, auto import datetime +import os.path +import yaml + +# try: +# import tomllib +# except ModuleNotFoundError: +# import tomli as tomllib +from enum import IntEnum, auto from dataclasses import dataclass -from reynir import NounPhrase from tree import Result -from queries import natlang_seq, sing_or_plur, load_dialogue_structure - -BaseResourceTypes = Union[str, int, float, bool, datetime.datetime, None] -ListResourceType = List[BaseResourceTypes] +from query import Query, ClientDataDict +from reynir import NounPhrase +from queries import natlang_seq, sing_or_plur +# Global key for storing client data for dialogues +DIALOGUE_KEY = "dialogue" +DIALOGUE_DATA_KEY = "dialogue_data" +EMPTY_DIALOGUE_DATA = "{}" -def list_items(items: Any) -> str: - item_list: List[str] = [] - for num, name in items: - # TODO: get general plural form - plural_name: str = NounPhrase(name).dative or name - item_list.append(sing_or_plur(num, name, plural_name)) - return natlang_seq(item_list) +ResourceType = Union[str, int, float, bool, datetime.datetime, None] +ListResourceType = List[ResourceType] class DialogueStructureType(TypedDict): - """ - A dialogue structure is a list of resources used in a dialogue. - """ + """ """ dialogue_name: str - variables: Optional[List[Any]] resources: List[Dict[Any, Any]] class ResourceState(IntEnum): + """Enum representing the different states a dialogue resource can be in.""" + UNFULFILLED = auto() PARTIALLY_FULFILLED = auto() FULFILLED = auto() @@ -41,11 +44,44 @@ class ResourceState(IntEnum): # SKIPPED = auto() +def load_dialogue_structure(filename: str) -> Any: + """Loads dialogue structure from YAML file.""" + basepath, _ = os.path.split(os.path.realpath(__file__)) + print(basepath) + fpath = os.path.join(basepath, filename) + print(fpath) + obj = None + with open(fpath, mode="r") as file: + obj = yaml.safe_load(file) + return obj + + +def list_items(items: Any) -> str: + item_list: List[str] = [] + for num, name in items: + # TODO: get general plural form + plural_name: str = NounPhrase(name).dative or name + item_list.append(sing_or_plur(num, name, plural_name)) + return natlang_seq(item_list) + + class DialogueStateManager: - def __init__( - self, yaml_file: str, saved_state: Optional[DialogueStructureType] = None - ): - obj = load_dialogue_structure(yaml_file) + def __init__(self, dialogue_name: str, query: Query, result: Result): + self._dialogue_name = dialogue_name + self._q = query + self._result = result + self._saved_state = self.get_dialogue_state() + + def not_in_dialogue(self, start_dialogue_qtype: str) -> bool: + """Check if the client is in or wants to start this dialogue""" + qt = self._result.get("qtype") + return ( + qt != start_dialogue_qtype + and self._saved_state.get("dialogue_name") != self._dialogue_name + ) + + def setup_dialogue(self): + obj = load_dialogue_structure(self._dialogue_name) print(obj) self.resources: List[Resource] = [] for i, resource in enumerate(obj["resources"]): @@ -55,9 +91,11 @@ def __init__( else: print(resource) newResource = DatetimeResource(**resource) - if saved_state and i < len(saved_state["resources"]): - newResource.__dict__.update(saved_state["resources"][i]) - newResource.state = ResourceState(saved_state["resources"][i]["state"]) + if self._saved_state and i < len(self._saved_state["resources"]): + newResource.__dict__.update(self._saved_state["resources"][i]) + newResource.state = ResourceState( + self._saved_state["resources"][i]["state"] + ) self.resources.append(newResource) self.resourceState: Optional[Resource] = None @@ -77,9 +115,41 @@ def generate_answer(self, result: Result) -> str: return resource.generate_answer() return "Upp kom villa, reyndu aftur." + def get_dialogue_state(self) -> DialogueStructureType: + """Load the dialogue state for a client""" + cd = self._q.client_data(DIALOGUE_KEY) + # Return empty DialogueStructureType in case no dialogue state exists + ds: DialogueStructureType = { + "dialogue_name": "", + "resources": [], + } + if cd: + ds_str = cd.get(DIALOGUE_DATA_KEY) + if isinstance(ds_str, str) and ds_str != EMPTY_DIALOGUE_DATA: + # TODO: Add try-except block + ds = json.loads(ds_str, cls=DialogueJSONDecoder) + return ds + + def set_dialogue_state(self, ds: DialogueStructureType) -> None: + """Save the state of a dialogue for a client""" + ds_json: str = json.dumps(ds, cls=DialogueJSONEncoder) + # Wrap data before saving dialogue state into client data + # (due to custom JSON serialization) + cd = {DIALOGUE_DATA_KEY: ds_json} + self._q.set_client_data(DIALOGUE_KEY, cast(ClientDataDict, cd)) + + def end_dialogue(self) -> None: + """End the client's current dialogue""" + # TODO: Remove line from database? + self._q.set_client_data(DIALOGUE_KEY, {DIALOGUE_DATA_KEY: EMPTY_DIALOGUE_DATA}) + + class DialogueJSONEncoder(json.JSONEncoder): - # TODO: check resource state def default(self, o: Any) -> Any: + # Add JSON encoding for any new Resource classes here! + # CUSTOM RESOURCE CLASSES + + # BASE RESOURCE CLASSES if isinstance(o, ListResource): d = o.__dict__.copy() d["__type__"] = "ListResource" @@ -98,18 +168,18 @@ def default(self, o: Any) -> Any: return d if isinstance(o, datetime.date): return { - '__type__' : 'date', - 'year' : o.year, - 'month' : o.month, - 'day' : o.day, + "__type__": "date", + "year": o.year, + "month": o.month, + "day": o.day, } if isinstance(o, datetime.time): return { - '__type__' : 'time', - 'hour' : o.hour, - 'minute' : o.minute, - 'second' : o.second, - 'microsecond' : o.microsecond, + "__type__": "time", + "hour": o.hour, + "minute": o.minute, + "second": o.second, + "microsecond": o.microsecond, } if isinstance(o, Resource): d = o.__dict__.copy() @@ -117,15 +187,17 @@ def default(self, o: Any) -> Any: return d return json.JSONEncoder.default(self, o) -class DialogueJSONDecoder(json.JSONDecoder): +class DialogueJSONDecoder(json.JSONDecoder): def __init__(self, *args: Any, **kwargs: Any): - json.JSONDecoder.__init__(self, object_hook=self.dialogue_decoding, *args, **kwargs) + json.JSONDecoder.__init__( + self, object_hook=self.dialogue_decoding, *args, **kwargs + ) def dialogue_decoding(self, d: Dict[Any, Any]) -> Any: - if '__type__' not in d: + if "__type__" not in d: return d - t = d.pop('__type__') + t = d.pop("__type__") if t == "ListResource": return ListResource(**d) if t == "YesNoResource": @@ -141,6 +213,7 @@ def dialogue_decoding(self, d: Dict[Any, Any]) -> Any: if t == "Resource": return Resource(**d) + @dataclass class Resource: name: str = "" diff --git a/queries/fruitseller.py b/queries/fruitseller.py index 6da7bb9a..c8c9239d 100644 --- a/queries/fruitseller.py +++ b/queries/fruitseller.py @@ -129,12 +129,12 @@ """ -_START_CONVERSATION_QTYPE = "QFruitStartQuery" +_START_DIALOGUE_QTYPE = "QFruitStartQuery" _DIALOGUE_NAME = "fruitseller" def QFruitStartQuery(node: Node, params: QueryStateDict, result: Result): - result.qtype = _START_CONVERSATION_QTYPE + result.qtype = _START_DIALOGUE_QTYPE def QAddFruitQuery(node: Node, params: QueryStateDict, result: Result): @@ -340,24 +340,19 @@ def _parse_yes(resource: Resource, result: Result) -> None: def sentence(state: QueryStateDict, result: Result) -> None: """Called when sentence processing is complete""" q: Query = state["query"] - dialogue_state = q.get_dialogue_state() or {} - qt = result.get("qtype") - - print("Dialogue state: ", dialogue_state) - # checka hvort user se i samtali med q.client_data - if qt != _START_CONVERSATION_QTYPE and not ( - dialogue_state and dialogue_state.get("dialogue_name") == _DIALOGUE_NAME - ): + dsm = DialogueStateManager(_DIALOGUE_NAME, q, result) + + if dsm.not_in_dialogue(_START_DIALOGUE_QTYPE): q.set_error("E_QUERY_NOT_UNDERSTOOD") return print("Fyrir dsm") - dsm = DialogueStateManager("fruit_seller/fruitseller.yaml", q.get_dialogue_state()) + dsm.setup_dialogue() print("Eftir dms") # Successfully matched a query type try: - if result.qtype == _START_CONVERSATION_QTYPE: + if result.qtype == _START_DIALOGUE_QTYPE: q.set_dialogue_state( - {"dialogue_name": "fruitseller", "resources": [], "variables": None} + {"dialogue_name": _DIALOGUE_NAME, "resources": [], "variables": None} ) else: print("Í else") @@ -369,7 +364,7 @@ def sentence(state: QueryStateDict, result: Result) -> None: print("eftir generate") q.set_dialogue_state( { - "dialogue_name": "fruitseller", + "dialogue_name": _DIALOGUE_NAME, "resources": [r.__dict__ for r in dsm.resources], "variables": None, } diff --git a/queries/fruit_seller/__init__.py b/queries/fruitseller/__init__.py similarity index 100% rename from queries/fruit_seller/__init__.py rename to queries/fruitseller/__init__.py diff --git a/queries/fruit_seller/fruitseller.toml b/queries/fruitseller/fruitseller.toml similarity index 100% rename from queries/fruit_seller/fruitseller.toml rename to queries/fruitseller/fruitseller.toml diff --git a/queries/fruit_seller/fruitseller.yaml b/queries/fruitseller/fruitseller.yaml similarity index 100% rename from queries/fruit_seller/fruitseller.yaml rename to queries/fruitseller/fruitseller.yaml diff --git a/queries/fruit_seller/resources.py b/queries/fruitseller/resources.py similarity index 100% rename from queries/fruit_seller/resources.py rename to queries/fruitseller/resources.py diff --git a/query.py b/query.py index 19b9b0e2..7d41bb3c 100755 --- a/query.py +++ b/query.py @@ -74,7 +74,6 @@ from islenska.bindb import BinFilterFunc from tree import Tree, TreeStateDict, Node -from queries.dialogue import DialogueStructureType # from nertokenizer import recognize_entities from images import get_image_url @@ -90,9 +89,7 @@ ContextDict = Dict[str, Any] # Client data -ClientDataDict = Union[ - Dict[str, Union[str, int, float, bool, Dict[str, str]]], DialogueStructureType -] +ClientDataDict = Dict[str, Union[str, int, float, bool, Dict[str, str]]] # Answer tuple (corresponds to parameter list of Query.set_answer()) AnswerTuple = Tuple[ResponseType, str, Optional[str]] @@ -144,7 +141,6 @@ def __call__(self, w: str, *, filter_func: Optional[BinFilterFunc] = None) -> st _IGNORED_PREFIX_RE = r"^({0})\s*".format("|".join(_IGNORED_QUERY_PREFIXES)) # Auto-capitalization corrections _CAPITALIZATION_REPLACEMENTS = (("í Dag", "í dag"),) -DIALOGUE_DATA_KEY = "dialogue" def beautify_query(query: str) -> str: @@ -896,19 +892,6 @@ def set_client_data(self, key: str, data: ClientDataDict) -> None: return Query.store_query_data(self.client_id, key, data) - def get_dialogue_state(self) -> Optional[DialogueStructureType]: - """Load the dialogue state for a client""" - return cast(DialogueStructureType, self.client_data(DIALOGUE_DATA_KEY)) - - def set_dialogue_state(self, ds: DialogueStructureType) -> None: - """Save the state of a dialogue for a client""" - self.set_client_data(DIALOGUE_DATA_KEY, ds) - - def end_dialogue(self) -> None: - """End the client's current dialogue""" - # TODO: Remove line from database? - self.set_client_data(DIALOGUE_DATA_KEY, {}) - @staticmethod def store_query_data(client_id: str, key: str, data: ClientDataDict) -> bool: """Save client query data in the database, under the given key""" diff --git a/requirements.txt b/requirements.txt index da473e9f..40367bb2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -27,4 +27,5 @@ odfpy>=1.4.1 pdfminer.six>=20201018 python-youtube>=0.8.1 PyYAML==6.0 +tomli >= 1.1.0 ; python_version < "3.11" azure-cognitiveservices-speech>=1.19.0 From 7cc70eff175ad947f0c6c4827d5ece8aa1c46f90 Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Thu, 23 Jun 2022 14:47:37 +0000 Subject: [PATCH 079/371] iot create token --- queries/iot_connect.py | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/queries/iot_connect.py b/queries/iot_connect.py index f7203f10..58204425 100644 --- a/queries/iot_connect.py +++ b/queries/iot_connect.py @@ -38,7 +38,7 @@ from queries import gen_answer, read_jsfile, read_grammar_file from tree import Result, Node from routes import better_jsonify - +from util import read_api_key class SmartLights(TypedDict): selected_light: str @@ -170,16 +170,21 @@ def sentence(state: QueryStateDict, result: Result) -> None: q.set_command(js) return elif result.qtype == "connect_speaker": - # host = str(flask.request.host) + sonos_key = read_api_key("SonosKey") + host = str(flask.request.host) print("Connect speaker sentence") client_id = str(q.client_id) answer = "Skráðu þig inn hjá Sonos" voice_answer = answer response = dict(answer=answer) q.set_answer(response, answer, voice_answer) - q.set_url(f"https://api.sonos.com/login/v3/oauth?client_id=74436dd6-476a-4470-ada3-3a9da4642dec&response_type=code&state={client_id}&scope=playback-control-all&redirect_uri=http://192.168.1.69:5000/connect_sonos.api") + print("sonos_key :", sonos_key) + print("host :", host) + q.set_url(f"https://api.sonos.com/login/v3/oauth?client_id={sonos_key}&response_type=code&state={client_id}&scope=playback-control-all&redirect_uri=http://{host}/connect_sonos.api") return elif result.qtype == "create_speaker_token": + sonos_encoded_credentials = read_api_key("SonosEncodedCredentials") + print("credientials :", sonos_encoded_credentials) answer = "Ég bjó til tóka frá Sonos" voice_answer = answer response = dict(answer=answer) @@ -187,16 +192,12 @@ def sentence(state: QueryStateDict, result: Result) -> None: print(code) q.set_answer(response, answer, voice_answer) q.set_url(f"https://google.com/") - - - - - - url = f"https://api.sonos.com/login/v3/oauth/access?grant_type=authorization_code&code={code}&redirect_uri=http://192.168.1.69:5000/connect_sonos.api" + host = str(flask.request.host) + url = f"https://api.sonos.com/login/v3/oauth/access?grant_type=authorization_code&code={code}&redirect_uri=http://{host}/connect_sonos.api" payload={} headers = { - 'Authorization': 'Basic NzQ0MzZkZDYtNDc2YS00NDcwLWFkYTMtM2E5ZGE0NjQyZGVjOjZlNmRhOGUzLWQ1MWEtNGFhMS1hZDg5LTFmZTY4OGVkZTI3ZA==', + 'Authorization': f'Basic {sonos_encoded_credentials}', 'Cookie': 'JSESSIONID=2DEFC02D2184D987F4CCAD5E45196948; AWSELB=69BFEFC914A689BF6DC8E4652748D7B501ED60290D5EA56F2E543ABD7CF357A5F65186AEBC76E6A16196350947ED84835621A185D1BF63900D4B3E7BC7FE3CF19CCF26B78C; AWSELBCORS=69BFEFC914A689BF6DC8E4652748D7B501ED60290D5EA56F2E543ABD7CF357A5F65186AEBC76E6A16196350947ED84835621A185D1BF63900D4B3E7BC7FE3CF19CCF26B78C' } @@ -208,13 +209,16 @@ def sentence(state: QueryStateDict, result: Result) -> None: response_json = response.json() sonos_access_token = response_json["access_token"] sonos_refresh_token = response_json["refresh_token"] + print(response.text) # print("access token :", response.access_token) # print("refresh token :", response.refresh_token) current_time = time.time() print("current time :", current_time) - # q.set_client_data("sonos_access_token", response.access_token) - # q.set_client_data("sonos_token_time", current_time) + + q.set_client_data("sonos_access_token", sonos_access_token) + q.set_client_data("sonos_refresh_token", sonos_refresh_token) + q.set_client_data("sonos_token_time", current_time) From 5cece8c2a1a29baf8fb3bd2702fe69ce56db1a9f Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Thu, 23 Jun 2022 14:52:12 +0000 Subject: [PATCH 080/371] style fix --- queries/iot_connect.py | 31 ++++++++++++------------------- routes/api.py | 6 ++---- 2 files changed, 14 insertions(+), 23 deletions(-) diff --git a/queries/iot_connect.py b/queries/iot_connect.py index 58204425..e8615f61 100644 --- a/queries/iot_connect.py +++ b/queries/iot_connect.py @@ -40,6 +40,7 @@ from routes import better_jsonify from util import read_api_key + class SmartLights(TypedDict): selected_light: str philips_hue: Dict[str, str] @@ -60,17 +61,10 @@ def help_text(lemma: str) -> str: """Help text to return when query.py is unable to parse a query but one of the above lemmas is found in it""" return "Ég skil þig ef þú segir til dæmis: {0}.".format( - random.choice( - ( - "Tengu miðstöðina", - "Tengdu ljósin" - "Tengdu hátalarann" - ) - ) + random.choice(("Tengu miðstöðina", "Tengdu ljósin" "Tengdu hátalarann")) ) - # This module wants to handle parse trees for queries HANDLE_TREE = True @@ -108,20 +102,24 @@ def help_text(lemma: str) -> str: """ + def QIoTConnectLights(node: Node, params: QueryStateDict, result: Result) -> None: result.qtype = "connect_lights" result.action = "connect_lights" + def QIoTConnectHub(node: Node, params: QueryStateDict, result: Result) -> None: print("Connect Hub") result.qtype = "connect_hub" result.action = "connect_hub" + def QIoTConnectSpeaker(node: Node, params: QueryStateDict, result: Result) -> None: print("Connect Speaker") result.qtype = "connect_speaker" result.action = "connect_speaker" + def QIoTCreateSpeakerToken(node: Node, params: QueryStateDict, result: Result) -> None: print("Create Token") result.qtype = "create_speaker_token" @@ -180,7 +178,9 @@ def sentence(state: QueryStateDict, result: Result) -> None: q.set_answer(response, answer, voice_answer) print("sonos_key :", sonos_key) print("host :", host) - q.set_url(f"https://api.sonos.com/login/v3/oauth?client_id={sonos_key}&response_type=code&state={client_id}&scope=playback-control-all&redirect_uri=http://{host}/connect_sonos.api") + q.set_url( + f"https://api.sonos.com/login/v3/oauth?client_id={sonos_key}&response_type=code&state={client_id}&scope=playback-control-all&redirect_uri=http://{host}/connect_sonos.api" + ) return elif result.qtype == "create_speaker_token": sonos_encoded_credentials = read_api_key("SonosEncodedCredentials") @@ -195,10 +195,10 @@ def sentence(state: QueryStateDict, result: Result) -> None: host = str(flask.request.host) url = f"https://api.sonos.com/login/v3/oauth/access?grant_type=authorization_code&code={code}&redirect_uri=http://{host}/connect_sonos.api" - payload={} + payload = {} headers = { - 'Authorization': f'Basic {sonos_encoded_credentials}', - 'Cookie': 'JSESSIONID=2DEFC02D2184D987F4CCAD5E45196948; AWSELB=69BFEFC914A689BF6DC8E4652748D7B501ED60290D5EA56F2E543ABD7CF357A5F65186AEBC76E6A16196350947ED84835621A185D1BF63900D4B3E7BC7FE3CF19CCF26B78C; AWSELBCORS=69BFEFC914A689BF6DC8E4652748D7B501ED60290D5EA56F2E543ABD7CF357A5F65186AEBC76E6A16196350947ED84835621A185D1BF63900D4B3E7BC7FE3CF19CCF26B78C' + "Authorization": f"Basic {sonos_encoded_credentials}", + "Cookie": "JSESSIONID=2DEFC02D2184D987F4CCAD5E45196948; AWSELB=69BFEFC914A689BF6DC8E4652748D7B501ED60290D5EA56F2E543ABD7CF357A5F65186AEBC76E6A16196350947ED84835621A185D1BF63900D4B3E7BC7FE3CF19CCF26B78C; AWSELBCORS=69BFEFC914A689BF6DC8E4652748D7B501ED60290D5EA56F2E543ABD7CF357A5F65186AEBC76E6A16196350947ED84835621A185D1BF63900D4B3E7BC7FE3CF19CCF26B78C", } response = requests.request("POST", url, headers=headers, data=payload) @@ -219,9 +219,7 @@ def sentence(state: QueryStateDict, result: Result) -> None: q.set_client_data("sonos_access_token", sonos_access_token) q.set_client_data("sonos_refresh_token", sonos_refresh_token) q.set_client_data("sonos_token_time", current_time) - - # if client_id and code: # success = QueryObject.store_query_data( # client_id, "sonos_code", code @@ -231,9 +229,6 @@ def sentence(state: QueryStateDict, result: Result) -> None: # return better_jsonify(valid=False, errmsg="Error registering Sonos token.") - - - # smartdevice_type = "smartlights" # client_id = str(q.client_id) # print("client_id:", client_id) @@ -264,6 +259,4 @@ def sentence(state: QueryStateDict, result: Result) -> None: # print("selected light :", selected_light) # print("hue credentials :", hue_credentials) - - # f"var BRIDGE_IP = '192.168.1.68';var USERNAME = 'p3obluiXT13IbHMpp4X63ZvZnpNRdbqqMt723gy2';" diff --git a/routes/api.py b/routes/api.py index 803f6ecb..c45f418f 100755 --- a/routes/api.py +++ b/routes/api.py @@ -726,10 +726,8 @@ def sonos_code(version: int = 1) -> Response: client_id = args.get("state") code = args.get("code") if client_id and code: - success = QueryObject.store_query_data( - client_id, "sonos_code", code - ) + success = QueryObject.store_query_data(client_id, "sonos_code", code) if success: return better_jsonify(valid=True, msg="Registered sonos code") - return better_jsonify(valid=False, errmsg="Error registering sonos code.") \ No newline at end of file + return better_jsonify(valid=False, errmsg="Error registering sonos code.") From 443297043be3a00282d37e744595dbb572f989d2 Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Thu, 23 Jun 2022 15:04:29 +0000 Subject: [PATCH 081/371] Added basic functions to iot_speakers.py --- queries/iot_speakers.py | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/queries/iot_speakers.py b/queries/iot_speakers.py index 3838e698..492e0ba1 100644 --- a/queries/iot_speakers.py +++ b/queries/iot_speakers.py @@ -1,4 +1,4 @@ -def getHouseholds(): +def getHouseholds(token): """ Returns the list of households of the user """ @@ -9,24 +9,24 @@ def getHouseholds(): response = requests.request("GET", url, headers=headers, data=payload) - print(response.text) + return response -def getGroups(): +def getGroups(houshold_id, token): """ Returns the list of groups of the user """ - url = "https://api.ws.sonos.com/control/api/v1/households/Sonos_2qmmZYj1IfZpziI3yTZT2AdYkP.LzZPKytb_zgm6t3fVIv7/groups" + url = "https://api.ws.sonos.com/control/api/v1/households/{household_id}/groups" payload = {} headers = {"Authorization": f"Bearer {token}"} response = requests.request("GET", url, headers=headers, data=payload) - print(response.text) + return response -def createToken(code): +def createToken(code, sonos_encoded_credentials): """ Creates a token given a code """ @@ -40,4 +40,20 @@ def createToken(code): response = requests.request("POST", url, headers=headers, data=payload) - print(response.text) + return response + + +def togglePlayPause(group_id, token): + """ + Toggles the play/pause of a group + """ + url = ( + f"https://api.ws.sonos.com/control/api/v1/groups/{group_id}/playback/playPause" + ) + + payload = {} + headers = {"Content-Type": "application/json", "Authorization": f"Bearer {token}"} + + response = requests.request("POST", url, headers=headers, data=payload) + + return response From 261790d6d80b558a4d9622821c33cd173ae7813c Mon Sep 17 00:00:00 2001 From: Logi E Date: Thu, 23 Jun 2022 15:28:42 +0000 Subject: [PATCH 082/371] working now! code is improved --- queries/__init__.py | 24 ------------- queries/dialogue.py | 35 ++++++++++++++----- .../{fruitseller.py => fruitseller_module.py} | 22 +++--------- 3 files changed, 30 insertions(+), 51 deletions(-) rename queries/{fruitseller.py => fruitseller_module.py} (95%) diff --git a/queries/__init__.py b/queries/__init__.py index 80bf94ec..e7538db5 100755 --- a/queries/__init__.py +++ b/queries/__init__.py @@ -705,27 +705,3 @@ def join_grammar_files(folder: str) -> str: with open(os.path.join(fpath, fname), mode="r") as file: grammar.append(file.read()) return "\n".join(grammar) - - -# class ResourceType(TypedDict, total=False): -# """ -# Representation of a single resource in a dialogue. -# """ - -# name: str -# prompt: str -# type: str -# repeat_prompt: Optional[str] -# required: bool -# repeatable: bool -# # verification_function: "check_fruits" -# confirm_prompt: Optional[str] -# data: Any -# # state: int # TODO: wrong type? -# cancel_prompt: Optional[str] -# # next_states: List[Any] -# # - QNo: #"verification_prompt" -# # - QYes: "Date" -# # - QNo: "repeat_prompt" -# # - QCancel: *Cancel - diff --git a/queries/dialogue.py b/queries/dialogue.py index 5c13ea4e..0533469b 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -2,8 +2,8 @@ from typing import Any, Dict, Union, List, Optional, cast from typing_extensions import TypedDict -import datetime import os.path +import datetime import yaml # try: @@ -31,7 +31,7 @@ class DialogueStructureType(TypedDict): """ """ dialogue_name: str - resources: List[Dict[Any, Any]] + resources: List["Resource"] class ResourceState(IntEnum): @@ -47,9 +47,7 @@ class ResourceState(IntEnum): def load_dialogue_structure(filename: str) -> Any: """Loads dialogue structure from YAML file.""" basepath, _ = os.path.split(os.path.realpath(__file__)) - print(basepath) - fpath = os.path.join(basepath, filename) - print(fpath) + fpath = os.path.join(basepath, filename, filename + ".yaml") # TODO: Fix this obj = None with open(fpath, mode="r") as file: obj = yaml.safe_load(file) @@ -70,7 +68,7 @@ def __init__(self, dialogue_name: str, query: Query, result: Result): self._dialogue_name = dialogue_name self._q = query self._result = result - self._saved_state = self.get_dialogue_state() + self._saved_state = self._get_saved_dialogue_state() def not_in_dialogue(self, start_dialogue_qtype: str) -> bool: """Check if the client is in or wants to start this dialogue""" @@ -92,15 +90,34 @@ def setup_dialogue(self): print(resource) newResource = DatetimeResource(**resource) if self._saved_state and i < len(self._saved_state["resources"]): - newResource.__dict__.update(self._saved_state["resources"][i]) + newResource.update(self._saved_state["resources"][i]) newResource.state = ResourceState( - self._saved_state["resources"][i]["state"] + self._saved_state["resources"][i].state ) self.resources.append(newResource) self.resourceState: Optional[Resource] = None self.ans: Optional[str] = None + def start_dialogue(self): + """Save client's state as having started this dialogue""" + self.set_dialogue_state( + { + "dialogue_name": self._dialogue_name, + "resources": [], + } + ) + + def update_dialogue_state(self): + """Update the dialogue state for a client""" + self.set_dialogue_state( + { + "dialogue_name": self._dialogue_name, + "resources": self.resources, + } + ) + # self.set_dialogue_state() + def generate_answer(self, result: Result) -> str: for resource in self.resources: if resource.required and resource.state is not ResourceState.CONFIRMED: @@ -115,7 +132,7 @@ def generate_answer(self, result: Result) -> str: return resource.generate_answer() return "Upp kom villa, reyndu aftur." - def get_dialogue_state(self) -> DialogueStructureType: + def _get_saved_dialogue_state(self) -> DialogueStructureType: """Load the dialogue state for a client""" cd = self._q.client_data(DIALOGUE_KEY) # Return empty DialogueStructureType in case no dialogue state exists diff --git a/queries/fruitseller.py b/queries/fruitseller_module.py similarity index 95% rename from queries/fruitseller.py rename to queries/fruitseller_module.py index c8c9239d..d7313163 100644 --- a/queries/fruitseller.py +++ b/queries/fruitseller_module.py @@ -351,28 +351,14 @@ def sentence(state: QueryStateDict, result: Result) -> None: # Successfully matched a query type try: if result.qtype == _START_DIALOGUE_QTYPE: - q.set_dialogue_state( - {"dialogue_name": _DIALOGUE_NAME, "resources": [], "variables": None} - ) - else: - print("Í else") - if dialogue_state is None: - q.set_error("E_QUERY_NOT_UNDERSTOOD") - return - print("fyrir generate") + dsm.start_dialogue() + ans = dsm.generate_answer(result) - print("eftir generate") - q.set_dialogue_state( - { - "dialogue_name": _DIALOGUE_NAME, - "resources": [r.__dict__ for r in dsm.resources], - "variables": None, - } - ) + dsm.update_dialogue_state() print("woohoo") if result.qtype == "OrderComplete" or result.qtype == "CancelOrder": - q.end_dialogue() + dsm.end_dialogue() q.set_answer(*gen_answer(ans)) return From 5949903c7d3af06aa2e9c6ebdbef4849d7dadfb4 Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Thu, 23 Jun 2022 15:36:37 +0000 Subject: [PATCH 083/371] IOT token dictionary refactor --- queries/iot_connect.py | 96 ++++++++++-------------------------------- 1 file changed, 23 insertions(+), 73 deletions(-) diff --git a/queries/iot_connect.py b/queries/iot_connect.py index 58204425..554f7143 100644 --- a/queries/iot_connect.py +++ b/queries/iot_connect.py @@ -40,13 +40,13 @@ from routes import better_jsonify from util import read_api_key -class SmartLights(TypedDict): - selected_light: str - philips_hue: Dict[str, str] + +class SpeakerCredentials(TypedDict): + tokens: Dict[str, str] class DeviceData(TypedDict): - smartlights: SmartLights + sonos: SpeakerCredentials _IoT_QTYPE = "IoTConnect" @@ -60,17 +60,10 @@ def help_text(lemma: str) -> str: """Help text to return when query.py is unable to parse a query but one of the above lemmas is found in it""" return "Ég skil þig ef þú segir til dæmis: {0}.".format( - random.choice( - ( - "Tengu miðstöðina", - "Tengdu ljósin" - "Tengdu hátalarann" - ) - ) + random.choice(("Tengu miðstöðina", "Tengdu ljósin" "Tengdu hátalarann")) ) - # This module wants to handle parse trees for queries HANDLE_TREE = True @@ -108,20 +101,24 @@ def help_text(lemma: str) -> str: """ + def QIoTConnectLights(node: Node, params: QueryStateDict, result: Result) -> None: result.qtype = "connect_lights" result.action = "connect_lights" + def QIoTConnectHub(node: Node, params: QueryStateDict, result: Result) -> None: print("Connect Hub") result.qtype = "connect_hub" result.action = "connect_hub" + def QIoTConnectSpeaker(node: Node, params: QueryStateDict, result: Result) -> None: print("Connect Speaker") result.qtype = "connect_speaker" result.action = "connect_speaker" + def QIoTCreateSpeakerToken(node: Node, params: QueryStateDict, result: Result) -> None: print("Create Token") result.qtype = "create_speaker_token" @@ -180,7 +177,9 @@ def sentence(state: QueryStateDict, result: Result) -> None: q.set_answer(response, answer, voice_answer) print("sonos_key :", sonos_key) print("host :", host) - q.set_url(f"https://api.sonos.com/login/v3/oauth?client_id={sonos_key}&response_type=code&state={client_id}&scope=playback-control-all&redirect_uri=http://{host}/connect_sonos.api") + q.set_url( + f"https://api.sonos.com/login/v3/oauth?client_id={sonos_key}&response_type=code&state={client_id}&scope=playback-control-all&redirect_uri=http://{host}/connect_sonos.api" + ) return elif result.qtype == "create_speaker_token": sonos_encoded_credentials = read_api_key("SonosEncodedCredentials") @@ -195,10 +194,10 @@ def sentence(state: QueryStateDict, result: Result) -> None: host = str(flask.request.host) url = f"https://api.sonos.com/login/v3/oauth/access?grant_type=authorization_code&code={code}&redirect_uri=http://{host}/connect_sonos.api" - payload={} + payload = {} headers = { - 'Authorization': f'Basic {sonos_encoded_credentials}', - 'Cookie': 'JSESSIONID=2DEFC02D2184D987F4CCAD5E45196948; AWSELB=69BFEFC914A689BF6DC8E4652748D7B501ED60290D5EA56F2E543ABD7CF357A5F65186AEBC76E6A16196350947ED84835621A185D1BF63900D4B3E7BC7FE3CF19CCF26B78C; AWSELBCORS=69BFEFC914A689BF6DC8E4652748D7B501ED60290D5EA56F2E543ABD7CF357A5F65186AEBC76E6A16196350947ED84835621A185D1BF63900D4B3E7BC7FE3CF19CCF26B78C' + "Authorization": f"Basic {sonos_encoded_credentials}", + "Cookie": "JSESSIONID=2DEFC02D2184D987F4CCAD5E45196948; AWSELB=69BFEFC914A689BF6DC8E4652748D7B501ED60290D5EA56F2E543ABD7CF357A5F65186AEBC76E6A16196350947ED84835621A185D1BF63900D4B3E7BC7FE3CF19CCF26B78C; AWSELBCORS=69BFEFC914A689BF6DC8E4652748D7B501ED60290D5EA56F2E543ABD7CF357A5F65186AEBC76E6A16196350947ED84835621A185D1BF63900D4B3E7BC7FE3CF19CCF26B78C", } response = requests.request("POST", url, headers=headers, data=payload) @@ -210,60 +209,11 @@ def sentence(state: QueryStateDict, result: Result) -> None: sonos_access_token = response_json["access_token"] sonos_refresh_token = response_json["refresh_token"] print(response.text) - - # print("access token :", response.access_token) - # print("refresh token :", response.refresh_token) - current_time = time.time() - print("current time :", current_time) - - q.set_client_data("sonos_access_token", sonos_access_token) - q.set_client_data("sonos_refresh_token", sonos_refresh_token) - q.set_client_data("sonos_token_time", current_time) - - - - # if client_id and code: - # success = QueryObject.store_query_data( - # client_id, "sonos_code", code - # ) - # if success: - # return better_jsonify(valid=True, msg="Registered Sonos token") - - # return better_jsonify(valid=False, errmsg="Error registering Sonos token.") - - - - - # smartdevice_type = "smartlights" - # client_id = str(q.client_id) - # print("client_id:", client_id) - - # # Fetch relevant data from the device_data table to perform an action on the lights - # device_data = cast(Optional[DeviceData], q.client_data(smartdevice_type)) - # print("device data :", device_data) - - # selected_light: Optional[str] = None - # hue_credentials: Optional[Dict[str, str]] = None - - # if device_data is not None and smartdevice_type in device_data: - # dev = device_data[smartdevice_type] - # assert dev is not None - # selected_light = dev.get("selected_light") - # hue_credentials = dev.get("philips_hue") - # bridge_ip = hue_credentials.get("ipAddress") - # username = hue_credentials.get("username") - - # if not device_data or not hue_credentials: - # answer = "ég var að kveikja ljósin! " - # q.set_answer(*gen_answer(answer)) - # return - - # # Successfully matched a query type - # print("bridge_ip: ", bridge_ip) - # print("username: ", username) - # print("selected light :", selected_light) - # print("hue credentials :", hue_credentials) - - - - # f"var BRIDGE_IP = '192.168.1.68';var USERNAME = 'p3obluiXT13IbHMpp4X63ZvZnpNRdbqqMt723gy2';" + sonos_credentials_dict = { + "access_token": sonos_access_token, + "refresh_token": sonos_refresh_token, + } + q.store_query_data( + str(q.client_id), "sonos_credentials", sonos_credentials_dict + ) + return From 9a97205c9f4d9fba249ef0bc44847093f07988dd Mon Sep 17 00:00:00 2001 From: Logi E Date: Thu, 23 Jun 2022 15:37:09 +0000 Subject: [PATCH 084/371] added helper functions for DatetimeResource --- queries/dialogue.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/queries/dialogue.py b/queries/dialogue.py index 0533469b..e36e5dfe 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -298,6 +298,32 @@ class DatetimeResource(Resource): date_fulfilled_prompt: Optional[str] = None time_fulfilled_prompt: Optional[str] = None + def has_date(self) -> bool: + return self.data is not None and any( + isinstance(x, datetime.date) for x in self.data + ) + + def has_time(self) -> bool: + return self.data is not None and any( + isinstance(x, datetime.time) for x in self.data + ) + + def update_date(self, new_date: datetime.date) -> None: + if self.data is None: + self.data = [] + for i, x in enumerate(self.data): + if isinstance(x, datetime.date): + self.data[i] = new_date + break + + def update_time(self, new_time: datetime.time) -> None: + if self.data is None: + self.data = [] + for i, x in enumerate(self.data): + if isinstance(x, datetime.time): + self.data[i] = new_time + break + def generate_answer(self) -> str: ans = "" if self.state is ResourceState.UNFULFILLED: From 8f35a38e41de1ac7a6ae7f6cd7cc1e2ced9bbe32 Mon Sep 17 00:00:00 2001 From: Logi E Date: Thu, 23 Jun 2022 15:45:05 +0000 Subject: [PATCH 085/371] using helper functions in query module --- queries/dialogue.py | 34 +++++++++++----------------------- queries/fruitseller_module.py | 20 ++++++-------------- 2 files changed, 17 insertions(+), 37 deletions(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index e36e5dfe..4d41674e 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -289,40 +289,28 @@ def generate_answer(self) -> str: @dataclass class YesNoResource(Resource): - data: Optional[bool] = None + data: bool = False @dataclass class DatetimeResource(Resource): - data: Optional[List[Union[Optional[datetime.date], Optional[datetime.time]]]] = None + data: List[Union[Optional[datetime.date], Optional[datetime.time]]] = list( + (None, None) + ) date_fulfilled_prompt: Optional[str] = None time_fulfilled_prompt: Optional[str] = None def has_date(self) -> bool: - return self.data is not None and any( - isinstance(x, datetime.date) for x in self.data - ) + return isinstance(self.data[0], datetime.date) def has_time(self) -> bool: - return self.data is not None and any( - isinstance(x, datetime.time) for x in self.data - ) + return isinstance(self.data[1], datetime.time) + + def set_date(self, new_date: Optional[datetime.date] = None) -> None: + self.data[0] = new_date - def update_date(self, new_date: datetime.date) -> None: - if self.data is None: - self.data = [] - for i, x in enumerate(self.data): - if isinstance(x, datetime.date): - self.data[i] = new_date - break - - def update_time(self, new_time: datetime.time) -> None: - if self.data is None: - self.data = [] - for i, x in enumerate(self.data): - if isinstance(x, datetime.time): - self.data[i] = new_time - break + def set_time(self, new_time: Optional[datetime.time] = None) -> None: + self.data[1] = new_time def generate_answer(self) -> str: ans = "" diff --git a/queries/fruitseller_module.py b/queries/fruitseller_module.py index d7313163..361ec995 100644 --- a/queries/fruitseller_module.py +++ b/queries/fruitseller_module.py @@ -217,11 +217,9 @@ def QFruitDateTime(node: Node, params: QueryStateDict, result: Result) -> None: result["delivery_date"] = datetime.date(y, m, d) def _dt_callback(resource: DatetimeResource, result: Result) -> None: - if resource.data is None: - resource.data = [] print("DATETIME SHOULD BE FULFILLED NOW") - resource.data.append(result["delivery_date"]) - resource.data.append(result["delivery_time"]) + resource.set_date(result["delivery_date"]) + resource.set_time(result["delivery_time"]) resource.state = ResourceState.FULFILLED if "callbacks" not in result: @@ -249,12 +247,8 @@ def QFruitDate(node: Node, params: QueryStateDict, result: Result) -> None: print("DELIVERY DATE:", result["delivery_date"]) def _dt_callback(resource: DatetimeResource, result: Result) -> None: - if resource.data is None: - resource.data = [] - - resource.data.append(result["delivery_date"]) - if isinstance(resource.data[0], datetime.time): - resource.data.reverse() + resource.set_date(result["delivery_date"]) + if resource.has_time(): resource.state = ResourceState.FULFILLED else: resource.state = ResourceState.PARTIALLY_FULFILLED @@ -278,10 +272,8 @@ def QFruitTime(node: Node, params: QueryStateDict, result: Result): print("TIME IS: ", result["delivery_time"]) def _dt_callback(resource: DatetimeResource, result: Result) -> None: - if resource.data is None: - resource.data = [] - resource.data.append(result["delivery_time"]) - if isinstance(resource.data[0], datetime.date): + resource.set_time(result["delivery_time"]) + if resource.has_date(): resource.state = ResourceState.FULFILLED else: resource.state = ResourceState.PARTIALLY_FULFILLED From c222c088c8dddce915ede49e7df35dcae224b21d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Thu, 23 Jun 2022 16:01:22 +0000 Subject: [PATCH 086/371] data resource attribute no longer optional --- queries/dialogue.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index 4d41674e..189fbe6e 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -11,7 +11,7 @@ # except ModuleNotFoundError: # import tomli as tomllib from enum import IntEnum, auto -from dataclasses import dataclass +from dataclasses import dataclass, field from tree import Result from query import Query, ClientDataDict @@ -36,7 +36,7 @@ class DialogueStructureType(TypedDict): class ResourceState(IntEnum): """Enum representing the different states a dialogue resource can be in.""" - + INITIAL = auto() UNFULFILLED = auto() PARTIALLY_FULFILLED = auto() FULFILLED = auto() @@ -121,6 +121,8 @@ def update_dialogue_state(self): def generate_answer(self, result: Result) -> str: for resource in self.resources: if resource.required and resource.state is not ResourceState.CONFIRMED: + if resource.state is ResourceState.INITIAL: + resource.state = ResourceState.UNFULFILLED if "callbacks" in result: for cb in result.callbacks: cb(resource, result) @@ -236,7 +238,7 @@ class Resource: name: str = "" required: bool = True data: Any = None - state: ResourceState = ResourceState.UNFULFILLED + state: ResourceState = ResourceState.INITIAL prompt: str = "" type: str = "" repeatable: bool = False @@ -258,7 +260,7 @@ def update(self, new_data: Optional["Resource"]) -> None: @dataclass class ListResource(Resource): - data: Optional[ListResourceType] = None + data: ListResourceType = field(default_factory=list) available_options: Optional[ListResourceType] = None def list_available_options(self) -> str: @@ -294,9 +296,7 @@ class YesNoResource(Resource): @dataclass class DatetimeResource(Resource): - data: List[Union[Optional[datetime.date], Optional[datetime.time]]] = list( - (None, None) - ) + data: List[Union[Optional[datetime.date], Optional[datetime.time]]] = field(default_factory=lambda: [None, None]) date_fulfilled_prompt: Optional[str] = None time_fulfilled_prompt: Optional[str] = None @@ -348,7 +348,7 @@ def generate_answer(self) -> str: @dataclass class NumberResource(Resource): - data: Optional[int] = None + data: int = 0 """ Three classes implemented for each resource From 14df48f5e951c1e16950d8229ca881d844bbad73 Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Thu, 23 Jun 2022 16:13:20 +0000 Subject: [PATCH 087/371] IoTconnect code cleanup --- queries/iot_connect.py | 41 ++++++++----------------- queries/js/IoT_Embla/Smart_Things/st.js | 5 +-- 2 files changed, 13 insertions(+), 33 deletions(-) diff --git a/queries/iot_connect.py b/queries/iot_connect.py index 554f7143..50b0f107 100644 --- a/queries/iot_connect.py +++ b/queries/iot_connect.py @@ -140,11 +140,10 @@ def sentence(state: QueryStateDict, result: Result) -> None: return q.set_qtype(result.qtype) + host = str(flask.request.host) + client_id = str(q.client_id) + if result.qtype == "connect_lights": - host = str(flask.request.host) - print("host: ", host) - client_id = str(q.client_id) - print("client_id:", client_id) js = read_jsfile("IoT_Embla/Philips_Hue/hub.js") js += f"syncConnectHub('{client_id}','{host}');" answer = "Philips Hue miðstöðin hefur verið tengd" @@ -153,65 +152,49 @@ def sentence(state: QueryStateDict, result: Result) -> None: q.set_answer(response, answer, voice_answer) q.set_command(js) return + elif result.qtype == "connect_hub": - host = str(flask.request.host) - print("host: ", host) - client_id = str(q.client_id) - print("client_id:", client_id) js = read_jsfile("IoT_Embla/Smart_Things/st_connecthub.js") js += f"syncConnectHub('{client_id}','{host}');" answer = "Smart Things miðstöðin hefur verið tengd" - voice_answer = answer - response = dict(answer=answer) + voice_answer, response = answer, dict(answer=answer) q.set_answer(response, answer, voice_answer) q.set_command(js) return + elif result.qtype == "connect_speaker": sonos_key = read_api_key("SonosKey") - host = str(flask.request.host) - print("Connect speaker sentence") - client_id = str(q.client_id) answer = "Skráðu þig inn hjá Sonos" - voice_answer = answer - response = dict(answer=answer) + voice_answer, response = answer, dict(answer=answer) q.set_answer(response, answer, voice_answer) - print("sonos_key :", sonos_key) - print("host :", host) q.set_url( f"https://api.sonos.com/login/v3/oauth?client_id={sonos_key}&response_type=code&state={client_id}&scope=playback-control-all&redirect_uri=http://{host}/connect_sonos.api" ) return + elif result.qtype == "create_speaker_token": sonos_encoded_credentials = read_api_key("SonosEncodedCredentials") - print("credientials :", sonos_encoded_credentials) answer = "Ég bjó til tóka frá Sonos" - voice_answer = answer - response = dict(answer=answer) + voice_answer, response = answer, dict(answer=answer) code = str(q.client_data("sonos_code")) - print(code) q.set_answer(response, answer, voice_answer) q.set_url(f"https://google.com/") - host = str(flask.request.host) - url = f"https://api.sonos.com/login/v3/oauth/access?grant_type=authorization_code&code={code}&redirect_uri=http://{host}/connect_sonos.api" + url = f"https://api.sonos.com/login/v3/oauth/access?grant_type=authorization_code&code={code}&redirect_uri=http://{host}/connect_sonos.api" payload = {} headers = { "Authorization": f"Basic {sonos_encoded_credentials}", "Cookie": "JSESSIONID=2DEFC02D2184D987F4CCAD5E45196948; AWSELB=69BFEFC914A689BF6DC8E4652748D7B501ED60290D5EA56F2E543ABD7CF357A5F65186AEBC76E6A16196350947ED84835621A185D1BF63900D4B3E7BC7FE3CF19CCF26B78C; AWSELBCORS=69BFEFC914A689BF6DC8E4652748D7B501ED60290D5EA56F2E543ABD7CF357A5F65186AEBC76E6A16196350947ED84835621A185D1BF63900D4B3E7BC7FE3CF19CCF26B78C", } - response = requests.request("POST", url, headers=headers, data=payload) if response.status_code != 200: print("Error:", response.status_code) print(response.text) return response_json = response.json() - sonos_access_token = response_json["access_token"] - sonos_refresh_token = response_json["refresh_token"] - print(response.text) sonos_credentials_dict = { - "access_token": sonos_access_token, - "refresh_token": sonos_refresh_token, + "access_token": response_json["access_token"], + "refresh_token": response_json["refresh_token"], } q.store_query_data( str(q.client_id), "sonos_credentials", sonos_credentials_dict diff --git a/queries/js/IoT_Embla/Smart_Things/st.js b/queries/js/IoT_Embla/Smart_Things/st.js index 3e069ba2..2f8662fa 100644 --- a/queries/js/IoT_Embla/Smart_Things/st.js +++ b/queries/js/IoT_Embla/Smart_Things/st.js @@ -133,9 +133,7 @@ function smartThingsWrapper( command, arguments = null ) { - - targetObject = smartThingsFuzzySearch(query) - + targetObject = smartThingsFuzzySearch(query); var myHeaders = new Headers(); myHeaders.append("Authorization", AUTH_TOKEN); @@ -169,4 +167,3 @@ function smartThingsWrapper( .then((result) => console.log(result)) .catch((error) => console.log("error", error)); } - From 073d49ee3b1c4aedb85571d7344711b8ddb02f45 Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Thu, 23 Jun 2022 16:16:25 +0000 Subject: [PATCH 088/371] remove redirect url when creating sonos token --- queries/iot_connect.py | 1 - 1 file changed, 1 deletion(-) diff --git a/queries/iot_connect.py b/queries/iot_connect.py index 50b0f107..d82cf980 100644 --- a/queries/iot_connect.py +++ b/queries/iot_connect.py @@ -178,7 +178,6 @@ def sentence(state: QueryStateDict, result: Result) -> None: voice_answer, response = answer, dict(answer=answer) code = str(q.client_data("sonos_code")) q.set_answer(response, answer, voice_answer) - q.set_url(f"https://google.com/") url = f"https://api.sonos.com/login/v3/oauth/access?grant_type=authorization_code&code={code}&redirect_uri=http://{host}/connect_sonos.api" payload = {} From 21491e3af602e2bee079dc4f93009d2961408287 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Thu, 23 Jun 2022 17:12:20 +0000 Subject: [PATCH 089/371] Fixed that it didn't work to go back to previous resources --- queries/dialogue.py | 20 ++++++++++++++++---- queries/fruitseller_module.py | 15 ++++++++------- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index 189fbe6e..00a9f591 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -119,19 +119,31 @@ def update_dialogue_state(self): # self.set_dialogue_state() def generate_answer(self, result: Result) -> str: - for resource in self.resources: + i = 0 + while i < len(self.resources): + resource = self.resources[i] if resource.required and resource.state is not ResourceState.CONFIRMED: if resource.state is ResourceState.INITIAL: resource.state = ResourceState.UNFULFILLED if "callbacks" in result: - for cb in result.callbacks: - cb(resource, result) + while len(result.callbacks) > 0: + r_name, cb = result.callbacks.pop(0) + if self.resources[i].name in r_name: + cb(resource, result) + else: + while i > -1: + i -= 1 + if self.resources[i].name in r_name: + cb(self.resources[i], result) + break if ( resource.state is ResourceState.CONFIRMED and resource != self.resources[-1] ): + i += 1 continue - return resource.generate_answer() + return self.resources[i].generate_answer() + i += 1 return "Upp kom villa, reyndu aftur." def _get_saved_dialogue_state(self) -> DialogueStructureType: diff --git a/queries/fruitseller_module.py b/queries/fruitseller_module.py index 361ec995..6df73b6f 100644 --- a/queries/fruitseller_module.py +++ b/queries/fruitseller_module.py @@ -8,6 +8,7 @@ from queries import gen_answer, parse_num from queries.dialogue import ( DatetimeResource, + ListResource, Resource, ResourceState, DialogueStateManager, @@ -140,14 +141,14 @@ def QFruitStartQuery(node: Node, params: QueryStateDict, result: Result): def QAddFruitQuery(node: Node, params: QueryStateDict, result: Result): if "callbacks" not in result: result["callbacks"] = [] - result.callbacks.append(_add_fruit) + result.callbacks.append((("Fruits", ),_add_fruit)) result.qtype = "QAddFruitQuery" def QRemoveFruitQuery(node: Node, params: QueryStateDict, result: Result): if "callbacks" not in result: result["callbacks"] = [] - result.callbacks.append(_remove_fruit) + result.callbacks.append((("Fruits", ), _remove_fruit)) result.qtype = "QRemoveFruitQuery" @@ -162,14 +163,14 @@ def QFruitOptionsQuery(node: Node, params: QueryStateDict, result: Result): def QYes(node: Node, params: QueryStateDict, result: Result): if "callbacks" not in result: result["callbacks"] = [] - result.callbacks.append(_parse_yes) + result.callbacks.append((("Fruits", "Date"), _parse_yes)) result.qtype = "QYes" def QNo(node: Node, params: QueryStateDict, result: Result): if "callbacks" not in result: result["callbacks"] = [] - result.callbacks.append(_parse_no) + result.callbacks.append((("Fruits", "Date"), _parse_no)) result.qtype = "QNo" @@ -224,7 +225,7 @@ def _dt_callback(resource: DatetimeResource, result: Result) -> None: if "callbacks" not in result: result["callbacks"] = [] - result.callbacks.append(_dt_callback) + result.callbacks.append((("Date", ), _dt_callback)) def QFruitDate(node: Node, params: QueryStateDict, result: Result) -> None: @@ -255,7 +256,7 @@ def _dt_callback(resource: DatetimeResource, result: Result) -> None: if "callbacks" not in result: result["callbacks"] = [] - result.callbacks.append(_dt_callback) + result.callbacks.append((("Date", ), _dt_callback)) return raise ValueError("No date in {0}".format(str(datenode))) @@ -280,7 +281,7 @@ def _dt_callback(resource: DatetimeResource, result: Result) -> None: if "callbacks" not in result: result["callbacks"] = [] - result.callbacks.append(_dt_callback) + result.callbacks.append((("Date", ), _dt_callback)) def _remove_fruit(resource: Resource, result: Result) -> None: From defb34c3d716491cc6c09e8922bd07dbfd198912 Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Thu, 23 Jun 2022 18:37:01 +0000 Subject: [PATCH 090/371] added sonos functions to file --- queries/iot_connect.py | 31 ++ queries/iot_speakers.py | 680 +++++++++++++++++++++++++++++++++++++--- routes/sonos.py | 113 +++++++ 3 files changed, 784 insertions(+), 40 deletions(-) create mode 100644 routes/sonos.py diff --git a/queries/iot_connect.py b/queries/iot_connect.py index 50b0f107..8fbd4627 100644 --- a/queries/iot_connect.py +++ b/queries/iot_connect.py @@ -39,6 +39,7 @@ from tree import Result, Node from routes import better_jsonify from util import read_api_key +from speech import text_to_audio_url class SpeakerCredentials(TypedDict): @@ -125,6 +126,33 @@ def QIoTCreateSpeakerToken(node: Node, params: QueryStateDict, result: Result) - result.action = "create_speaker_token" +def audioClip(audioclip_url): + """ + Plays an audioclip + """ + import requests + import json + + url = f"https://api.ws.sonos.com/control/api/v1/players/RINCON_542A1B599FF201400/audioClip" + + payload = json.dumps( + { + "name": "Embla", + "appId": "com.acme.app", + "streamUrl": f"{audioclip_url}", + "volume": 30, + "priority": "HIGH", + "clipType": "CUSTOM", + } + ) + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer bEy3xnmpvoxrLcP7syVxVdjO2maj", + } + + response = requests.request("POST", url, headers=headers, data=payload) + + def sentence(state: QueryStateDict, result: Result) -> None: """Called when sentence processing is complete""" q: Query = state["query"] @@ -148,6 +176,7 @@ def sentence(state: QueryStateDict, result: Result) -> None: js += f"syncConnectHub('{client_id}','{host}');" answer = "Philips Hue miðstöðin hefur verið tengd" voice_answer = answer + # audioClip(text_to_audio_url(voice_answer)) response = dict(answer=answer) q.set_answer(response, answer, voice_answer) q.set_command(js) @@ -166,6 +195,7 @@ def sentence(state: QueryStateDict, result: Result) -> None: sonos_key = read_api_key("SonosKey") answer = "Skráðu þig inn hjá Sonos" voice_answer, response = answer, dict(answer=answer) + audioClip(text_to_audio_url(voice_answer)) q.set_answer(response, answer, voice_answer) q.set_url( f"https://api.sonos.com/login/v3/oauth?client_id={sonos_key}&response_type=code&state={client_id}&scope=playback-control-all&redirect_uri=http://{host}/connect_sonos.api" @@ -176,6 +206,7 @@ def sentence(state: QueryStateDict, result: Result) -> None: sonos_encoded_credentials = read_api_key("SonosEncodedCredentials") answer = "Ég bjó til tóka frá Sonos" voice_answer, response = answer, dict(answer=answer) + audioClip(text_to_audio_url(voice_answer)) code = str(q.client_data("sonos_code")) q.set_answer(response, answer, voice_answer) q.set_url(f"https://google.com/") diff --git a/queries/iot_speakers.py b/queries/iot_speakers.py index 492e0ba1..f0348115 100644 --- a/queries/iot_speakers.py +++ b/queries/iot_speakers.py @@ -1,59 +1,659 @@ -def getHouseholds(token): - """ - Returns the list of households of the user - """ - url = "https://api.ws.sonos.com/control/api/v1/households" +""" - payload = {} - headers = {"Authorization": f"Bearer {token}"} + Greynir: Natural language processing for Icelandic - response = requests.request("GET", url, headers=headers, data=payload) + Randomness query response module - return response + Copyright (C) 2022 Miðeind ehf. + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. -def getGroups(houshold_id, token): - """ - Returns the list of groups of the user - """ - url = "https://api.ws.sonos.com/control/api/v1/households/{household_id}/groups" + You should have received a copy of the GNU General Public License + along with this program. If not, see http://www.gnu.org/licenses/. - payload = {} - headers = {"Authorization": f"Bearer {token}"} + This query module handles queries related to the generation + of random numbers, e.g. "Kastaðu tengingi", "Nefndu tölu milli 5 og 10", etc. - response = requests.request("GET", url, headers=headers, data=payload) +""" - return response +# TODO: add "láttu", "hafðu", "litaðu", "kveiktu" functionality. +# TODO: make the objects of sentences more modular, so that the same structure doesn't need to be written for each action +# TODO: ditto the previous comment. make the initial non-terminals general and go into specifics at the terminal level instead. +# TODO: substituion klósett, baðherbergi hugmyndÆ senda lista i javascript og profa i röð +# TODO: Embla stores old javascript code cached which has caused errors +# TODO: Cut down javascript sent to Embla +# TODO: Two specified groups or lights. +# TODO: No specified location +# TODO: Fix scene issues +from typing import Dict, Mapping, Optional, cast +from typing_extensions import TypedDict -def createToken(code, sonos_encoded_credentials): - """ - Creates a token given a code - """ - url = f"https://api.sonos.com/login/v3/oauth/access?grant_type=authorization_code&code={code}&redirect_uri=http://localhost:5000/connect_sonos.api" +import logging +import random +import json +import flask - payload = {} - headers = { - "Authorization": f"Basic {sonos_encoded_credentials}", - "Cookie": "JSESSIONID=F710019AF0A3B7126A8702577C883B5F; AWSELB=69BFEFC914A689BF6DC8E4652748D7B501ED60290D5EA56F2E543ABD7CF357A5F65186AEBCFB059E28075D83A700FD504C030A53CC28683B515BE3DCA3CC587AFAF606E171; AWSELBCORS=69BFEFC914A689BF6DC8E4652748D7B501ED60290D5EA56F2E543ABD7CF357A5F65186AEBCFB059E28075D83A700FD504C030A53CC28683B515BE3DCA3CC587AFAF606E171", - } +from query import Query, QueryStateDict, AnswerTuple +from queries import gen_answer, read_jsfile, read_grammar_file +from tree import Result, Node - response = requests.request("POST", url, headers=headers, data=payload) - return response +class SmartLights(TypedDict): + selected_light: str + philips_hue: Dict[str, str] -def togglePlayPause(group_id, token): - """ - Toggles the play/pause of a group - """ - url = ( - f"https://api.ws.sonos.com/control/api/v1/groups/{group_id}/playback/playPause" +class DeviceData(TypedDict): + smartlights: SmartLights + + +_IoT_QTYPE = "IoT" + +TOPIC_LEMMAS = [ + "ljós", + "kveikja", + "litur", + "birta", + "hækka", + "stemmning", + "sena", + "stemming", + "stemning", +] + + +def help_text(lemma: str) -> str: + """Help text to return when query.py is unable to parse a query but + one of the above lemmas is found in it""" + return "Ég skil þig ef þú segir til dæmis: {0}.".format( + random.choice( + ( + "Kveiktu á ljósunum inni í eldhúsi", + "Slökktu á leslampanum", + "Breyttu lit lýsingarinnar í stofunni í bláan", + "Gerðu ljósið í borðstofunni bjartara", + "Stilltu á bjartasta niðri í kjallara", + ) + ) + ) + + +_COLORS = { + "gulur": 60 * 65535 / 360, + "rauður": 360 * 65535 / 360, + "grænn": 120 * 65535 / 360, + "blár": 240 * 65535 / 360, + "ljósblár": 180 * 65535 / 360, + "bleikur": 300 * 65535 / 360, + "hvítur": [], + "fjólublár": [], + "brúnn": [], + "appelsínugulur": [], +} + + +# This module wants to handle parse trees for queries +HANDLE_TREE = True + +# The grammar nonterminals this module wants to handle +QUERY_NONTERMINALS = {"QCHANGE"} + +# The context-free grammar for the queries recognized by this plug-in module +# GRAMMAR = read_grammar_file("iot_hue") + +GRAMMAR = f""" + +/þgf = þgf +/ef = ef + +Query → + QCHANGE '?'? + +QCHANGE → + QCHANGEQuery + +QCHANGEQuery -> + QCHANGEMakeVerb QCHANGEMakeRest + | QCHANGESetVerb QCHANGESetRest + | QCHANGEChangeVerb QCHANGEChangeRest + | QCHANGELetVerb QCHANGELetRest + | QCHANGETurnOnVerb QCHANGETurnOnRest + | QCHANGETurnOffVerb QCHANGETurnOffRest + | QCHANGEIncreaseOrDecreaseVerb QCHANGEIncreaseOrDecreaseRest + +QCHANGEMakeVerb -> + 'gera:so'_bh + +QCHANGESetVerb -> + 'setja:so'_bh + | 'stilla:so'_bh + +QCHANGEChangeVerb -> + 'breyta:so'_bh + +QCHANGELetVerb -> + 'láta:so'_bh + +QCHANGETurnOnVerb -> + 'kveikja:so'_bh + +QCHANGETurnOffVerb -> + 'slökkva:so'_bh + +QCHANGEIncreaseOrDecreaseVerb -> + QCHANGEIncreaseVerb + | QCHANGEDecreaseVerb + +QCHANGEIncreaseVerb -> + 'hækka:so'_bh + | 'auka:so'_bh + +QCHANGEDecreaseVerb -> + 'lækka:so'_bh + | 'minnka:so'_bh + +QCHANGEMakeRest -> + QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigMake + | QCHANGESubject/þf QCHANGEHvernigMake QCHANGEHvar? + | QCHANGEHvar? QCHANGESubject/þf QCHANGEHvernigMake + | QCHANGEHvar? QCHANGEHvernigMake QCHANGESubject/þf + | QCHANGEHvernigMake QCHANGESubject/þf QCHANGEHvar? + | QCHANGEHvernigMake QCHANGEHvar? QCHANGESubject/þf + +# TODO: Add support for "stilltu rauðan lit á ljósið í eldhúsinu" +QCHANGESetRest -> + QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigSet + | QCHANGESubject/þf QCHANGEHvernigSet QCHANGEHvar? + | QCHANGEHvar? QCHANGESubject/þf QCHANGEHvernigSet + | QCHANGEHvar? QCHANGEHvernigSet QCHANGESubject/þf + | QCHANGEHvernigSet QCHANGESubject/þf QCHANGEHvar? + | QCHANGEHvernigSet QCHANGEHvar? QCHANGESubject/þf + +QCHANGEChangeRest -> + QCHANGESubjectOne/þgf QCHANGEHvar? QCHANGEHvernigChange + | QCHANGESubjectOne/þgf QCHANGEHvernigChange QCHANGEHvar? + | QCHANGEHvar? QCHANGESubjectOne/þgf QCHANGEHvernigChange + | QCHANGEHvar? QCHANGEHvernigChange QCHANGESubjectOne/þgf + | QCHANGEHvernigChange QCHANGESubjectOne/þgf QCHANGEHvar? + | QCHANGEHvernigChange QCHANGEHvar? QCHANGESubjectOne/þgf + +QCHANGELetRest -> + QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigLet + | QCHANGESubject/þf QCHANGEHvernigLet QCHANGEHvar? + | QCHANGEHvar? QCHANGESubject/þf QCHANGEHvernigLet + | QCHANGEHvar? QCHANGEHvernigLet QCHANGESubject/þf + | QCHANGEHvernigLet QCHANGESubject/þf QCHANGEHvar? + | QCHANGEHvernigLet QCHANGEHvar? QCHANGESubject/þf + +QCHANGETurnOnRest -> + QCHANGETurnOnLightsRest + | QCHANGEAHverju QCHANGEHvar? + | QCHANGEHvar? QCHANGEAHverju + +QCHANGETurnOnLightsRest -> + QCHANGELightSubject/þf QCHANGEHvar? + | QCHANGEHvar QCHANGELightSubject/þf? + +# Would be good to add "slökktu á rauða litnum" functionality +QCHANGETurnOffRest -> + QCHANGETurnOffLightsRest + +QCHANGETurnOffLightsRest -> + QCHANGELightSubject/þf QCHANGEHvar? + | QCHANGEHvar QCHANGELightSubject/þf? + +# TODO: Make the subject categorization cleaner +QCHANGEIncreaseOrDecreaseRest -> + QCHANGELightSubject/þf QCHANGEHvar? + | QCHANGEBrightnessSubject/þf QCHANGEHvar? + +QCHANGESubject/fall -> + QCHANGESubjectOne/fall + | QCHANGESubjectTwo/fall + +# TODO: Decide whether LightSubject/þgf should be accepted +QCHANGESubjectOne/fall -> + QCHANGELightSubject/fall + | QCHANGEColorSubject/fall + | QCHANGEBrightnessSubject/fall + | QCHANGESceneSubject/fall + +QCHANGESubjectTwo/fall -> + QCHANGEGroupNameSubject/fall # á bara að styðja "gerðu eldhúsið rautt", "gerðu eldhúsið rómó" "gerðu eldhúsið bjartara", t.d. + +QCHANGEHvar -> + QCHANGELocationPreposition QCHANGEGroupName/þgf + +QCHANGEHvernigMake -> + QCHANGEAnnadAndlag # gerðu litinn rauðan í eldhúsinu EÐA gerðu birtuna meiri í eldhúsinu + | QCHANGEAdHverju # gerðu litinn að rauðum í eldhúsinu + | QCHANGEThannigAd + +QCHANGEHvernigSet -> + QCHANGEAHvad + | QCHANGEThannigAd + +QCHANGEHvernigChange -> + QCHANGEIHvad + | QCHANGEThannigAd + +QCHANGEHvernigLet -> + QCHANGEBecome QCHANGESomethingOrSomehow + | QCHANGEBe QCHANGESomehow + +QCHANGEThannigAd -> + "þannig" "að"? pfn_nf QCHANGEBeOrBecomeSubjunctive QCHANGEAnnadAndlag + +# I think these verbs only appear in these forms. +# In which case these terminals should be deleted and a direct reference should be made in the relevant non-terminals. +QCHANGEBe -> + "vera" + +QCHANGEBecome -> + "verða" + +QCHANGEBeOrBecomeSubjunctive -> + "verði" + | "sé" + +QCHANGELightSubject/fall -> + QCHANGELight/fall + +QCHANGEColorSubject/fall -> + QCHANGEColorWord/fall QCHANGELight/ef? + | QCHANGEColorWord/fall "á" QCHANGELight/þgf + +QCHANGEBrightnessSubject/fall -> + QCHANGEBrightnessWord/fall QCHANGELight/ef? + | QCHANGEBrightnessWord/fall "á" QCHANGELight/þgf + +QCHANGESceneSubject/fall -> + QCHANGESceneWord/fall + +QCHANGEGroupNameSubject/fall -> + QCHANGEGroupName/fall + +QCHANGELocationPreposition -> + QCHANGELocationPrepositionFirstPart? QCHANGELocationPrepositionSecondPart + +# The latter proverbs are grammatically incorrect, but common errors, both in speech and transcription. +# The list provided is taken from StefnuAtv in Greynir.grammar. That includes "aftur:ao", which is not applicable here. +QCHANGELocationPrepositionFirstPart -> + StaðarAtv + | "fram:ao" + | "inn:ao" + | "niður:ao" + | "upp:ao" + | "út:ao" + +QCHANGELocationPrepositionSecondPart -> + "á" | "í" + +QCHANGEGroupName/fall -> + no/fall + +QCHANGELightName/fall -> + no/fall + +QCHANGEColorName -> + {" | ".join(f"'{color}:lo'" for color in _COLORS.keys())} + +QCHANGESceneName -> + no + | lo + +QCHANGEAnnadAndlag -> + QCHANGENewSetting/nf + | QCHANGESpyrjaHuldu/nf + +QCHANGEAdHverju -> + "að" QCHANGENewSetting/þgf + +QCHANGEAHvad -> + "á" QCHANGENewSetting/þf + +QCHANGEIHvad -> + "í" QCHANGENewSetting/þf + +QCHANGEAHverju -> + "á" QCHANGELight/þgf + | "á" QCHANGENewSetting/þgf + +QCHANGESomethingOrSomehow -> + QCHANGEAnnadAndlag + | QCHANGEAdHverju + +QCHANGESomehow -> + QCHANGEAnnadAndlag + | QCHANGEThannigAd + +QCHANGELight/fall -> + QCHANGELightName/fall + | QCHANGELightWord/fall + +# Should 'birta' be included +QCHANGELightWord/fall -> + 'ljós'/fall + | 'lýsing'/fall + | 'birta'/fall + | 'Birta'/fall + +QCHANGEColorWord/fall -> + 'litur'/fall + | 'litblær'/fall + | 'blær'/fall + +QCHANGEBrightnessWords/fall -> + 'bjartur'/fall + | QCHANGEBrightnessWord/fall + +QCHANGEBrightnessWord/fall -> + 'birta'/fall + | 'Birta'/fall + | 'birtustig'/fall + +QCHANGESceneWord/fall -> + 'sena'/fall + | 'stemning'/fall + | 'stemming'/fall + | 'stemmning'/fall + +# Need to ask Hulda how this works. +QCHANGESpyrjaHuldu/fall -> + # QCHANGEHuldaColor/fall + QCHANGEHuldaBrightness/fall + # | QCHANGEHuldaScene/fall + +# Do I need a "new light state" non-terminal? +QCHANGENewSetting/fall -> + QCHANGENewColor/fall + | QCHANGENewBrightness/fall + | QCHANGENewScene/fall + +# Missing "meira dimmt" +QCHANGEHuldaBrightness/fall -> + QCHANGEMoreBrighterOrHigher/fall QCHANGEBrightnessWords/fall? + | QCHANGELessDarkerOrLower/fall QCHANGEBrightnessWords/fall? + +#Unsure about whether to include /fall after QCHANGEColorName +QCHANGENewColor/fall -> + QCHANGEColorWord/fall QCHANGEColorName + | QCHANGEColorName QCHANGEColorWord/fall? + +QCHANGENewBrightness/fall -> + 'sá'/fall? QCHANGEBrightestOrDarkest/fall + | QCHANGEBrightestOrDarkest/fall QCHANGEBrightnessOrSettingWord/fall + +QCHANGENewScene/fall -> + QCHANGESceneWord/fall QCHANGESceneName + | QCHANGESceneName QCHANGESceneWord/fall? + +QCHANGEMoreBrighterOrHigher/fall -> + 'mikill:lo'_mst/fall + | 'bjartur:lo'_mst/fall + | 'ljós:lo'_mst/fall + | 'hár:lo'_mst/fall + +QCHANGELessDarkerOrLower/fall -> + 'lítill:lo'_mst/fall + | 'dökkur:lo'_mst/fall + | 'dimmur:lo'_mst/fall + | 'lágur:lo'_mst/fall + +QCHANGEBrightestOrDarkest/fall -> + QCHANGEBrightest/fall + | QCHANGEDarkest/fall + +QCHANGEBrightest/fall -> + 'bjartur:lo'_evb + | 'bjartur:lo'_esb + | 'ljós:lo'_evb + | 'ljós:lo'_esb + +QCHANGEDarkest/fall -> + 'dimmur:lo'_evb + | 'dimmur:lo'_esb + | 'dökkur:lo'_evb + | 'dökkur:lo'_esb + +QCHANGEBrightnessOrSettingWord/fall -> + QCHANGEBrightnessWord/fall + | QCHANGESettingWord/fall + +QCHANGESettingWord/fall -> + 'stilling'/fall + +""" + + +def QCHANGEColorWord(node: Node, params: QueryStateDict, result: Result) -> None: + result.changing_color = True + + +def QCHANGESceneWord(node: Node, params: QueryStateDict, result: Result) -> None: + result.changing_scene = True + + +def QCHANGEBrightnessWord(node: Node, params: QueryStateDict, result: Result) -> None: + result.changing_brightness = True + + +def QCHANGEQuery(node: Node, params: QueryStateDict, result: Result) -> None: + result.qtype = _IoT_QTYPE + + +def QCHANGETurnOnLightsRest(node: Node, params: QueryStateDict, result: Result) -> None: + result.action = "turn_on" + if "hue_obj" not in result: + result["hue_obj"] = {"on": True} + else: + result["hue_obj"]["on"] = True + + +def QCHANGETurnOffLightsRest(node: Node, params: QueryStateDict, result: Result) -> None: + result.action = "turn_off" + if "hue_obj" not in result: + result["hue_obj"] = {"on": False} + else: + result["hue_obj"]["on"] = False + + +def QCHANGENewColor(node: Node, params: QueryStateDict, result: Result) -> None: + result.action = "set_color" + print(result.color_name) + color_hue = _COLORS.get(result.color_name, None) + print(color_hue) + if color_hue is not None: + if "hue_obj" not in result: + result["hue_obj"] = {"on": True, "hue": int(color_hue)} + else: + result["hue_obj"]["hue"] = int(color_hue) + result["hue_obj"]["on"] = True + + +def QCHANGEMoreBrighterOrHigher( + node: Node, params: QueryStateDict, result: Result +) -> None: + result.action = "increase_brightness" + if "hue_obj" not in result: + result["hue_obj"] = {"on": True, "bri_inc": 64} + else: + result["hue_obj"]["bri_inc"] = 64 + result["hue_obj"]["on"] = True + + +def QCHANGELessDarkerOrLower(node: Node, params: QueryStateDict, result: Result) -> None: + result.action = "decrease_brightness" + if "hue_obj" not in result: + result["hue_obj"] = {"bri_inc": -64} + else: + result["hue_obj"]["bri_inc"] = -64 + + +def QCHANGEIncreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: + result.action = "increase_brightness" + if "hue_obj" not in result: + result["hue_obj"] = {"on": True, "bri_inc": 64} + else: + result["hue_obj"]["bri_inc"] = 64 + result["hue_obj"]["on"] = True + + +def QCHANGEDecreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: + result.action = "decrease_brightness" + if "hue_obj" not in result: + result["hue_obj"] = {"bri_inc": -64} + else: + result["hue_obj"]["bri_inc"] = -64 + + +def QCHANGEBrightest(node: Node, params: QueryStateDict, result: Result) -> None: + result.action = "decrease_brightness" + if "hue_obj" not in result: + result["hue_obj"] = {"bri": 255} + else: + result["hue_obj"]["bri"] = 255 + + +def QCHANGEDarkest(node: Node, params: QueryStateDict, result: Result) -> None: + result.action = "decrease_brightness" + if "hue_obj" not in result: + result["hue_obj"] = {"bri": 0} + else: + result["hue_obj"]["bri"] = 0 + + +def QCHANGENewScene(node: Node, params: QueryStateDict, result: Result) -> None: + result.action = "set_scene" + scene_name = result.get("scene_name", None) + print(scene_name) + if scene_name is not None: + if "hue_obj" not in result: + result["hue_obj"] = {"on": True, "scene": scene_name} + else: + result["hue_obj"]["scene"] = scene_name + result["hue_obj"]["on"] = True + + +def QCHANGEColorName(node: Node, params: QueryStateDict, result: Result) -> None: + result["color_name"] = ( + node.first_child(lambda x: True).string_self().strip("'").split(":")[0] ) - payload = {} - headers = {"Content-Type": "application/json", "Authorization": f"Bearer {token}"} - response = requests.request("POST", url, headers=headers, data=payload) +def QCHANGESceneName(node: Node, params: QueryStateDict, result: Result) -> None: + result["scene_name"] = result._indefinite + print(result.get("scene_name", None)) + + +def QCHANGEGroupName(node: Node, params: QueryStateDict, result: Result) -> None: + result["group_name"] = result._indefinite + + +def QCHANGELightName(node: Node, params: QueryStateDict, result: Result) -> None: + result["light_name"] = result._indefinite + + +# Convert color name into hue +# Taken from home.py +_COLOR_NAME_TO_CIE: Mapping[str, float] = { + "gulur": 60 * 65535 / 360, + "grænn": 120 * 65535 / 360, + "ljósblár": 180 * 65535 / 360, + "blár": 240 * 65535 / 360, + "bleikur": 300 * 65535 / 360, + "rauður": 360 * 65535 / 360, + # "Rauð": 360 * 65535 / 360, +} + + +def sentence(state: QueryStateDict, result: Result) -> None: + """Called when sentence processing is complete""" + q: Query = state["query"] + changing_color = result.get("changing_color", False) + changing_scene = result.get("changing_scene", False) + changing_brightness = result.get("changing_brightness", False) + print("error?", sum((changing_color, changing_scene, changing_brightness)) > 1) + if ( + sum((changing_color, changing_scene, changing_brightness)) > 1 + or "qtype" not in result + ): + q.set_error("E_QUERY_NOT_UNDERSTOOD") + return + + q.set_qtype(result.qtype) + + smartdevice_type = "smartlights" + client_id = str(q.client_id) + print("client_id:", client_id) + + # Fetch relevant data from the device_data table to perform an action on the lights + device_data = cast(Optional[DeviceData], q.client_data(smartdevice_type)) + print("location :", q.location) + print("device data :", device_data) + + selected_light: Optional[str] = None + print("selected light:", selected_light) + hue_credentials: Optional[Dict[str, str]] = None + + if device_data is not None and smartdevice_type in device_data: + dev = device_data[smartdevice_type] + assert dev is not None + selected_light = dev.get("selected_light") + hue_credentials = dev.get("philips_hue") + bridge_ip = hue_credentials.get("ipAddress") + username = hue_credentials.get("username") + + if not device_data or not hue_credentials: + answer = "Það vantar að tengja Philips Hub-inn." + q.set_answer(*gen_answer(answer)) + return + + # Successfully matched a query type + print("bridge_ip: ", bridge_ip) + print("username: ", username) + print("selected light :", selected_light) + print("hue credentials :", hue_credentials) + + try: + # kalla í javascripts stuff + light_or_group_name = result.get("light_name", result.get("group_name", "")) + color_name = result.get("color_name", "") + print("GROUP NAME:", light_or_group_name) + print("COLOR NAME:", color_name) + print(result.hue_obj) + q.set_answer( + *gen_answer( + "ég var að kveikja ljósin! " + # + group_name + # + " " + # + color_name + # + " " + # + result.action + # + " " + # + str(result.hue_obj.get("hue", "enginn litur")) + ) + ) + js = ( + read_jsfile("IoT_Embla/fuse.js") + + f"var BRIDGE_IP = '{bridge_ip}';var USERNAME = '{username}';" + + read_jsfile("IoT_Embla/Philips_Hue/fuse_search.js") + + read_jsfile("IoT_Embla/Philips_Hue/lights.js") + + read_jsfile("IoT_Embla/Philips_Hue/set_lights.js") + ) + js += f"setLights('{light_or_group_name}', '{json.dumps(result.hue_obj)}');" + q.set_command(js) + except Exception as e: + logging.warning("Exception while processing random query: {0}".format(e)) + q.set_error("E_EXCEPTION: {0}".format(e)) + raise - return response + # f"var BRIDGE_IP = '192.168.1.68';var USERNAME = 'p3obluiXT13IbHMpp4X63ZvZnpNRdbqqMt723gy2';" diff --git a/routes/sonos.py b/routes/sonos.py new file mode 100644 index 00000000..dce575b9 --- /dev/null +++ b/routes/sonos.py @@ -0,0 +1,113 @@ +""" + + Greynir: Natural language processing for Icelandic + + Copyright (C) 2022 Miðeind ehf. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see http://www.gnu.org/licenses/. + + + API routes + Note: All routes ending with .api are configured not to be cached by nginx + +""" + +import requests + + +def getHouseholds(token): + """ + Returns the list of households of the user + """ + url = "https://api.ws.sonos.com/control/api/v1/households" + + payload = {} + headers = {"Authorization": f"Bearer {token}"} + + response = requests.request("GET", url, headers=headers, data=payload) + + return response + + +def getGroups(houshold_id, token): + """ + Returns the list of groups of the user + """ + url = "https://api.ws.sonos.com/control/api/v1/households/{household_id}/groups" + + payload = {} + headers = {"Authorization": f"Bearer {token}"} + + response = requests.request("GET", url, headers=headers, data=payload) + + return response + + +def createToken(code, sonos_encoded_credentials): + """ + Creates a token given a code + """ + url = f"https://api.sonos.com/login/v3/oauth/access?grant_type=authorization_code&code={code}&redirect_uri=http://localhost:5000/connect_sonos.api" + + payload = {} + headers = { + "Authorization": f"Basic {sonos_encoded_credentials}", + "Cookie": "JSESSIONID=F710019AF0A3B7126A8702577C883B5F; AWSELB=69BFEFC914A689BF6DC8E4652748D7B501ED60290D5EA56F2E543ABD7CF357A5F65186AEBCFB059E28075D83A700FD504C030A53CC28683B515BE3DCA3CC587AFAF606E171; AWSELBCORS=69BFEFC914A689BF6DC8E4652748D7B501ED60290D5EA56F2E543ABD7CF357A5F65186AEBCFB059E28075D83A700FD504C030A53CC28683B515BE3DCA3CC587AFAF606E171", + } + + response = requests.request("POST", url, headers=headers, data=payload) + + return response + + +def togglePlayPause(group_id, token): + """ + Toggles the play/pause of a group + """ + url = ( + f"https://api.ws.sonos.com/control/api/v1/groups/{group_id}/playback/playPause" + ) + + payload = {} + headers = {"Content-Type": "application/json", "Authorization": f"Bearer {token}"} + + response = requests.request("POST", url, headers=headers, data=payload) + + return response + + +def audioClip(audioclip_url, player_id, token): + """ + Plays an audioclip from link to .mp3 file + """ + import requests + import json + + url = f"https://api.ws.sonos.com/control/api/v1/players/{player_id}/audioClip" + + payload = json.dumps( + { + "name": "Embla", + "appId": "com.acme.app", + "streamUrl": f"{audioclip_url}", + "volume": 30, + "priority": "HIGH", + "clipType": "CUSTOM", + } + ) + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {token}", + } + + response = requests.request("POST", url, headers=headers, data=payload) From d884bb0b4a57931fbf4adc105e41864bf88a83f0 Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Thu, 23 Jun 2022 18:54:40 +0000 Subject: [PATCH 091/371] rudimentary grammar --- queries/iot_speakers.py | 651 ++++++++++++++++------------------------ 1 file changed, 252 insertions(+), 399 deletions(-) diff --git a/queries/iot_speakers.py b/queries/iot_speakers.py index f0348115..27b01ae2 100644 --- a/queries/iot_speakers.py +++ b/queries/iot_speakers.py @@ -46,27 +46,10 @@ from tree import Result, Node -class SmartLights(TypedDict): - selected_light: str - philips_hue: Dict[str, str] - - -class DeviceData(TypedDict): - smartlights: SmartLights - - _IoT_QTYPE = "IoT" TOPIC_LEMMAS = [ - "ljós", - "kveikja", - "litur", - "birta", - "hækka", - "stemmning", - "sena", - "stemming", - "stemning", + "tónlist", ] @@ -75,36 +58,16 @@ def help_text(lemma: str) -> str: one of the above lemmas is found in it""" return "Ég skil þig ef þú segir til dæmis: {0}.".format( random.choice( - ( - "Kveiktu á ljósunum inni í eldhúsi", - "Slökktu á leslampanum", - "Breyttu lit lýsingarinnar í stofunni í bláan", - "Gerðu ljósið í borðstofunni bjartara", - "Stilltu á bjartasta niðri í kjallara", - ) + ("Hækkaðu í tónlistinni", "Kveiktu á tónlist", "Láttu vera tónlist") ) ) -_COLORS = { - "gulur": 60 * 65535 / 360, - "rauður": 360 * 65535 / 360, - "grænn": 120 * 65535 / 360, - "blár": 240 * 65535 / 360, - "ljósblár": 180 * 65535 / 360, - "bleikur": 300 * 65535 / 360, - "hvítur": [], - "fjólublár": [], - "brúnn": [], - "appelsínugulur": [], -} - - # This module wants to handle parse trees for queries HANDLE_TREE = True # The grammar nonterminals this module wants to handle -QUERY_NONTERMINALS = {"QCHANGE"} +QUERY_NONTERMINALS = {"QIoTSpeaker"} # The context-free grammar for the queries recognized by this plug-in module # GRAMMAR = read_grammar_file("iot_hue") @@ -115,75 +78,77 @@ def help_text(lemma: str) -> str: /ef = ef Query → - QCHANGE '?'? + QIoTSpeaker '?'? -QCHANGE → - QCHANGEQuery +QIoTSpeaker → + QIoTSpeakerQuery -QCHANGEQuery -> - QCHANGEMakeVerb QCHANGEMakeRest - | QCHANGESetVerb QCHANGESetRest - | QCHANGEChangeVerb QCHANGEChangeRest - | QCHANGELetVerb QCHANGELetRest - | QCHANGETurnOnVerb QCHANGETurnOnRest - | QCHANGETurnOffVerb QCHANGETurnOffRest - | QCHANGEIncreaseOrDecreaseVerb QCHANGEIncreaseOrDecreaseRest +QIoTSpeakerQuery -> + QIoTSpeakerMakeVerb QIoTSpeakerMakeRest + | QIoTSpeakerSetVerb QIoTSpeakerSetRest + | QIoTSpeakerChangeVerb QIoTSpeakerChangeRest + | QIoTSpeakerLetVerb QIoTSpeakerLetRest + | QIoTSpeakerTurnOnVerb QIoTSpeakerTurnOnRest + | QIoTSpeakerTurnOffVerb QIoTSpeakerTurnOffRest + | QIoTSpeakerIncreaseOrDecreaseVerb QIoTSpeakerIncreaseOrDecreaseRest -QCHANGEMakeVerb -> +QIoTSpeakerMakeVerb -> 'gera:so'_bh -QCHANGESetVerb -> +QIoTSpeakerSetVerb -> 'setja:so'_bh | 'stilla:so'_bh -QCHANGEChangeVerb -> +QIoTSpeakerChangeVerb -> 'breyta:so'_bh -QCHANGELetVerb -> +QIoTSpeakerLetVerb -> 'láta:so'_bh -QCHANGETurnOnVerb -> +QIoTSpeakerTurnOnVerb -> 'kveikja:so'_bh -QCHANGETurnOffVerb -> +QIoTSpeakerTurnOffVerb -> 'slökkva:so'_bh -QCHANGEIncreaseOrDecreaseVerb -> - QCHANGEIncreaseVerb - | QCHANGEDecreaseVerb +QIoTSpeakerIncreaseOrDecreaseVerb -> + QIoTSpeakerIncreaseVerb + | QIoTSpeakerDecreaseVerb -QCHANGEIncreaseVerb -> +QIoTSpeakerIncreaseVerb -> 'hækka:so'_bh | 'auka:so'_bh -QCHANGEDecreaseVerb -> +QIoTSpeakerDecreaseVerb -> 'lækka:so'_bh | 'minnka:so'_bh QCHANGEMakeRest -> - QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigMake - | QCHANGESubject/þf QCHANGEHvernigMake QCHANGEHvar? - | QCHANGEHvar? QCHANGESubject/þf QCHANGEHvernigMake - | QCHANGEHvar? QCHANGEHvernigMake QCHANGESubject/þf - | QCHANGEHvernigMake QCHANGESubject/þf QCHANGEHvar? - | QCHANGEHvernigMake QCHANGEHvar? QCHANGESubject/þf + # QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigMake + # | QCHANGESubject/þf QCHANGEHvernigMake QCHANGEHvar? + # | QCHANGEHvar? QCHANGESubject/þf QCHANGEHvernigMake + # | QCHANGEHvar? QCHANGEHvernigMake QCHANGESubject/þf + # | QCHANGEHvernigMake QCHANGESubject/þf QCHANGEHvar? + # | QCHANGEHvernigMake QCHANGEHvar? QCHANGESubject/þf + QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? # TODO: Add support for "stilltu rauðan lit á ljósið í eldhúsinu" QCHANGESetRest -> - QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigSet - | QCHANGESubject/þf QCHANGEHvernigSet QCHANGEHvar? - | QCHANGEHvar? QCHANGESubject/þf QCHANGEHvernigSet - | QCHANGEHvar? QCHANGEHvernigSet QCHANGESubject/þf - | QCHANGEHvernigSet QCHANGESubject/þf QCHANGEHvar? - | QCHANGEHvernigSet QCHANGEHvar? QCHANGESubject/þf + # QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigSet + # | QCHANGESubject/þf QCHANGEHvernigSet QCHANGEHvar? + # | QCHANGEHvar? QCHANGESubject/þf QCHANGEHvernigSet + # | QCHANGEHvar? QCHANGEHvernigSet QCHANGESubject/þf + # | QCHANGEHvernigSet QCHANGESubject/þf QCHANGEHvar? + # | QCHANGEHvernigSet QCHANGEHvar? QCHANGESubject/þf + "á" QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? QCHANGEChangeRest -> - QCHANGESubjectOne/þgf QCHANGEHvar? QCHANGEHvernigChange - | QCHANGESubjectOne/þgf QCHANGEHvernigChange QCHANGEHvar? - | QCHANGEHvar? QCHANGESubjectOne/þgf QCHANGEHvernigChange - | QCHANGEHvar? QCHANGEHvernigChange QCHANGESubjectOne/þgf - | QCHANGEHvernigChange QCHANGESubjectOne/þgf QCHANGEHvar? - | QCHANGEHvernigChange QCHANGEHvar? QCHANGESubjectOne/þgf + # QCHANGESubjectOne/þgf QCHANGEHvar? QCHANGEHvernigChange + # | QCHANGESubjectOne/þgf QCHANGEHvernigChange QCHANGEHvar? + # | QCHANGEHvar? QCHANGESubjectOne/þgf QCHANGEHvernigChange + # | QCHANGEHvar? QCHANGEHvernigChange QCHANGESubjectOne/þgf + # | QCHANGEHvernigChange QCHANGESubjectOne/þgf QCHANGEHvar? + # | QCHANGEHvernigChange QCHANGEHvar? QCHANGESubjectOne/þgf QCHANGELetRest -> QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigLet @@ -192,101 +157,109 @@ def help_text(lemma: str) -> str: | QCHANGEHvar? QCHANGEHvernigLet QCHANGESubject/þf | QCHANGEHvernigLet QCHANGESubject/þf QCHANGEHvar? | QCHANGEHvernigLet QCHANGEHvar? QCHANGESubject/þf + "vera" QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? QCHANGETurnOnRest -> - QCHANGETurnOnLightsRest - | QCHANGEAHverju QCHANGEHvar? - | QCHANGEHvar? QCHANGEAHverju + # QCHANGETurnOnLightsRest + # | QCHANGEAHverju QCHANGEHvar? + # | QCHANGEHvar? QCHANGEAHverju + "á" QIoTSpeakerMusicWord/þgf QIoTSpeakerHvar? -QCHANGETurnOnLightsRest -> - QCHANGELightSubject/þf QCHANGEHvar? - | QCHANGEHvar QCHANGELightSubject/þf? +# QCHANGETurnOnLightsRest -> +# QCHANGELightSubject/þf QCHANGEHvar? +# | QCHANGEHvar QCHANGELightSubject/þf? # Would be good to add "slökktu á rauða litnum" functionality QCHANGETurnOffRest -> - QCHANGETurnOffLightsRest + # QCHANGETurnOffLightsRest + "á" QIoTSpeakerMusicWord/þgf QIoTSpeakerHvar? -QCHANGETurnOffLightsRest -> - QCHANGELightSubject/þf QCHANGEHvar? - | QCHANGEHvar QCHANGELightSubject/þf? +# QCHANGETurnOffLightsRest -> +# QCHANGELightSubject/þf QCHANGEHvar? +# | QCHANGEHvar QCHANGELightSubject/þf? # TODO: Make the subject categorization cleaner QCHANGEIncreaseOrDecreaseRest -> - QCHANGELightSubject/þf QCHANGEHvar? - | QCHANGEBrightnessSubject/þf QCHANGEHvar? + # QCHANGELightSubject/þf QCHANGEHvar? + # | QCHANGEBrightnessSubject/þf QCHANGEHvar? + QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? + | "í" QIoTSpeakerMusicWord/þgf QIoTSpeakerHvar? + +# QCHANGESubject/fall -> +# QCHANGESubjectOne/fall +# | QCHANGESubjectTwo/fall -QCHANGESubject/fall -> - QCHANGESubjectOne/fall - | QCHANGESubjectTwo/fall +QIoTMusicWord -> + 'tónlist'/fall -# TODO: Decide whether LightSubject/þgf should be accepted -QCHANGESubjectOne/fall -> - QCHANGELightSubject/fall - | QCHANGEColorSubject/fall - | QCHANGEBrightnessSubject/fall - | QCHANGESceneSubject/fall +# # TODO: Decide whether LightSubject/þgf should be accepted +# QCHANGESubjectOne/fall -> +# QCHANGELightSubject/fall +# | QCHANGEColorSubject/fall +# | QCHANGEBrightnessSubject/fall +# | QCHANGESceneSubject/fall -QCHANGESubjectTwo/fall -> - QCHANGEGroupNameSubject/fall # á bara að styðja "gerðu eldhúsið rautt", "gerðu eldhúsið rómó" "gerðu eldhúsið bjartara", t.d. +# QCHANGESubjectTwo/fall -> +# QCHANGEGroupNameSubject/fall # á bara að styðja "gerðu eldhúsið rautt", "gerðu eldhúsið rómó" "gerðu eldhúsið bjartara", t.d. -QCHANGEHvar -> - QCHANGELocationPreposition QCHANGEGroupName/þgf +QIoTSpeakerHvar -> + QIoTSpeakerLocationPreposition QIoTSpeakerGroupName/þgf -QCHANGEHvernigMake -> - QCHANGEAnnadAndlag # gerðu litinn rauðan í eldhúsinu EÐA gerðu birtuna meiri í eldhúsinu - | QCHANGEAdHverju # gerðu litinn að rauðum í eldhúsinu - | QCHANGEThannigAd +# QCHANGEHvernigMake -> +# QCHANGEAnnadAndlag # gerðu litinn rauðan í eldhúsinu EÐA gerðu birtuna meiri í eldhúsinu +# | QCHANGEAdHverju # gerðu litinn að rauðum í eldhúsinu +# | QCHANGEThannigAd -QCHANGEHvernigSet -> - QCHANGEAHvad - | QCHANGEThannigAd +# QCHANGEHvernigSet -> +# QCHANGEAHvad +# | QCHANGEThannigAd -QCHANGEHvernigChange -> - QCHANGEIHvad - | QCHANGEThannigAd +# QCHANGEHvernigChange -> +# QCHANGEIHvad +# | QCHANGEThannigAd -QCHANGEHvernigLet -> - QCHANGEBecome QCHANGESomethingOrSomehow - | QCHANGEBe QCHANGESomehow +# QCHANGEHvernigLet -> +# QCHANGEBecome QCHANGESomethingOrSomehow +# | QCHANGEBe QCHANGESomehow -QCHANGEThannigAd -> - "þannig" "að"? pfn_nf QCHANGEBeOrBecomeSubjunctive QCHANGEAnnadAndlag +# QCHANGEThannigAd -> +# "þannig" "að"? pfn_nf QCHANGEBeOrBecomeSubjunctive QCHANGEAnnadAndlag # I think these verbs only appear in these forms. # In which case these terminals should be deleted and a direct reference should be made in the relevant non-terminals. -QCHANGEBe -> - "vera" +# QCHANGEBe -> +# "vera" -QCHANGEBecome -> - "verða" +# QCHANGEBecome -> +# "verða" -QCHANGEBeOrBecomeSubjunctive -> - "verði" - | "sé" +# QCHANGEBeOrBecomeSubjunctive -> +# "verði" +# | "sé" -QCHANGELightSubject/fall -> - QCHANGELight/fall +# QCHANGELightSubject/fall -> +# QCHANGELight/fall -QCHANGEColorSubject/fall -> - QCHANGEColorWord/fall QCHANGELight/ef? - | QCHANGEColorWord/fall "á" QCHANGELight/þgf +# QCHANGEColorSubject/fall -> +# QCHANGEColorWord/fall QCHANGELight/ef? +# | QCHANGEColorWord/fall "á" QCHANGELight/þgf -QCHANGEBrightnessSubject/fall -> - QCHANGEBrightnessWord/fall QCHANGELight/ef? - | QCHANGEBrightnessWord/fall "á" QCHANGELight/þgf +# QCHANGEBrightnessSubject/fall -> +# QCHANGEBrightnessWord/fall QCHANGELight/ef? +# | QCHANGEBrightnessWord/fall "á" QCHANGELight/þgf -QCHANGESceneSubject/fall -> - QCHANGESceneWord/fall +# QCHANGESceneSubject/fall -> +# QCHANGESceneWord/fall -QCHANGEGroupNameSubject/fall -> - QCHANGEGroupName/fall +# QCHANGEGroupNameSubject/fall -> +# QCHANGEGroupName/fall -QCHANGELocationPreposition -> - QCHANGELocationPrepositionFirstPart? QCHANGELocationPrepositionSecondPart +QIoTSpeakerLocationPreposition -> + QIoTSpeakerLocationPrepositionFirstPart? QIoTSpeakerLocationPrepositionSecondPart # The latter proverbs are grammatically incorrect, but common errors, both in speech and transcription. # The list provided is taken from StefnuAtv in Greynir.grammar. That includes "aftur:ao", which is not applicable here. -QCHANGELocationPrepositionFirstPart -> +QIoTSpeakerLocationPrepositionFirstPart -> StaðarAtv | "fram:ao" | "inn:ao" @@ -294,195 +267,148 @@ def help_text(lemma: str) -> str: | "upp:ao" | "út:ao" -QCHANGELocationPrepositionSecondPart -> +QIoTSpeakerLocationPrepositionSecondPart -> "á" | "í" -QCHANGEGroupName/fall -> +QIoTSpeakerGroupName/fall -> no/fall -QCHANGELightName/fall -> - no/fall - -QCHANGEColorName -> - {" | ".join(f"'{color}:lo'" for color in _COLORS.keys())} - -QCHANGESceneName -> - no - | lo - -QCHANGEAnnadAndlag -> - QCHANGENewSetting/nf - | QCHANGESpyrjaHuldu/nf - -QCHANGEAdHverju -> - "að" QCHANGENewSetting/þgf - -QCHANGEAHvad -> - "á" QCHANGENewSetting/þf - -QCHANGEIHvad -> - "í" QCHANGENewSetting/þf - -QCHANGEAHverju -> - "á" QCHANGELight/þgf - | "á" QCHANGENewSetting/þgf - -QCHANGESomethingOrSomehow -> - QCHANGEAnnadAndlag - | QCHANGEAdHverju - -QCHANGESomehow -> - QCHANGEAnnadAndlag - | QCHANGEThannigAd - -QCHANGELight/fall -> - QCHANGELightName/fall - | QCHANGELightWord/fall - -# Should 'birta' be included -QCHANGELightWord/fall -> - 'ljós'/fall - | 'lýsing'/fall - | 'birta'/fall - | 'Birta'/fall - -QCHANGEColorWord/fall -> - 'litur'/fall - | 'litblær'/fall - | 'blær'/fall - -QCHANGEBrightnessWords/fall -> - 'bjartur'/fall - | QCHANGEBrightnessWord/fall - -QCHANGEBrightnessWord/fall -> - 'birta'/fall - | 'Birta'/fall - | 'birtustig'/fall - -QCHANGESceneWord/fall -> - 'sena'/fall - | 'stemning'/fall - | 'stemming'/fall - | 'stemmning'/fall - -# Need to ask Hulda how this works. -QCHANGESpyrjaHuldu/fall -> - # QCHANGEHuldaColor/fall - QCHANGEHuldaBrightness/fall - # | QCHANGEHuldaScene/fall - -# Do I need a "new light state" non-terminal? -QCHANGENewSetting/fall -> - QCHANGENewColor/fall - | QCHANGENewBrightness/fall - | QCHANGENewScene/fall - -# Missing "meira dimmt" -QCHANGEHuldaBrightness/fall -> - QCHANGEMoreBrighterOrHigher/fall QCHANGEBrightnessWords/fall? - | QCHANGELessDarkerOrLower/fall QCHANGEBrightnessWords/fall? - -#Unsure about whether to include /fall after QCHANGEColorName -QCHANGENewColor/fall -> - QCHANGEColorWord/fall QCHANGEColorName - | QCHANGEColorName QCHANGEColorWord/fall? - -QCHANGENewBrightness/fall -> - 'sá'/fall? QCHANGEBrightestOrDarkest/fall - | QCHANGEBrightestOrDarkest/fall QCHANGEBrightnessOrSettingWord/fall - -QCHANGENewScene/fall -> - QCHANGESceneWord/fall QCHANGESceneName - | QCHANGESceneName QCHANGESceneWord/fall? - -QCHANGEMoreBrighterOrHigher/fall -> - 'mikill:lo'_mst/fall - | 'bjartur:lo'_mst/fall - | 'ljós:lo'_mst/fall - | 'hár:lo'_mst/fall - -QCHANGELessDarkerOrLower/fall -> - 'lítill:lo'_mst/fall - | 'dökkur:lo'_mst/fall - | 'dimmur:lo'_mst/fall - | 'lágur:lo'_mst/fall - -QCHANGEBrightestOrDarkest/fall -> - QCHANGEBrightest/fall - | QCHANGEDarkest/fall - -QCHANGEBrightest/fall -> - 'bjartur:lo'_evb - | 'bjartur:lo'_esb - | 'ljós:lo'_evb - | 'ljós:lo'_esb - -QCHANGEDarkest/fall -> - 'dimmur:lo'_evb - | 'dimmur:lo'_esb - | 'dökkur:lo'_evb - | 'dökkur:lo'_esb - -QCHANGEBrightnessOrSettingWord/fall -> - QCHANGEBrightnessWord/fall - | QCHANGESettingWord/fall - -QCHANGESettingWord/fall -> - 'stilling'/fall +# QCHANGELightName/fall -> +# no/fall + +# QCHANGEColorName -> +# {" | ".join(f"'{color}:lo'" for color in _COLORS.keys())} + +# QCHANGESceneName -> +# no +# | lo + +# QCHANGEAnnadAndlag -> +# QCHANGENewSetting/nf +# | QCHANGESpyrjaHuldu/nf + +# QCHANGEAdHverju -> +# "að" QCHANGENewSetting/þgf + +# QCHANGEAHvad -> +# "á" QCHANGENewSetting/þf + +# QCHANGEIHvad -> +# "í" QCHANGENewSetting/þf + +# QCHANGEAHverju -> +# "á" QCHANGELight/þgf +# | "á" QCHANGENewSetting/þgf + +# QCHANGESomethingOrSomehow -> +# QCHANGEAnnadAndlag +# | QCHANGEAdHverju + +# QCHANGESomehow -> +# QCHANGEAnnadAndlag +# | QCHANGEThannigAd + +# QCHANGELight/fall -> +# QCHANGELightName/fall +# | QCHANGELightWord/fall + +# # Should 'birta' be included +# QCHANGELightWord/fall -> +# 'ljós'/fall +# | 'lýsing'/fall +# | 'birta'/fall +# | 'Birta'/fall + +# QCHANGEColorWord/fall -> +# 'litur'/fall +# | 'litblær'/fall +# | 'blær'/fall + +# QCHANGEBrightnessWords/fall -> +# 'bjartur'/fall +# | QCHANGEBrightnessWord/fall + +# QCHANGEBrightnessWord/fall -> +# 'birta'/fall +# | 'Birta'/fall +# | 'birtustig'/fall + +# QCHANGESceneWord/fall -> +# 'sena'/fall +# | 'stemning'/fall +# | 'stemming'/fall +# | 'stemmning'/fall + +# # Need to ask Hulda how this works. +# QCHANGESpyrjaHuldu/fall -> +# # QCHANGEHuldaColor/fall +# QCHANGEHuldaBrightness/fall +# # | QCHANGEHuldaScene/fall + +# # Do I need a "new light state" non-terminal? +# QCHANGENewSetting/fall -> +# QCHANGENewColor/fall +# | QCHANGENewBrightness/fall +# | QCHANGENewScene/fall + +# # Missing "meira dimmt" +# QCHANGEHuldaBrightness/fall -> +# QCHANGEMoreBrighterOrHigher/fall QCHANGEBrightnessWords/fall? +# | QCHANGELessDarkerOrLower/fall QCHANGEBrightnessWords/fall? + +# #Unsure about whether to include /fall after QCHANGEColorName +# QCHANGENewColor/fall -> +# QCHANGEColorWord/fall QCHANGEColorName +# | QCHANGEColorName QCHANGEColorWord/fall? + +# QCHANGENewBrightness/fall -> +# 'sá'/fall? QCHANGEBrightestOrDarkest/fall +# | QCHANGEBrightestOrDarkest/fall QCHANGEBrightnessOrSettingWord/fall + +# QCHANGENewScene/fall -> +# QCHANGESceneWord/fall QCHANGESceneName +# | QCHANGESceneName QCHANGESceneWord/fall? + +# QCHANGEMoreBrighterOrHigher/fall -> +# 'mikill:lo'_mst/fall +# | 'bjartur:lo'_mst/fall +# | 'ljós:lo'_mst/fall +# | 'hár:lo'_mst/fall + +# QCHANGELessDarkerOrLower/fall -> +# 'lítill:lo'_mst/fall +# | 'dökkur:lo'_mst/fall +# | 'dimmur:lo'_mst/fall +# | 'lágur:lo'_mst/fall + +# QCHANGEBrightestOrDarkest/fall -> +# QCHANGEBrightest/fall +# | QCHANGEDarkest/fall + +# QCHANGEBrightest/fall -> +# 'bjartur:lo'_evb +# | 'bjartur:lo'_esb +# | 'ljós:lo'_evb +# | 'ljós:lo'_esb + +# QCHANGEDarkest/fall -> +# 'dimmur:lo'_evb +# | 'dimmur:lo'_esb +# | 'dökkur:lo'_evb +# | 'dökkur:lo'_esb + +# QCHANGEBrightnessOrSettingWord/fall -> +# QCHANGEBrightnessWord/fall +# | QCHANGESettingWord/fall + +# QCHANGESettingWord/fall -> +# 'stilling'/fall """ -def QCHANGEColorWord(node: Node, params: QueryStateDict, result: Result) -> None: - result.changing_color = True - - -def QCHANGESceneWord(node: Node, params: QueryStateDict, result: Result) -> None: - result.changing_scene = True - - -def QCHANGEBrightnessWord(node: Node, params: QueryStateDict, result: Result) -> None: - result.changing_brightness = True - - -def QCHANGEQuery(node: Node, params: QueryStateDict, result: Result) -> None: - result.qtype = _IoT_QTYPE - - -def QCHANGETurnOnLightsRest(node: Node, params: QueryStateDict, result: Result) -> None: - result.action = "turn_on" - if "hue_obj" not in result: - result["hue_obj"] = {"on": True} - else: - result["hue_obj"]["on"] = True - - -def QCHANGETurnOffLightsRest(node: Node, params: QueryStateDict, result: Result) -> None: - result.action = "turn_off" - if "hue_obj" not in result: - result["hue_obj"] = {"on": False} - else: - result["hue_obj"]["on"] = False - - -def QCHANGENewColor(node: Node, params: QueryStateDict, result: Result) -> None: - result.action = "set_color" - print(result.color_name) - color_hue = _COLORS.get(result.color_name, None) - print(color_hue) - if color_hue is not None: - if "hue_obj" not in result: - result["hue_obj"] = {"on": True, "hue": int(color_hue)} - else: - result["hue_obj"]["hue"] = int(color_hue) - result["hue_obj"]["on"] = True - - -def QCHANGEMoreBrighterOrHigher( - node: Node, params: QueryStateDict, result: Result -) -> None: - result.action = "increase_brightness" +def QIoTSpeakerIncreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: + result.action = "increase_volume" if "hue_obj" not in result: result["hue_obj"] = {"on": True, "bri_inc": 64} else: @@ -490,91 +416,18 @@ def QCHANGEMoreBrighterOrHigher( result["hue_obj"]["on"] = True -def QCHANGELessDarkerOrLower(node: Node, params: QueryStateDict, result: Result) -> None: - result.action = "decrease_brightness" +def QIoTSpeakerDecreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: + result.action = "decrease_volume" if "hue_obj" not in result: result["hue_obj"] = {"bri_inc": -64} else: result["hue_obj"]["bri_inc"] = -64 -def QCHANGEIncreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: - result.action = "increase_brightness" - if "hue_obj" not in result: - result["hue_obj"] = {"on": True, "bri_inc": 64} - else: - result["hue_obj"]["bri_inc"] = 64 - result["hue_obj"]["on"] = True - - -def QCHANGEDecreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: - result.action = "decrease_brightness" - if "hue_obj" not in result: - result["hue_obj"] = {"bri_inc": -64} - else: - result["hue_obj"]["bri_inc"] = -64 - - -def QCHANGEBrightest(node: Node, params: QueryStateDict, result: Result) -> None: - result.action = "decrease_brightness" - if "hue_obj" not in result: - result["hue_obj"] = {"bri": 255} - else: - result["hue_obj"]["bri"] = 255 - - -def QCHANGEDarkest(node: Node, params: QueryStateDict, result: Result) -> None: - result.action = "decrease_brightness" - if "hue_obj" not in result: - result["hue_obj"] = {"bri": 0} - else: - result["hue_obj"]["bri"] = 0 - - -def QCHANGENewScene(node: Node, params: QueryStateDict, result: Result) -> None: - result.action = "set_scene" - scene_name = result.get("scene_name", None) - print(scene_name) - if scene_name is not None: - if "hue_obj" not in result: - result["hue_obj"] = {"on": True, "scene": scene_name} - else: - result["hue_obj"]["scene"] = scene_name - result["hue_obj"]["on"] = True - - -def QCHANGEColorName(node: Node, params: QueryStateDict, result: Result) -> None: - result["color_name"] = ( - node.first_child(lambda x: True).string_self().strip("'").split(":")[0] - ) - - -def QCHANGESceneName(node: Node, params: QueryStateDict, result: Result) -> None: - result["scene_name"] = result._indefinite - print(result.get("scene_name", None)) - - -def QCHANGEGroupName(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTSpeakerGroupName(node: Node, params: QueryStateDict, result: Result) -> None: result["group_name"] = result._indefinite -def QCHANGELightName(node: Node, params: QueryStateDict, result: Result) -> None: - result["light_name"] = result._indefinite - - -# Convert color name into hue -# Taken from home.py -_COLOR_NAME_TO_CIE: Mapping[str, float] = { - "gulur": 60 * 65535 / 360, - "grænn": 120 * 65535 / 360, - "ljósblár": 180 * 65535 / 360, - "blár": 240 * 65535 / 360, - "bleikur": 300 * 65535 / 360, - "rauður": 360 * 65535 / 360, - # "Rauð": 360 * 65535 / 360, -} - - def sentence(state: QueryStateDict, result: Result) -> None: """Called when sentence processing is complete""" q: Query = state["query"] From 209bb92051c99cd8a10ae9b6ce6e9fb4bd0b8428 Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Thu, 23 Jun 2022 19:45:51 +0000 Subject: [PATCH 092/371] create token now stores user sonos info --- queries/iot_connect.py | 177 ++++++++++++++++++++++++++++++---------- queries/iot_speakers.py | 18 +--- routes/__init__.py | 1 + routes/sonos.py | 12 +-- 4 files changed, 141 insertions(+), 67 deletions(-) diff --git a/queries/iot_connect.py b/queries/iot_connect.py index 5bd4effe..7426bbc2 100644 --- a/queries/iot_connect.py +++ b/queries/iot_connect.py @@ -126,33 +126,6 @@ def QIoTCreateSpeakerToken(node: Node, params: QueryStateDict, result: Result) - result.action = "create_speaker_token" -def audioClip(audioclip_url): - """ - Plays an audioclip - """ - import requests - import json - - url = f"https://api.ws.sonos.com/control/api/v1/players/RINCON_542A1B599FF201400/audioClip" - - payload = json.dumps( - { - "name": "Embla", - "appId": "com.acme.app", - "streamUrl": f"{audioclip_url}", - "volume": 30, - "priority": "HIGH", - "clipType": "CUSTOM", - } - ) - headers = { - "Content-Type": "application/json", - "Authorization": f"Bearer bEy3xnmpvoxrLcP7syVxVdjO2maj", - } - - response = requests.request("POST", url, headers=headers, data=payload) - - def sentence(state: QueryStateDict, result: Result) -> None: """Called when sentence processing is complete""" q: Query = state["query"] @@ -195,7 +168,6 @@ def sentence(state: QueryStateDict, result: Result) -> None: sonos_key = read_api_key("SonosKey") answer = "Skráðu þig inn hjá Sonos" voice_answer, response = answer, dict(answer=answer) - audioClip(text_to_audio_url(voice_answer)) q.set_answer(response, answer, voice_answer) q.set_url( f"https://api.sonos.com/login/v3/oauth?client_id={sonos_key}&response_type=code&state={client_id}&scope=playback-control-all&redirect_uri=http://{host}/connect_sonos.api" @@ -203,30 +175,145 @@ def sentence(state: QueryStateDict, result: Result) -> None: return elif result.qtype == "create_speaker_token": - sonos_encoded_credentials = read_api_key("SonosEncodedCredentials") - answer = "Ég bjó til tóka frá Sonos" - voice_answer, response = answer, dict(answer=answer) - audioClip(text_to_audio_url(voice_answer)) code = str(q.client_data("sonos_code")) - q.set_answer(response, answer, voice_answer) - - url = f"https://api.sonos.com/login/v3/oauth/access?grant_type=authorization_code&code={code}&redirect_uri=http://{host}/connect_sonos.api" - payload = {} - headers = { - "Authorization": f"Basic {sonos_encoded_credentials}", - "Cookie": "JSESSIONID=2DEFC02D2184D987F4CCAD5E45196948; AWSELB=69BFEFC914A689BF6DC8E4652748D7B501ED60290D5EA56F2E543ABD7CF357A5F65186AEBC76E6A16196350947ED84835621A185D1BF63900D4B3E7BC7FE3CF19CCF26B78C; AWSELBCORS=69BFEFC914A689BF6DC8E4652748D7B501ED60290D5EA56F2E543ABD7CF357A5F65186AEBC76E6A16196350947ED84835621A185D1BF63900D4B3E7BC7FE3CF19CCF26B78C", - } - response = requests.request("POST", url, headers=headers, data=payload) + sonos_credentials_dict = {} + sonos_encoded_credentials = read_api_key("SonosEncodedCredentials") + response = create_token(code, sonos_encoded_credentials, host) if response.status_code != 200: print("Error:", response.status_code) print(response.text) return response_json = response.json() - sonos_credentials_dict = { - "access_token": response_json["access_token"], - "refresh_token": response_json["refresh_token"], - } + sonos_credentials_dict.update( + { + "access_token": response_json["access_token"], + "refresh_token": response_json["refresh_token"], + } + ) + response = get_households(sonos_credentials_dict["access_token"]) + if response.status_code != 200: + print("Error:", response.status_code) + print(response.text) + return + response_json = response.json() + sonos_credentials_dict.update( + { + "household_id": response_json["households"][0]["id"], + } + ) + response = get_groups( + sonos_credentials_dict["household_id"], + sonos_credentials_dict["access_token"], + ) + if response.status_code != 200: + print("Error:", response.status_code) + print(response.text) + return + response_json = response.json() + sonos_credentials_dict.update( + { + "group_id": response_json["groups"][0]["id"], + "player_id": response_json["players"][0]["id"], + } + ) q.store_query_data( str(q.client_id), "sonos_credentials", sonos_credentials_dict ) + answer = "Ég bjó til tóka frá Sonos" + # voice_answer = answer + audio_clip( + text_to_audio_url(answer), + sonos_credentials_dict["player_id"], + sonos_credentials_dict["access_token"], + ) + q.set_answer(response, answer, voice_answer) return + + +# put this in a separate file +def get_households(token): + """ + Returns the list of households of the user + """ + url = f"https://api.ws.sonos.com/control/api/v1/households" + + payload = {} + headers = {"Authorization": f"Bearer {token}"} + + response = requests.request("GET", url, headers=headers, data=payload) + + return response + + +def get_groups(household_id, token): + """ + Returns the list of groups of the user + """ + url = f"https://api.ws.sonos.com/control/api/v1/households/{household_id}/groups" + + payload = {} + headers = {"Authorization": f"Bearer {token}"} + + response = requests.request("GET", url, headers=headers, data=payload) + + return response + + +def create_token(code, sonos_encoded_credentials, host): + """ + Creates a token given a code + """ + url = f"https://api.sonos.com/login/v3/oauth/access?grant_type=authorization_code&code={code}&redirect_uri=http://{host}/connect_sonos.api" + + payload = {} + headers = { + "Authorization": f"Basic {sonos_encoded_credentials}", + "Cookie": "JSESSIONID=F710019AF0A3B7126A8702577C883B5F; AWSELB=69BFEFC914A689BF6DC8E4652748D7B501ED60290D5EA56F2E543ABD7CF357A5F65186AEBCFB059E28075D83A700FD504C030A53CC28683B515BE3DCA3CC587AFAF606E171; AWSELBCORS=69BFEFC914A689BF6DC8E4652748D7B501ED60290D5EA56F2E543ABD7CF357A5F65186AEBCFB059E28075D83A700FD504C030A53CC28683B515BE3DCA3CC587AFAF606E171", + } + + response = requests.request("POST", url, headers=headers, data=payload) + + return response + + +def toggle_play_pause(group_id, token): + """ + Toggles the play/pause of a group + """ + url = ( + f"https://api.ws.sonos.com/control/api/v1/groups/{group_id}/playback/playPause" + ) + + payload = {} + headers = {"Content-Type": "application/json", "Authorization": f"Bearer {token}"} + + response = requests.request("POST", url, headers=headers, data=payload) + + return response + + +def audio_clip(audioclip_url, player_id, token): + """ + Plays an audioclip from link to .mp3 file + """ + import requests + import json + + url = f"https://api.ws.sonos.com/control/api/v1/players/{player_id}/audioClip" + + payload = json.dumps( + { + "name": "Embla", + "appId": "com.acme.app", + "streamUrl": f"{audioclip_url}", + "volume": 30, + "priority": "HIGH", + "clipType": "CUSTOM", + } + ) + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {token}", + } + + response = requests.request("POST", url, headers=headers, data=payload) diff --git a/queries/iot_speakers.py b/queries/iot_speakers.py index 27b01ae2..d4f10a25 100644 --- a/queries/iot_speakers.py +++ b/queries/iot_speakers.py @@ -431,27 +431,13 @@ def QIoTSpeakerGroupName(node: Node, params: QueryStateDict, result: Result) -> def sentence(state: QueryStateDict, result: Result) -> None: """Called when sentence processing is complete""" q: Query = state["query"] - changing_color = result.get("changing_color", False) - changing_scene = result.get("changing_scene", False) - changing_brightness = result.get("changing_brightness", False) - print("error?", sum((changing_color, changing_scene, changing_brightness)) > 1) - if ( - sum((changing_color, changing_scene, changing_brightness)) > 1 - or "qtype" not in result - ): - q.set_error("E_QUERY_NOT_UNDERSTOOD") - return - q.set_qtype(result.qtype) + q.set_qtype(result.get["qtype"]) - smartdevice_type = "smartlights" - client_id = str(q.client_id) - print("client_id:", client_id) + smartdevice_type = "smartSpeaker" # Fetch relevant data from the device_data table to perform an action on the lights device_data = cast(Optional[DeviceData], q.client_data(smartdevice_type)) - print("location :", q.location) - print("device data :", device_data) selected_light: Optional[str] = None print("selected light:", selected_light) diff --git a/routes/__init__.py b/routes/__init__.py index 320bc842..00582e1c 100755 --- a/routes/__init__.py +++ b/routes/__init__.py @@ -380,4 +380,5 @@ def get_status(task: str): from .words import * from .stats import * from .salescloud import * +from .sonos import * from nn.api import * diff --git a/routes/sonos.py b/routes/sonos.py index dce575b9..e4a3314a 100644 --- a/routes/sonos.py +++ b/routes/sonos.py @@ -25,7 +25,7 @@ import requests -def getHouseholds(token): +def get_households(token): """ Returns the list of households of the user """ @@ -39,7 +39,7 @@ def getHouseholds(token): return response -def getGroups(houshold_id, token): +def get_groups(houshold_id, token): """ Returns the list of groups of the user """ @@ -53,11 +53,11 @@ def getGroups(houshold_id, token): return response -def createToken(code, sonos_encoded_credentials): +def create_token(code, sonos_encoded_credentials, host): """ Creates a token given a code """ - url = f"https://api.sonos.com/login/v3/oauth/access?grant_type=authorization_code&code={code}&redirect_uri=http://localhost:5000/connect_sonos.api" + url = f"https://api.sonos.com/login/v3/oauth/access?grant_type=authorization_code&code={code}&redirect_uri=http://{host}/connect_sonos.api" payload = {} headers = { @@ -70,7 +70,7 @@ def createToken(code, sonos_encoded_credentials): return response -def togglePlayPause(group_id, token): +def toggle_play_pause(group_id, token): """ Toggles the play/pause of a group """ @@ -86,7 +86,7 @@ def togglePlayPause(group_id, token): return response -def audioClip(audioclip_url, player_id, token): +def audio_clip(audioclip_url, player_id, token): """ Plays an audioclip from link to .mp3 file """ From d3bb77e77eb7e1909373f045738d4cbbd425b16b Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Fri, 24 Jun 2022 10:01:45 +0000 Subject: [PATCH 093/371] moved sonos.py and deleted reference to it --- queries/iot_connect.py | 4 +- queries/iot_speakers.py | 956 +++++++++++++++++------------------ {routes => queries}/sonos.py | 8 +- routes/__init__.py | 1 - 4 files changed, 484 insertions(+), 485 deletions(-) rename {routes => queries}/sonos.py (93%) diff --git a/queries/iot_connect.py b/queries/iot_connect.py index 7426bbc2..37c9c3d1 100644 --- a/queries/iot_connect.py +++ b/queries/iot_connect.py @@ -220,7 +220,7 @@ def sentence(state: QueryStateDict, result: Result) -> None: str(q.client_id), "sonos_credentials", sonos_credentials_dict ) answer = "Ég bjó til tóka frá Sonos" - # voice_answer = answer + voice_answer = answer audio_clip( text_to_audio_url(answer), sonos_credentials_dict["player_id"], @@ -306,7 +306,7 @@ def audio_clip(audioclip_url, player_id, token): "name": "Embla", "appId": "com.acme.app", "streamUrl": f"{audioclip_url}", - "volume": 30, + "volume": 50, "priority": "HIGH", "clipType": "CUSTOM", } diff --git a/queries/iot_speakers.py b/queries/iot_speakers.py index d4f10a25..ba7f5254 100644 --- a/queries/iot_speakers.py +++ b/queries/iot_speakers.py @@ -1,498 +1,498 @@ -""" +# """ - Greynir: Natural language processing for Icelandic +# Greynir: Natural language processing for Icelandic - Randomness query response module +# Randomness query response module - Copyright (C) 2022 Miðeind ehf. +# Copyright (C) 2022 Miðeind ehf. - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. - You should have received a copy of the GNU General Public License - along with this program. If not, see http://www.gnu.org/licenses/. +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. - This query module handles queries related to the generation - of random numbers, e.g. "Kastaðu tengingi", "Nefndu tölu milli 5 og 10", etc. +# This query module handles queries related to the generation +# of random numbers, e.g. "Kastaðu tengingi", "Nefndu tölu milli 5 og 10", etc. -""" +# """ -# TODO: add "láttu", "hafðu", "litaðu", "kveiktu" functionality. -# TODO: make the objects of sentences more modular, so that the same structure doesn't need to be written for each action -# TODO: ditto the previous comment. make the initial non-terminals general and go into specifics at the terminal level instead. -# TODO: substituion klósett, baðherbergi hugmyndÆ senda lista i javascript og profa i röð -# TODO: Embla stores old javascript code cached which has caused errors -# TODO: Cut down javascript sent to Embla -# TODO: Two specified groups or lights. -# TODO: No specified location -# TODO: Fix scene issues +# # TODO: add "láttu", "hafðu", "litaðu", "kveiktu" functionality. +# # TODO: make the objects of sentences more modular, so that the same structure doesn't need to be written for each action +# # TODO: ditto the previous comment. make the initial non-terminals general and go into specifics at the terminal level instead. +# # TODO: substituion klósett, baðherbergi hugmyndÆ senda lista i javascript og profa i röð +# # TODO: Embla stores old javascript code cached which has caused errors +# # TODO: Cut down javascript sent to Embla +# # TODO: Two specified groups or lights. +# # TODO: No specified location +# # TODO: Fix scene issues -from typing import Dict, Mapping, Optional, cast -from typing_extensions import TypedDict +# from typing import Dict, Mapping, Optional, cast +# from typing_extensions import TypedDict -import logging -import random -import json -import flask +# import logging +# import random +# import json +# import flask -from query import Query, QueryStateDict, AnswerTuple -from queries import gen_answer, read_jsfile, read_grammar_file -from tree import Result, Node +# from query import Query, QueryStateDict, AnswerTuple +# from queries import gen_answer, read_jsfile, read_grammar_file +# from tree import Result, Node -_IoT_QTYPE = "IoT" +# _IoT_QTYPE = "IoT" -TOPIC_LEMMAS = [ - "tónlist", -] +# TOPIC_LEMMAS = [ +# "tónlist", +# ] -def help_text(lemma: str) -> str: - """Help text to return when query.py is unable to parse a query but - one of the above lemmas is found in it""" - return "Ég skil þig ef þú segir til dæmis: {0}.".format( - random.choice( - ("Hækkaðu í tónlistinni", "Kveiktu á tónlist", "Láttu vera tónlist") - ) - ) - - -# This module wants to handle parse trees for queries -HANDLE_TREE = True - -# The grammar nonterminals this module wants to handle -QUERY_NONTERMINALS = {"QIoTSpeaker"} - -# The context-free grammar for the queries recognized by this plug-in module -# GRAMMAR = read_grammar_file("iot_hue") - -GRAMMAR = f""" - -/þgf = þgf -/ef = ef - -Query → - QIoTSpeaker '?'? - -QIoTSpeaker → - QIoTSpeakerQuery - -QIoTSpeakerQuery -> - QIoTSpeakerMakeVerb QIoTSpeakerMakeRest - | QIoTSpeakerSetVerb QIoTSpeakerSetRest - | QIoTSpeakerChangeVerb QIoTSpeakerChangeRest - | QIoTSpeakerLetVerb QIoTSpeakerLetRest - | QIoTSpeakerTurnOnVerb QIoTSpeakerTurnOnRest - | QIoTSpeakerTurnOffVerb QIoTSpeakerTurnOffRest - | QIoTSpeakerIncreaseOrDecreaseVerb QIoTSpeakerIncreaseOrDecreaseRest - -QIoTSpeakerMakeVerb -> - 'gera:so'_bh - -QIoTSpeakerSetVerb -> - 'setja:so'_bh - | 'stilla:so'_bh - -QIoTSpeakerChangeVerb -> - 'breyta:so'_bh - -QIoTSpeakerLetVerb -> - 'láta:so'_bh - -QIoTSpeakerTurnOnVerb -> - 'kveikja:so'_bh - -QIoTSpeakerTurnOffVerb -> - 'slökkva:so'_bh - -QIoTSpeakerIncreaseOrDecreaseVerb -> - QIoTSpeakerIncreaseVerb - | QIoTSpeakerDecreaseVerb - -QIoTSpeakerIncreaseVerb -> - 'hækka:so'_bh - | 'auka:so'_bh - -QIoTSpeakerDecreaseVerb -> - 'lækka:so'_bh - | 'minnka:so'_bh - -QCHANGEMakeRest -> - # QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigMake - # | QCHANGESubject/þf QCHANGEHvernigMake QCHANGEHvar? - # | QCHANGEHvar? QCHANGESubject/þf QCHANGEHvernigMake - # | QCHANGEHvar? QCHANGEHvernigMake QCHANGESubject/þf - # | QCHANGEHvernigMake QCHANGESubject/þf QCHANGEHvar? - # | QCHANGEHvernigMake QCHANGEHvar? QCHANGESubject/þf - QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? - -# TODO: Add support for "stilltu rauðan lit á ljósið í eldhúsinu" -QCHANGESetRest -> - # QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigSet - # | QCHANGESubject/þf QCHANGEHvernigSet QCHANGEHvar? - # | QCHANGEHvar? QCHANGESubject/þf QCHANGEHvernigSet - # | QCHANGEHvar? QCHANGEHvernigSet QCHANGESubject/þf - # | QCHANGEHvernigSet QCHANGESubject/þf QCHANGEHvar? - # | QCHANGEHvernigSet QCHANGEHvar? QCHANGESubject/þf - "á" QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? - -QCHANGEChangeRest -> - # QCHANGESubjectOne/þgf QCHANGEHvar? QCHANGEHvernigChange - # | QCHANGESubjectOne/þgf QCHANGEHvernigChange QCHANGEHvar? - # | QCHANGEHvar? QCHANGESubjectOne/þgf QCHANGEHvernigChange - # | QCHANGEHvar? QCHANGEHvernigChange QCHANGESubjectOne/þgf - # | QCHANGEHvernigChange QCHANGESubjectOne/þgf QCHANGEHvar? - # | QCHANGEHvernigChange QCHANGEHvar? QCHANGESubjectOne/þgf - -QCHANGELetRest -> - QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigLet - | QCHANGESubject/þf QCHANGEHvernigLet QCHANGEHvar? - | QCHANGEHvar? QCHANGESubject/þf QCHANGEHvernigLet - | QCHANGEHvar? QCHANGEHvernigLet QCHANGESubject/þf - | QCHANGEHvernigLet QCHANGESubject/þf QCHANGEHvar? - | QCHANGEHvernigLet QCHANGEHvar? QCHANGESubject/þf - "vera" QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? - -QCHANGETurnOnRest -> - # QCHANGETurnOnLightsRest - # | QCHANGEAHverju QCHANGEHvar? - # | QCHANGEHvar? QCHANGEAHverju - "á" QIoTSpeakerMusicWord/þgf QIoTSpeakerHvar? - -# QCHANGETurnOnLightsRest -> -# QCHANGELightSubject/þf QCHANGEHvar? -# | QCHANGEHvar QCHANGELightSubject/þf? - -# Would be good to add "slökktu á rauða litnum" functionality -QCHANGETurnOffRest -> - # QCHANGETurnOffLightsRest - "á" QIoTSpeakerMusicWord/þgf QIoTSpeakerHvar? - -# QCHANGETurnOffLightsRest -> -# QCHANGELightSubject/þf QCHANGEHvar? -# | QCHANGEHvar QCHANGELightSubject/þf? - -# TODO: Make the subject categorization cleaner -QCHANGEIncreaseOrDecreaseRest -> - # QCHANGELightSubject/þf QCHANGEHvar? - # | QCHANGEBrightnessSubject/þf QCHANGEHvar? - QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? - | "í" QIoTSpeakerMusicWord/þgf QIoTSpeakerHvar? - -# QCHANGESubject/fall -> -# QCHANGESubjectOne/fall -# | QCHANGESubjectTwo/fall - -QIoTMusicWord -> - 'tónlist'/fall - -# # TODO: Decide whether LightSubject/þgf should be accepted -# QCHANGESubjectOne/fall -> -# QCHANGELightSubject/fall -# | QCHANGEColorSubject/fall -# | QCHANGEBrightnessSubject/fall -# | QCHANGESceneSubject/fall - -# QCHANGESubjectTwo/fall -> -# QCHANGEGroupNameSubject/fall # á bara að styðja "gerðu eldhúsið rautt", "gerðu eldhúsið rómó" "gerðu eldhúsið bjartara", t.d. - -QIoTSpeakerHvar -> - QIoTSpeakerLocationPreposition QIoTSpeakerGroupName/þgf - -# QCHANGEHvernigMake -> -# QCHANGEAnnadAndlag # gerðu litinn rauðan í eldhúsinu EÐA gerðu birtuna meiri í eldhúsinu -# | QCHANGEAdHverju # gerðu litinn að rauðum í eldhúsinu -# | QCHANGEThannigAd - -# QCHANGEHvernigSet -> -# QCHANGEAHvad -# | QCHANGEThannigAd - -# QCHANGEHvernigChange -> -# QCHANGEIHvad -# | QCHANGEThannigAd - -# QCHANGEHvernigLet -> -# QCHANGEBecome QCHANGESomethingOrSomehow -# | QCHANGEBe QCHANGESomehow - -# QCHANGEThannigAd -> -# "þannig" "að"? pfn_nf QCHANGEBeOrBecomeSubjunctive QCHANGEAnnadAndlag - -# I think these verbs only appear in these forms. -# In which case these terminals should be deleted and a direct reference should be made in the relevant non-terminals. -# QCHANGEBe -> -# "vera" - -# QCHANGEBecome -> -# "verða" - -# QCHANGEBeOrBecomeSubjunctive -> -# "verði" -# | "sé" - -# QCHANGELightSubject/fall -> -# QCHANGELight/fall - -# QCHANGEColorSubject/fall -> -# QCHANGEColorWord/fall QCHANGELight/ef? -# | QCHANGEColorWord/fall "á" QCHANGELight/þgf - -# QCHANGEBrightnessSubject/fall -> -# QCHANGEBrightnessWord/fall QCHANGELight/ef? -# | QCHANGEBrightnessWord/fall "á" QCHANGELight/þgf - -# QCHANGESceneSubject/fall -> -# QCHANGESceneWord/fall - -# QCHANGEGroupNameSubject/fall -> -# QCHANGEGroupName/fall - -QIoTSpeakerLocationPreposition -> - QIoTSpeakerLocationPrepositionFirstPart? QIoTSpeakerLocationPrepositionSecondPart - -# The latter proverbs are grammatically incorrect, but common errors, both in speech and transcription. -# The list provided is taken from StefnuAtv in Greynir.grammar. That includes "aftur:ao", which is not applicable here. -QIoTSpeakerLocationPrepositionFirstPart -> - StaðarAtv - | "fram:ao" - | "inn:ao" - | "niður:ao" - | "upp:ao" - | "út:ao" - -QIoTSpeakerLocationPrepositionSecondPart -> - "á" | "í" - -QIoTSpeakerGroupName/fall -> - no/fall - -# QCHANGELightName/fall -> +# def help_text(lemma: str) -> str: +# """Help text to return when query.py is unable to parse a query but +# one of the above lemmas is found in it""" +# return "Ég skil þig ef þú segir til dæmis: {0}.".format( +# random.choice( +# ("Hækkaðu í tónlistinni", "Kveiktu á tónlist", "Láttu vera tónlist") +# ) +# ) + + +# # This module wants to handle parse trees for queries +# HANDLE_TREE = True + +# # The grammar nonterminals this module wants to handle +# QUERY_NONTERMINALS = {"QIoTSpeaker"} + +# # The context-free grammar for the queries recognized by this plug-in module +# # GRAMMAR = read_grammar_file("iot_hue") + +# GRAMMAR = f""" + +# /þgf = þgf +# /ef = ef + +# Query → +# QIoTSpeaker '?'? + +# QIoTSpeaker → +# QIoTSpeakerQuery + +# QIoTSpeakerQuery -> +# QIoTSpeakerMakeVerb QIoTSpeakerMakeRest +# | QIoTSpeakerSetVerb QIoTSpeakerSetRest +# | QIoTSpeakerChangeVerb QIoTSpeakerChangeRest +# | QIoTSpeakerLetVerb QIoTSpeakerLetRest +# | QIoTSpeakerTurnOnVerb QIoTSpeakerTurnOnRest +# | QIoTSpeakerTurnOffVerb QIoTSpeakerTurnOffRest +# | QIoTSpeakerIncreaseOrDecreaseVerb QIoTSpeakerIncreaseOrDecreaseRest + +# QIoTSpeakerMakeVerb -> +# 'gera:so'_bh + +# QIoTSpeakerSetVerb -> +# 'setja:so'_bh +# | 'stilla:so'_bh + +# QIoTSpeakerChangeVerb -> +# 'breyta:so'_bh + +# QIoTSpeakerLetVerb -> +# 'láta:so'_bh + +# QIoTSpeakerTurnOnVerb -> +# 'kveikja:so'_bh + +# QIoTSpeakerTurnOffVerb -> +# 'slökkva:so'_bh + +# QIoTSpeakerIncreaseOrDecreaseVerb -> +# QIoTSpeakerIncreaseVerb +# | QIoTSpeakerDecreaseVerb + +# QIoTSpeakerIncreaseVerb -> +# 'hækka:so'_bh +# | 'auka:so'_bh + +# QIoTSpeakerDecreaseVerb -> +# 'lækka:so'_bh +# | 'minnka:so'_bh + +# QCHANGEMakeRest -> +# # QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigMake +# # | QCHANGESubject/þf QCHANGEHvernigMake QCHANGEHvar? +# # | QCHANGEHvar? QCHANGESubject/þf QCHANGEHvernigMake +# # | QCHANGEHvar? QCHANGEHvernigMake QCHANGESubject/þf +# # | QCHANGEHvernigMake QCHANGESubject/þf QCHANGEHvar? +# # | QCHANGEHvernigMake QCHANGEHvar? QCHANGESubject/þf +# QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? + +# # TODO: Add support for "stilltu rauðan lit á ljósið í eldhúsinu" +# QCHANGESetRest -> +# # QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigSet +# # | QCHANGESubject/þf QCHANGEHvernigSet QCHANGEHvar? +# # | QCHANGEHvar? QCHANGESubject/þf QCHANGEHvernigSet +# # | QCHANGEHvar? QCHANGEHvernigSet QCHANGESubject/þf +# # | QCHANGEHvernigSet QCHANGESubject/þf QCHANGEHvar? +# # | QCHANGEHvernigSet QCHANGEHvar? QCHANGESubject/þf +# "á" QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? + +# QCHANGEChangeRest -> +# # QCHANGESubjectOne/þgf QCHANGEHvar? QCHANGEHvernigChange +# # | QCHANGESubjectOne/þgf QCHANGEHvernigChange QCHANGEHvar? +# # | QCHANGEHvar? QCHANGESubjectOne/þgf QCHANGEHvernigChange +# # | QCHANGEHvar? QCHANGEHvernigChange QCHANGESubjectOne/þgf +# # | QCHANGEHvernigChange QCHANGESubjectOne/þgf QCHANGEHvar? +# # | QCHANGEHvernigChange QCHANGEHvar? QCHANGESubjectOne/þgf + +# QCHANGELetRest -> +# QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigLet +# | QCHANGESubject/þf QCHANGEHvernigLet QCHANGEHvar? +# | QCHANGEHvar? QCHANGESubject/þf QCHANGEHvernigLet +# | QCHANGEHvar? QCHANGEHvernigLet QCHANGESubject/þf +# | QCHANGEHvernigLet QCHANGESubject/þf QCHANGEHvar? +# | QCHANGEHvernigLet QCHANGEHvar? QCHANGESubject/þf +# "vera" QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? + +# QCHANGETurnOnRest -> +# # QCHANGETurnOnLightsRest +# # | QCHANGEAHverju QCHANGEHvar? +# # | QCHANGEHvar? QCHANGEAHverju +# "á" QIoTSpeakerMusicWord/þgf QIoTSpeakerHvar? + +# # QCHANGETurnOnLightsRest -> +# # QCHANGELightSubject/þf QCHANGEHvar? +# # | QCHANGEHvar QCHANGELightSubject/þf? + +# # Would be good to add "slökktu á rauða litnum" functionality +# QCHANGETurnOffRest -> +# # QCHANGETurnOffLightsRest +# "á" QIoTSpeakerMusicWord/þgf QIoTSpeakerHvar? + +# # QCHANGETurnOffLightsRest -> +# # QCHANGELightSubject/þf QCHANGEHvar? +# # | QCHANGEHvar QCHANGELightSubject/þf? + +# # TODO: Make the subject categorization cleaner +# QCHANGEIncreaseOrDecreaseRest -> +# # QCHANGELightSubject/þf QCHANGEHvar? +# # | QCHANGEBrightnessSubject/þf QCHANGEHvar? +# QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? +# | "í" QIoTSpeakerMusicWord/þgf QIoTSpeakerHvar? + +# # QCHANGESubject/fall -> +# # QCHANGESubjectOne/fall +# # | QCHANGESubjectTwo/fall + +# QIoTMusicWord -> +# 'tónlist'/fall + +# # # TODO: Decide whether LightSubject/þgf should be accepted +# # QCHANGESubjectOne/fall -> +# # QCHANGELightSubject/fall +# # | QCHANGEColorSubject/fall +# # | QCHANGEBrightnessSubject/fall +# # | QCHANGESceneSubject/fall + +# # QCHANGESubjectTwo/fall -> +# # QCHANGEGroupNameSubject/fall # á bara að styðja "gerðu eldhúsið rautt", "gerðu eldhúsið rómó" "gerðu eldhúsið bjartara", t.d. + +# QIoTSpeakerHvar -> +# QIoTSpeakerLocationPreposition QIoTSpeakerGroupName/þgf + +# # QCHANGEHvernigMake -> +# # QCHANGEAnnadAndlag # gerðu litinn rauðan í eldhúsinu EÐA gerðu birtuna meiri í eldhúsinu +# # | QCHANGEAdHverju # gerðu litinn að rauðum í eldhúsinu +# # | QCHANGEThannigAd + +# # QCHANGEHvernigSet -> +# # QCHANGEAHvad +# # | QCHANGEThannigAd + +# # QCHANGEHvernigChange -> +# # QCHANGEIHvad +# # | QCHANGEThannigAd + +# # QCHANGEHvernigLet -> +# # QCHANGEBecome QCHANGESomethingOrSomehow +# # | QCHANGEBe QCHANGESomehow + +# # QCHANGEThannigAd -> +# # "þannig" "að"? pfn_nf QCHANGEBeOrBecomeSubjunctive QCHANGEAnnadAndlag + +# # I think these verbs only appear in these forms. +# # In which case these terminals should be deleted and a direct reference should be made in the relevant non-terminals. +# # QCHANGEBe -> +# # "vera" + +# # QCHANGEBecome -> +# # "verða" + +# # QCHANGEBeOrBecomeSubjunctive -> +# # "verði" +# # | "sé" + +# # QCHANGELightSubject/fall -> +# # QCHANGELight/fall + +# # QCHANGEColorSubject/fall -> +# # QCHANGEColorWord/fall QCHANGELight/ef? +# # | QCHANGEColorWord/fall "á" QCHANGELight/þgf + +# # QCHANGEBrightnessSubject/fall -> +# # QCHANGEBrightnessWord/fall QCHANGELight/ef? +# # | QCHANGEBrightnessWord/fall "á" QCHANGELight/þgf + +# # QCHANGESceneSubject/fall -> +# # QCHANGESceneWord/fall + +# # QCHANGEGroupNameSubject/fall -> +# # QCHANGEGroupName/fall + +# QIoTSpeakerLocationPreposition -> +# QIoTSpeakerLocationPrepositionFirstPart? QIoTSpeakerLocationPrepositionSecondPart + +# # The latter proverbs are grammatically incorrect, but common errors, both in speech and transcription. +# # The list provided is taken from StefnuAtv in Greynir.grammar. That includes "aftur:ao", which is not applicable here. +# QIoTSpeakerLocationPrepositionFirstPart -> +# StaðarAtv +# | "fram:ao" +# | "inn:ao" +# | "niður:ao" +# | "upp:ao" +# | "út:ao" + +# QIoTSpeakerLocationPrepositionSecondPart -> +# "á" | "í" + +# QIoTSpeakerGroupName/fall -> # no/fall -# QCHANGEColorName -> -# {" | ".join(f"'{color}:lo'" for color in _COLORS.keys())} - -# QCHANGESceneName -> -# no -# | lo - -# QCHANGEAnnadAndlag -> -# QCHANGENewSetting/nf -# | QCHANGESpyrjaHuldu/nf - -# QCHANGEAdHverju -> -# "að" QCHANGENewSetting/þgf - -# QCHANGEAHvad -> -# "á" QCHANGENewSetting/þf - -# QCHANGEIHvad -> -# "í" QCHANGENewSetting/þf - -# QCHANGEAHverju -> -# "á" QCHANGELight/þgf -# | "á" QCHANGENewSetting/þgf - -# QCHANGESomethingOrSomehow -> -# QCHANGEAnnadAndlag -# | QCHANGEAdHverju - -# QCHANGESomehow -> -# QCHANGEAnnadAndlag -# | QCHANGEThannigAd - -# QCHANGELight/fall -> -# QCHANGELightName/fall -# | QCHANGELightWord/fall - -# # Should 'birta' be included -# QCHANGELightWord/fall -> -# 'ljós'/fall -# | 'lýsing'/fall -# | 'birta'/fall -# | 'Birta'/fall - -# QCHANGEColorWord/fall -> -# 'litur'/fall -# | 'litblær'/fall -# | 'blær'/fall - -# QCHANGEBrightnessWords/fall -> -# 'bjartur'/fall -# | QCHANGEBrightnessWord/fall - -# QCHANGEBrightnessWord/fall -> -# 'birta'/fall -# | 'Birta'/fall -# | 'birtustig'/fall - -# QCHANGESceneWord/fall -> -# 'sena'/fall -# | 'stemning'/fall -# | 'stemming'/fall -# | 'stemmning'/fall +# # QCHANGELightName/fall -> +# # no/fall + +# # QCHANGEColorName -> +# # {" | ".join(f"'{color}:lo'" for color in _COLORS.keys())} + +# # QCHANGESceneName -> +# # no +# # | lo + +# # QCHANGEAnnadAndlag -> +# # QCHANGENewSetting/nf +# # | QCHANGESpyrjaHuldu/nf + +# # QCHANGEAdHverju -> +# # "að" QCHANGENewSetting/þgf + +# # QCHANGEAHvad -> +# # "á" QCHANGENewSetting/þf + +# # QCHANGEIHvad -> +# # "í" QCHANGENewSetting/þf + +# # QCHANGEAHverju -> +# # "á" QCHANGELight/þgf +# # | "á" QCHANGENewSetting/þgf + +# # QCHANGESomethingOrSomehow -> +# # QCHANGEAnnadAndlag +# # | QCHANGEAdHverju + +# # QCHANGESomehow -> +# # QCHANGEAnnadAndlag +# # | QCHANGEThannigAd + +# # QCHANGELight/fall -> +# # QCHANGELightName/fall +# # | QCHANGELightWord/fall + +# # # Should 'birta' be included +# # QCHANGELightWord/fall -> +# # 'ljós'/fall +# # | 'lýsing'/fall +# # | 'birta'/fall +# # | 'Birta'/fall + +# # QCHANGEColorWord/fall -> +# # 'litur'/fall +# # | 'litblær'/fall +# # | 'blær'/fall + +# # QCHANGEBrightnessWords/fall -> +# # 'bjartur'/fall +# # | QCHANGEBrightnessWord/fall + +# # QCHANGEBrightnessWord/fall -> +# # 'birta'/fall +# # | 'Birta'/fall +# # | 'birtustig'/fall + +# # QCHANGESceneWord/fall -> +# # 'sena'/fall +# # | 'stemning'/fall +# # | 'stemming'/fall +# # | 'stemmning'/fall + +# # # Need to ask Hulda how this works. +# # QCHANGESpyrjaHuldu/fall -> +# # # QCHANGEHuldaColor/fall +# # QCHANGEHuldaBrightness/fall +# # # | QCHANGEHuldaScene/fall -# # Need to ask Hulda how this works. -# QCHANGESpyrjaHuldu/fall -> -# # QCHANGEHuldaColor/fall -# QCHANGEHuldaBrightness/fall -# # | QCHANGEHuldaScene/fall +# # # Do I need a "new light state" non-terminal? +# # QCHANGENewSetting/fall -> +# # QCHANGENewColor/fall +# # | QCHANGENewBrightness/fall +# # | QCHANGENewScene/fall -# # Do I need a "new light state" non-terminal? -# QCHANGENewSetting/fall -> -# QCHANGENewColor/fall -# | QCHANGENewBrightness/fall -# | QCHANGENewScene/fall - -# # Missing "meira dimmt" -# QCHANGEHuldaBrightness/fall -> -# QCHANGEMoreBrighterOrHigher/fall QCHANGEBrightnessWords/fall? -# | QCHANGELessDarkerOrLower/fall QCHANGEBrightnessWords/fall? - -# #Unsure about whether to include /fall after QCHANGEColorName -# QCHANGENewColor/fall -> -# QCHANGEColorWord/fall QCHANGEColorName -# | QCHANGEColorName QCHANGEColorWord/fall? - -# QCHANGENewBrightness/fall -> -# 'sá'/fall? QCHANGEBrightestOrDarkest/fall -# | QCHANGEBrightestOrDarkest/fall QCHANGEBrightnessOrSettingWord/fall - -# QCHANGENewScene/fall -> -# QCHANGESceneWord/fall QCHANGESceneName -# | QCHANGESceneName QCHANGESceneWord/fall? - -# QCHANGEMoreBrighterOrHigher/fall -> -# 'mikill:lo'_mst/fall -# | 'bjartur:lo'_mst/fall -# | 'ljós:lo'_mst/fall -# | 'hár:lo'_mst/fall - -# QCHANGELessDarkerOrLower/fall -> -# 'lítill:lo'_mst/fall -# | 'dökkur:lo'_mst/fall -# | 'dimmur:lo'_mst/fall -# | 'lágur:lo'_mst/fall - -# QCHANGEBrightestOrDarkest/fall -> -# QCHANGEBrightest/fall -# | QCHANGEDarkest/fall - -# QCHANGEBrightest/fall -> -# 'bjartur:lo'_evb -# | 'bjartur:lo'_esb -# | 'ljós:lo'_evb -# | 'ljós:lo'_esb - -# QCHANGEDarkest/fall -> -# 'dimmur:lo'_evb -# | 'dimmur:lo'_esb -# | 'dökkur:lo'_evb -# | 'dökkur:lo'_esb - -# QCHANGEBrightnessOrSettingWord/fall -> -# QCHANGEBrightnessWord/fall -# | QCHANGESettingWord/fall - -# QCHANGESettingWord/fall -> -# 'stilling'/fall - -""" - - -def QIoTSpeakerIncreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: - result.action = "increase_volume" - if "hue_obj" not in result: - result["hue_obj"] = {"on": True, "bri_inc": 64} - else: - result["hue_obj"]["bri_inc"] = 64 - result["hue_obj"]["on"] = True - - -def QIoTSpeakerDecreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: - result.action = "decrease_volume" - if "hue_obj" not in result: - result["hue_obj"] = {"bri_inc": -64} - else: - result["hue_obj"]["bri_inc"] = -64 - - -def QIoTSpeakerGroupName(node: Node, params: QueryStateDict, result: Result) -> None: - result["group_name"] = result._indefinite - - -def sentence(state: QueryStateDict, result: Result) -> None: - """Called when sentence processing is complete""" - q: Query = state["query"] - - q.set_qtype(result.get["qtype"]) - - smartdevice_type = "smartSpeaker" - - # Fetch relevant data from the device_data table to perform an action on the lights - device_data = cast(Optional[DeviceData], q.client_data(smartdevice_type)) - - selected_light: Optional[str] = None - print("selected light:", selected_light) - hue_credentials: Optional[Dict[str, str]] = None - - if device_data is not None and smartdevice_type in device_data: - dev = device_data[smartdevice_type] - assert dev is not None - selected_light = dev.get("selected_light") - hue_credentials = dev.get("philips_hue") - bridge_ip = hue_credentials.get("ipAddress") - username = hue_credentials.get("username") - - if not device_data or not hue_credentials: - answer = "Það vantar að tengja Philips Hub-inn." - q.set_answer(*gen_answer(answer)) - return - - # Successfully matched a query type - print("bridge_ip: ", bridge_ip) - print("username: ", username) - print("selected light :", selected_light) - print("hue credentials :", hue_credentials) - - try: - # kalla í javascripts stuff - light_or_group_name = result.get("light_name", result.get("group_name", "")) - color_name = result.get("color_name", "") - print("GROUP NAME:", light_or_group_name) - print("COLOR NAME:", color_name) - print(result.hue_obj) - q.set_answer( - *gen_answer( - "ég var að kveikja ljósin! " - # + group_name - # + " " - # + color_name - # + " " - # + result.action - # + " " - # + str(result.hue_obj.get("hue", "enginn litur")) - ) - ) - js = ( - read_jsfile("IoT_Embla/fuse.js") - + f"var BRIDGE_IP = '{bridge_ip}';var USERNAME = '{username}';" - + read_jsfile("IoT_Embla/Philips_Hue/fuse_search.js") - + read_jsfile("IoT_Embla/Philips_Hue/lights.js") - + read_jsfile("IoT_Embla/Philips_Hue/set_lights.js") - ) - js += f"setLights('{light_or_group_name}', '{json.dumps(result.hue_obj)}');" - q.set_command(js) - except Exception as e: - logging.warning("Exception while processing random query: {0}".format(e)) - q.set_error("E_EXCEPTION: {0}".format(e)) - raise - - # f"var BRIDGE_IP = '192.168.1.68';var USERNAME = 'p3obluiXT13IbHMpp4X63ZvZnpNRdbqqMt723gy2';" +# # # Missing "meira dimmt" +# # QCHANGEHuldaBrightness/fall -> +# # QCHANGEMoreBrighterOrHigher/fall QCHANGEBrightnessWords/fall? +# # | QCHANGELessDarkerOrLower/fall QCHANGEBrightnessWords/fall? + +# # #Unsure about whether to include /fall after QCHANGEColorName +# # QCHANGENewColor/fall -> +# # QCHANGEColorWord/fall QCHANGEColorName +# # | QCHANGEColorName QCHANGEColorWord/fall? + +# # QCHANGENewBrightness/fall -> +# # 'sá'/fall? QCHANGEBrightestOrDarkest/fall +# # | QCHANGEBrightestOrDarkest/fall QCHANGEBrightnessOrSettingWord/fall + +# # QCHANGENewScene/fall -> +# # QCHANGESceneWord/fall QCHANGESceneName +# # | QCHANGESceneName QCHANGESceneWord/fall? + +# # QCHANGEMoreBrighterOrHigher/fall -> +# # 'mikill:lo'_mst/fall +# # | 'bjartur:lo'_mst/fall +# # | 'ljós:lo'_mst/fall +# # | 'hár:lo'_mst/fall + +# # QCHANGELessDarkerOrLower/fall -> +# # 'lítill:lo'_mst/fall +# # | 'dökkur:lo'_mst/fall +# # | 'dimmur:lo'_mst/fall +# # | 'lágur:lo'_mst/fall + +# # QCHANGEBrightestOrDarkest/fall -> +# # QCHANGEBrightest/fall +# # | QCHANGEDarkest/fall + +# # QCHANGEBrightest/fall -> +# # 'bjartur:lo'_evb +# # | 'bjartur:lo'_esb +# # | 'ljós:lo'_evb +# # | 'ljós:lo'_esb + +# # QCHANGEDarkest/fall -> +# # 'dimmur:lo'_evb +# # | 'dimmur:lo'_esb +# # | 'dökkur:lo'_evb +# # | 'dökkur:lo'_esb + +# # QCHANGEBrightnessOrSettingWord/fall -> +# # QCHANGEBrightnessWord/fall +# # | QCHANGESettingWord/fall + +# # QCHANGESettingWord/fall -> +# # 'stilling'/fall + +# """ + + +# def QIoTSpeakerIncreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: +# result.action = "increase_volume" +# if "hue_obj" not in result: +# result["hue_obj"] = {"on": True, "bri_inc": 64} +# else: +# result["hue_obj"]["bri_inc"] = 64 +# result["hue_obj"]["on"] = True + + +# def QIoTSpeakerDecreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: +# result.action = "decrease_volume" +# if "hue_obj" not in result: +# result["hue_obj"] = {"bri_inc": -64} +# else: +# result["hue_obj"]["bri_inc"] = -64 + + +# def QIoTSpeakerGroupName(node: Node, params: QueryStateDict, result: Result) -> None: +# result["group_name"] = result._indefinite + + +# def sentence(state: QueryStateDict, result: Result) -> None: +# """Called when sentence processing is complete""" +# q: Query = state["query"] + +# q.set_qtype(result.get["qtype"]) + +# smartdevice_type = "smartSpeaker" + +# # Fetch relevant data from the device_data table to perform an action on the lights +# device_data = cast(Optional[DeviceData], q.client_data(smartdevice_type)) + +# selected_light: Optional[str] = None +# print("selected light:", selected_light) +# hue_credentials: Optional[Dict[str, str]] = None + +# if device_data is not None and smartdevice_type in device_data: +# dev = device_data[smartdevice_type] +# assert dev is not None +# selected_light = dev.get("selected_light") +# hue_credentials = dev.get("philips_hue") +# bridge_ip = hue_credentials.get("ipAddress") +# username = hue_credentials.get("username") + +# if not device_data or not hue_credentials: +# answer = "Það vantar að tengja Philips Hub-inn." +# q.set_answer(*gen_answer(answer)) +# return + +# # Successfully matched a query type +# print("bridge_ip: ", bridge_ip) +# print("username: ", username) +# print("selected light :", selected_light) +# print("hue credentials :", hue_credentials) + +# try: +# # kalla í javascripts stuff +# light_or_group_name = result.get("light_name", result.get("group_name", "")) +# color_name = result.get("color_name", "") +# print("GROUP NAME:", light_or_group_name) +# print("COLOR NAME:", color_name) +# print(result.hue_obj) +# q.set_answer( +# *gen_answer( +# "ég var að kveikja ljósin! " +# # + group_name +# # + " " +# # + color_name +# # + " " +# # + result.action +# # + " " +# # + str(result.hue_obj.get("hue", "enginn litur")) +# ) +# ) +# js = ( +# read_jsfile("IoT_Embla/fuse.js") +# + f"var BRIDGE_IP = '{bridge_ip}';var USERNAME = '{username}';" +# + read_jsfile("IoT_Embla/Philips_Hue/fuse_search.js") +# + read_jsfile("IoT_Embla/Philips_Hue/lights.js") +# + read_jsfile("IoT_Embla/Philips_Hue/set_lights.js") +# ) +# js += f"setLights('{light_or_group_name}', '{json.dumps(result.hue_obj)}');" +# q.set_command(js) +# except Exception as e: +# logging.warning("Exception while processing random query: {0}".format(e)) +# q.set_error("E_EXCEPTION: {0}".format(e)) +# raise + +# # f"var BRIDGE_IP = '192.168.1.68';var USERNAME = 'p3obluiXT13IbHMpp4X63ZvZnpNRdbqqMt723gy2';" diff --git a/routes/sonos.py b/queries/sonos.py similarity index 93% rename from routes/sonos.py rename to queries/sonos.py index e4a3314a..7be9869c 100644 --- a/routes/sonos.py +++ b/queries/sonos.py @@ -29,7 +29,7 @@ def get_households(token): """ Returns the list of households of the user """ - url = "https://api.ws.sonos.com/control/api/v1/households" + url = f"https://api.ws.sonos.com/control/api/v1/households" payload = {} headers = {"Authorization": f"Bearer {token}"} @@ -39,11 +39,11 @@ def get_households(token): return response -def get_groups(houshold_id, token): +def get_groups(household_id, token): """ Returns the list of groups of the user """ - url = "https://api.ws.sonos.com/control/api/v1/households/{household_id}/groups" + url = f"https://api.ws.sonos.com/control/api/v1/households/{household_id}/groups" payload = {} headers = {"Authorization": f"Bearer {token}"} @@ -100,7 +100,7 @@ def audio_clip(audioclip_url, player_id, token): "name": "Embla", "appId": "com.acme.app", "streamUrl": f"{audioclip_url}", - "volume": 30, + "volume": 50, "priority": "HIGH", "clipType": "CUSTOM", } diff --git a/routes/__init__.py b/routes/__init__.py index 00582e1c..320bc842 100755 --- a/routes/__init__.py +++ b/routes/__init__.py @@ -380,5 +380,4 @@ def get_status(task: str): from .words import * from .stats import * from .salescloud import * -from .sonos import * from nn.api import * From 87e193e4db5e852f28b48c502b703bdac3afe732 Mon Sep 17 00:00:00 2001 From: Logi E Date: Fri, 24 Jun 2022 10:49:47 +0000 Subject: [PATCH 094/371] Implemented TOML parsing --- queries/dialogue.py | 382 +++++++++++++-------------- queries/fruitseller/fruitseller.toml | 9 +- queries/fruitseller/fruitseller.yaml | 7 - 3 files changed, 190 insertions(+), 208 deletions(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index 00a9f591..57af1553 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -1,15 +1,14 @@ import json -from typing import Any, Dict, Union, List, Optional, cast +from typing import Any, Dict, Mapping, Union, List, Optional, cast from typing_extensions import TypedDict import os.path import datetime -import yaml -# try: -# import tomllib -# except ModuleNotFoundError: -# import tomli as tomllib +try: + import tomllib +except ModuleNotFoundError: + import tomli as tomllib from enum import IntEnum, auto from dataclasses import dataclass, field @@ -36,25 +35,47 @@ class DialogueStructureType(TypedDict): class ResourceState(IntEnum): """Enum representing the different states a dialogue resource can be in.""" + INITIAL = auto() UNFULFILLED = auto() PARTIALLY_FULFILLED = auto() FULFILLED = auto() CONFIRMED = auto() - # SKIPPED = auto() + PAUSED = auto() + SKIPPED = auto() -def load_dialogue_structure(filename: str) -> Any: - """Loads dialogue structure from YAML file.""" - basepath, _ = os.path.split(os.path.realpath(__file__)) - fpath = os.path.join(basepath, filename, filename + ".yaml") # TODO: Fix this - obj = None - with open(fpath, mode="r") as file: - obj = yaml.safe_load(file) - return obj +########################## +# RESOURCE CLASSES # +########################## -def list_items(items: Any) -> str: +@dataclass +class Resource: + name: str = "" + required: bool = True + data: Any = None + state: ResourceState = ResourceState.INITIAL + prompt: str = "" + type: str = "" + repeatable: bool = False + repeat_prompt: Optional[str] = None + confirm_prompt: Optional[str] = None + cancel_prompt: Optional[str] = None + _repeat_count: int = 0 + + def next_action(self) -> Any: + raise NotImplementedError() + + def generate_answer(self) -> str: + raise NotImplementedError() + + def update(self, new_data: Optional["Resource"]) -> None: + if new_data: + self.__dict__.update(new_data.__dict__) + + +def _list_items(items: Any) -> str: item_list: List[str] = [] for num, name in items: # TODO: get general plural form @@ -63,12 +84,142 @@ def list_items(items: Any) -> str: return natlang_seq(item_list) +@dataclass +class ListResource(Resource): + data: ListResourceType = field(default_factory=list) + available_options: Optional[ListResourceType] = None + + def list_available_options(self) -> str: + raise NotImplementedError() + + def generate_answer(self) -> str: + print("State: ", self.state) + ans: str = "" + if self.state is ResourceState.UNFULFILLED: + if self._repeat_count == 0 or not self.repeatable: + ans = self.prompt + if self.state is ResourceState.PARTIALLY_FULFILLED: + if self.repeat_prompt: + ans = ( + f"{self.repeat_prompt.format(list_items = _list_items(self.data))}" + ) + if self.state is ResourceState.FULFILLED: + if self.confirm_prompt: + ans = ( + f"{self.confirm_prompt.format(list_items = _list_items(self.data))}" + ) + return ans + + +# TODO: +# ExactlyOneResource (choose one resource from options) +# SetResource (a set of resources)? +# ... + + +@dataclass +class YesNoResource(Resource): + data: bool = False + + +@dataclass +class DatetimeResource(Resource): + data: List[Union[Optional[datetime.date], Optional[datetime.time]]] = field( + default_factory=lambda: [None, None] + ) + date_fulfilled_prompt: Optional[str] = None + time_fulfilled_prompt: Optional[str] = None + + def has_date(self) -> bool: + return isinstance(self.data[0], datetime.date) + + def has_time(self) -> bool: + return isinstance(self.data[1], datetime.time) + + def set_date(self, new_date: Optional[datetime.date] = None) -> None: + self.data[0] = new_date + + def set_time(self, new_time: Optional[datetime.time] = None) -> None: + self.data[1] = new_time + + def generate_answer(self) -> str: + ans = "" + if self.state is ResourceState.UNFULFILLED: + if self._repeat_count == 0 or not self.repeatable: + ans = self.prompt + if self.state is ResourceState.PARTIALLY_FULFILLED: + if self.data: + if len(self.data) > 0 and self.data[0] and self.date_fulfilled_prompt: + ans = self.date_fulfilled_prompt.format( + date=self.data[0].strftime("%Y/%m/%d") + ) + if len(self.data) > 1 and self.data[1] and self.time_fulfilled_prompt: + ans = self.time_fulfilled_prompt.format( + time=self.data[1].strftime("%H:%M") + ) + if self.state is ResourceState.FULFILLED: + if ( + self.data + and self.confirm_prompt + and len(self.data) == 2 + and self.data[0] + and self.data[1] + ): + ans = self.confirm_prompt.format( + date_time=datetime.datetime.combine( + cast(datetime.date, self.data[0]), + cast(datetime.time, self.data[1]), + ).strftime("%Y/%m/%d %H:%M") + ) + if self.state is ResourceState.CONFIRMED: + ans = "Pöntunin er staðfest." + return ans + + +@dataclass +class NumberResource(Resource): + data: int = 0 + + +############################## +# RESOURCE CLASSES END # +############################## + +_RESOURCE_TYPES: Mapping[str, Any] = { + "Resource": Resource, + "ListResource": ListResource, + "YesNoResource": YesNoResource, + "DatetimeResource": DatetimeResource, + "NumberResource": NumberResource, +} + + +def _load_dialogue_structure(filename: str) -> DialogueStructureType: + """Loads dialogue structure from TOML file.""" + basepath, _ = os.path.split(os.path.realpath(__file__)) + fpath = os.path.join(basepath, filename, filename + ".toml") + with open(fpath, mode="r") as file: + f = file.read() + obj: Dict[str, Any] = tomllib.loads(f) # type:ignore + assert "dialogue_name" in obj + assert "resources" in obj + for i, resource in enumerate(obj["resources"]): + assert "name" in resource + assert "type" in resource + obj["resources"][i] = _RESOURCE_TYPES[resource["type"]](**resource) + print("OBJ:", obj) + return cast(DialogueStructureType, obj) + + class DialogueStateManager: def __init__(self, dialogue_name: str, query: Query, result: Result): - self._dialogue_name = dialogue_name - self._q = query - self._result = result - self._saved_state = self._get_saved_dialogue_state() + self._dialogue_name: str = dialogue_name + self._q: Query = query + self._result: Result = result + self._saved_state: DialogueStructureType = self._get_saved_dialogue_state() + # TODO: CALL STACK! + # TODO: Delegate answering from a resource to another resource or to another dialogue + # TODO: í ávaxtasamtali "ég vil panta flug" "viltu að ég geymi ávaxtapöntunina eða eyði henni?" ... def not_in_dialogue(self, start_dialogue_qtype: str) -> bool: """Check if the client is in or wants to start this dialogue""" @@ -79,29 +230,22 @@ def not_in_dialogue(self, start_dialogue_qtype: str) -> bool: ) def setup_dialogue(self): - obj = load_dialogue_structure(self._dialogue_name) + obj = _load_dialogue_structure(self._dialogue_name) print(obj) self.resources: List[Resource] = [] for i, resource in enumerate(obj["resources"]): - newResource: Resource - if resource.get("type") == "ListResource": - newResource = ListResource(**resource) - else: - print(resource) - newResource = DatetimeResource(**resource) if self._saved_state and i < len(self._saved_state["resources"]): - newResource.update(self._saved_state["resources"][i]) - newResource.state = ResourceState( - self._saved_state["resources"][i].state - ) - self.resources.append(newResource) + resource.update(self._saved_state["resources"][i]) + resource.state = ResourceState(self._saved_state["resources"][i].state) + self.resources.append(resource) self.resourceState: Optional[Resource] = None self.ans: Optional[str] = None def start_dialogue(self): """Save client's state as having started this dialogue""" - self.set_dialogue_state( + # New empty dialogue state, with correct dialogue name + self._set_dialogue_state( { "dialogue_name": self._dialogue_name, "resources": [], @@ -110,13 +254,13 @@ def start_dialogue(self): def update_dialogue_state(self): """Update the dialogue state for a client""" - self.set_dialogue_state( + # Save resources to client data + self._set_dialogue_state( { "dialogue_name": self._dialogue_name, "resources": self.resources, } ) - # self.set_dialogue_state() def generate_answer(self, result: Result) -> str: i = 0 @@ -161,7 +305,7 @@ def _get_saved_dialogue_state(self) -> DialogueStructureType: ds = json.loads(ds_str, cls=DialogueJSONDecoder) return ds - def set_dialogue_state(self, ds: DialogueStructureType) -> None: + def _set_dialogue_state(self, ds: DialogueStructureType) -> None: """Save the state of a dialogue for a client""" ds_json: str = json.dumps(ds, cls=DialogueJSONEncoder) # Wrap data before saving dialogue state into client data @@ -177,25 +321,12 @@ def end_dialogue(self) -> None: class DialogueJSONEncoder(json.JSONEncoder): def default(self, o: Any) -> Any: - # Add JSON encoding for any new Resource classes here! - # CUSTOM RESOURCE CLASSES + # Add JSON encoding for any new classes here - # BASE RESOURCE CLASSES - if isinstance(o, ListResource): - d = o.__dict__.copy() - d["__type__"] = "ListResource" - return d - if isinstance(o, YesNoResource): - d = o.__dict__.copy() - d["__type__"] = "YesNoResource" - return d - if isinstance(o, DatetimeResource): - d = o.__dict__.copy() - d["__type__"] = "DatetimeResource" - return d - if isinstance(o, NumberResource): + if isinstance(o, Resource): + # CLASSES THAT INHERIT FROM RESOURCE d = o.__dict__.copy() - d["__type__"] = "NumberResource" + d["__type__"] = o.__class__.__name__ return d if isinstance(o, datetime.date): return { @@ -212,10 +343,6 @@ def default(self, o: Any) -> Any: "second": o.second, "microsecond": o.microsecond, } - if isinstance(o, Resource): - d = o.__dict__.copy() - d["__type__"] = "Resource" - return d return json.JSONEncoder.default(self, o) @@ -229,147 +356,8 @@ def dialogue_decoding(self, d: Dict[Any, Any]) -> Any: if "__type__" not in d: return d t = d.pop("__type__") - if t == "ListResource": - return ListResource(**d) - if t == "YesNoResource": - return YesNoResource(**d) - if t == "DatetimeResource": - return DatetimeResource(**d) - if t == "NumberResource": - return NumberResource(**d) if t == "date": return datetime.date(**d) if t == "time": return datetime.time(**d) - if t == "Resource": - return Resource(**d) - - -@dataclass -class Resource: - name: str = "" - required: bool = True - data: Any = None - state: ResourceState = ResourceState.INITIAL - prompt: str = "" - type: str = "" - repeatable: bool = False - repeat_prompt: Optional[str] = None - confirm_prompt: Optional[str] = None - cancel_prompt: Optional[str] = None - _repeat_count: int = 0 - - def next_action(self) -> Any: - raise NotImplementedError() - - def generate_answer(self) -> str: - raise NotImplementedError() - - def update(self, new_data: Optional["Resource"]) -> None: - if new_data: - self.__dict__.update(new_data.__dict__) - - -@dataclass -class ListResource(Resource): - data: ListResourceType = field(default_factory=list) - available_options: Optional[ListResourceType] = None - - def list_available_options(self) -> str: - raise NotImplementedError() - - def generate_answer(self) -> str: - print("State: ", self.state) - ans: str = "" - if self.state is ResourceState.UNFULFILLED: - if self._repeat_count == 0 or not self.repeatable: - ans = self.prompt - if self.state is ResourceState.PARTIALLY_FULFILLED: - if self.repeat_prompt: - ans = f"{self.repeat_prompt.format(list_items = list_items(self.data))}" - if self.state is ResourceState.FULFILLED: - if self.confirm_prompt: - ans = ( - f"{self.confirm_prompt.format(list_items = list_items(self.data))}" - ) - return ans - - -# TODO: -# ExactlyOneResource (choose one resource from options) -# SetResource (a set of resources)? -# ... - - -@dataclass -class YesNoResource(Resource): - data: bool = False - - -@dataclass -class DatetimeResource(Resource): - data: List[Union[Optional[datetime.date], Optional[datetime.time]]] = field(default_factory=lambda: [None, None]) - date_fulfilled_prompt: Optional[str] = None - time_fulfilled_prompt: Optional[str] = None - - def has_date(self) -> bool: - return isinstance(self.data[0], datetime.date) - - def has_time(self) -> bool: - return isinstance(self.data[1], datetime.time) - - def set_date(self, new_date: Optional[datetime.date] = None) -> None: - self.data[0] = new_date - - def set_time(self, new_time: Optional[datetime.time] = None) -> None: - self.data[1] = new_time - - def generate_answer(self) -> str: - ans = "" - if self.state is ResourceState.UNFULFILLED: - if self._repeat_count == 0 or not self.repeatable: - ans = self.prompt - if self.state is ResourceState.PARTIALLY_FULFILLED: - if self.data: - if len(self.data) > 0 and self.data[0] and self.date_fulfilled_prompt: - ans = self.date_fulfilled_prompt.format( - date=self.data[0].strftime("%Y/%m/%d") - ) - if len(self.data) > 1 and self.data[1] and self.time_fulfilled_prompt: - ans = self.time_fulfilled_prompt.format( - time=self.data[1].strftime("%H:%M") - ) - if self.state is ResourceState.FULFILLED: - if ( - self.data - and self.confirm_prompt - and len(self.data) == 2 - and self.data[0] - and self.data[1] - ): - ans = self.confirm_prompt.format( - date_time=datetime.datetime.combine( - cast(datetime.date, self.data[0]), - cast(datetime.time, self.data[1]), - ).strftime("%Y/%m/%d %H:%M") - ) - if self.state is ResourceState.CONFIRMED: - ans = "Pöntunin er staðfest." - return ans - - -@dataclass -class NumberResource(Resource): - data: int = 0 - - -""" Three classes implemented for each resource - class DataState(): - pass - - class PartiallyFulfilledState(): - pass - - class FulfillState(DataState): - pass -""" + return _RESOURCE_TYPES[t](**d) diff --git a/queries/fruitseller/fruitseller.toml b/queries/fruitseller/fruitseller.toml index 89be4954..2c1fa180 100644 --- a/queries/fruitseller/fruitseller.toml +++ b/queries/fruitseller/fruitseller.toml @@ -3,20 +3,21 @@ dialogue_name = "fruitseller" [[resources]] name = "Fruits" prompt = "Hvaða ávexti má bjóða þér?" -# resource_nonterminal: "QFruitList" type = "ListResource" repeatable = true -# verification_function: "check_fruits" confirm_prompt = "Viltu staðfesta ávextina {list_items}?" repeat_prompt = "Pöntunin samanstendur af {list_items}. Verður það eitthvað fleira?" [[resources]] name = "Date" type = "DatetimeResource" -# verification_function: "check_date" prompt = "Hvenær viltu fá ávextina?" time_fulfilled_prompt = "Afhendingin verður klukkan {time}. Hvaða dagsetningu viltu fá ávextina?" date_fulfilled_prompt = "Afhendingin verður {date}. Klukkan hvað viltu fá ávextina?" confirm_prompt = "Afhending pöntunar er {date_time}. Viltu staðfesta afhendinguna?" - +# [[resources]] +# name = "Final" +# type = "FinalResource" +# prompt = "Pöntunin þín er {fruits} og verður afhent {date_time}." +# requires = ["Fruits", "Date"] diff --git a/queries/fruitseller/fruitseller.yaml b/queries/fruitseller/fruitseller.yaml index 2e678f45..44de7e6a 100644 --- a/queries/fruitseller/fruitseller.yaml +++ b/queries/fruitseller/fruitseller.yaml @@ -1,20 +1,13 @@ dialogue_name: "fruitseller" -variables: - - &Success 1 - - &Cancel -1 -# Resources, in order resources: - name: "Fruits" prompt: "Hvaða ávexti má bjóða þér?" - # resource_nonterminal: "QFruitList" type: "ListResource" repeatable: true - # verification_function: "check_fruits" confirm_prompt: "Viltu staðfesta ávextina {list_items}?" repeat_prompt: "Pöntunin samanstendur af {list_items}. Verður það eitthvað fleira?" - name: "Date" type: "DatetimeResource" - # verification_function: "check_date" prompt: "Hvenær viltu fá ávextina?" time_fulfilled_prompt: "Afhendingin verður klukkan {time}. Hvaða dagsetningu viltu fá ávextina?" date_fulfilled_prompt: "Afhendingin verður {date}. Klukkan hvað viltu fá ávextina?" From c7feb1ebbfff677e0fa0653eb492c801fa8aee50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Fri, 24 Jun 2022 10:59:47 +0000 Subject: [PATCH 095/371] Initial YesNoResource --- queries/dialogue.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/queries/dialogue.py b/queries/dialogue.py index 57af1553..51cb90fc 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -120,6 +120,18 @@ def generate_answer(self) -> str: @dataclass class YesNoResource(Resource): data: bool = False + yes_answer: Optional[str] = None + no_answer: Optional[str] = None + + def generate_answer(self) -> str: + ans = "" + if self.data: + if self.yes_answer: + ans = self.yes_answer + else: + if self.no_answer: + ans = self.no_answer + return ans @dataclass From 941ea34029a15b33cb25447bf2204d28f85a4338 Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Fri, 24 Jun 2022 11:37:36 +0000 Subject: [PATCH 096/371] brought changes --- queries/iot_speakers.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/queries/iot_speakers.py b/queries/iot_speakers.py index ba7f5254..9b0d96e7 100644 --- a/queries/iot_speakers.py +++ b/queries/iot_speakers.py @@ -62,6 +62,9 @@ # ) # ) +# TOPIC_LEMMAS = [ +# "tónlist", +# ] # # This module wants to handle parse trees for queries # HANDLE_TREE = True From f86ec531c7bb9a9315ec054a8e524743a3c6581f Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Fri, 24 Jun 2022 11:37:36 +0000 Subject: [PATCH 097/371] brought changes --- queries/iot_speakers.py | 946 ++++++++++++++++++++-------------------- 1 file changed, 469 insertions(+), 477 deletions(-) diff --git a/queries/iot_speakers.py b/queries/iot_speakers.py index 9b0d96e7..1c2ea51c 100644 --- a/queries/iot_speakers.py +++ b/queries/iot_speakers.py @@ -1,414 +1,56 @@ -# """ +""" -# Greynir: Natural language processing for Icelandic + Greynir: Natural language processing for Icelandic -# Randomness query response module + Randomness query response module -# Copyright (C) 2022 Miðeind ehf. + Copyright (C) 2022 Miðeind ehf. -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. -# You should have received a copy of the GNU General Public License -# along with this program. If not, see http://www.gnu.org/licenses/. + You should have received a copy of the GNU General Public License + along with this program. If not, see http://www.gnu.org/licenses/. -# This query module handles queries related to the generation -# of random numbers, e.g. "Kastaðu tengingi", "Nefndu tölu milli 5 og 10", etc. + This query module handles queries related to the generation + of random numbers, e.g. "Kastaðu tengingi", "Nefndu tölu milli 5 og 10", etc. -# """ +""" -# # TODO: add "láttu", "hafðu", "litaðu", "kveiktu" functionality. -# # TODO: make the objects of sentences more modular, so that the same structure doesn't need to be written for each action -# # TODO: ditto the previous comment. make the initial non-terminals general and go into specifics at the terminal level instead. -# # TODO: substituion klósett, baðherbergi hugmyndÆ senda lista i javascript og profa i röð -# # TODO: Embla stores old javascript code cached which has caused errors -# # TODO: Cut down javascript sent to Embla -# # TODO: Two specified groups or lights. -# # TODO: No specified location -# # TODO: Fix scene issues +# TODO: add "láttu", "hafðu", "litaðu", "kveiktu" functionality. +# TODO: make the objects of sentences more modular, so that the same structure doesn't need to be written for each action +# TODO: ditto the previous comment. make the initial non-terminals general and go into specifics at the terminal level instead. +# TODO: substituion klósett, baðherbergi hugmyndÆ senda lista i javascript og profa i röð +# TODO: Embla stores old javascript code cached which has caused errors +# TODO: Cut down javascript sent to Embla +# TODO: Two specified groups or lights. +# TODO: No specified location +# TODO: Fix scene issues -# from typing import Dict, Mapping, Optional, cast -# from typing_extensions import TypedDict +from typing import Dict, Mapping, Optional, cast +from typing_extensions import TypedDict -# import logging -# import random -# import json -# import flask +import logging +import random +import json +import flask -# from query import Query, QueryStateDict, AnswerTuple -# from queries import gen_answer, read_jsfile, read_grammar_file -# from tree import Result, Node +from query import Query, QueryStateDict, AnswerTuple +from queries import gen_answer, read_jsfile, read_grammar_file +from tree import Result, Node -# _IoT_QTYPE = "IoT" - -# TOPIC_LEMMAS = [ -# "tónlist", -# ] - - -# def help_text(lemma: str) -> str: -# """Help text to return when query.py is unable to parse a query but -# one of the above lemmas is found in it""" -# return "Ég skil þig ef þú segir til dæmis: {0}.".format( -# random.choice( -# ("Hækkaðu í tónlistinni", "Kveiktu á tónlist", "Láttu vera tónlist") -# ) -# ) - -# TOPIC_LEMMAS = [ -# "tónlist", -# ] - -# # This module wants to handle parse trees for queries -# HANDLE_TREE = True - -# # The grammar nonterminals this module wants to handle -# QUERY_NONTERMINALS = {"QIoTSpeaker"} - -# # The context-free grammar for the queries recognized by this plug-in module -# # GRAMMAR = read_grammar_file("iot_hue") - -# GRAMMAR = f""" - -# /þgf = þgf -# /ef = ef - -# Query → -# QIoTSpeaker '?'? - -# QIoTSpeaker → -# QIoTSpeakerQuery - -# QIoTSpeakerQuery -> -# QIoTSpeakerMakeVerb QIoTSpeakerMakeRest -# | QIoTSpeakerSetVerb QIoTSpeakerSetRest -# | QIoTSpeakerChangeVerb QIoTSpeakerChangeRest -# | QIoTSpeakerLetVerb QIoTSpeakerLetRest -# | QIoTSpeakerTurnOnVerb QIoTSpeakerTurnOnRest -# | QIoTSpeakerTurnOffVerb QIoTSpeakerTurnOffRest -# | QIoTSpeakerIncreaseOrDecreaseVerb QIoTSpeakerIncreaseOrDecreaseRest - -# QIoTSpeakerMakeVerb -> -# 'gera:so'_bh - -# QIoTSpeakerSetVerb -> -# 'setja:so'_bh -# | 'stilla:so'_bh - -# QIoTSpeakerChangeVerb -> -# 'breyta:so'_bh - -# QIoTSpeakerLetVerb -> -# 'láta:so'_bh - -# QIoTSpeakerTurnOnVerb -> -# 'kveikja:so'_bh - -# QIoTSpeakerTurnOffVerb -> -# 'slökkva:so'_bh - -# QIoTSpeakerIncreaseOrDecreaseVerb -> -# QIoTSpeakerIncreaseVerb -# | QIoTSpeakerDecreaseVerb - -# QIoTSpeakerIncreaseVerb -> -# 'hækka:so'_bh -# | 'auka:so'_bh - -# QIoTSpeakerDecreaseVerb -> -# 'lækka:so'_bh -# | 'minnka:so'_bh - -# QCHANGEMakeRest -> -# # QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigMake -# # | QCHANGESubject/þf QCHANGEHvernigMake QCHANGEHvar? -# # | QCHANGEHvar? QCHANGESubject/þf QCHANGEHvernigMake -# # | QCHANGEHvar? QCHANGEHvernigMake QCHANGESubject/þf -# # | QCHANGEHvernigMake QCHANGESubject/þf QCHANGEHvar? -# # | QCHANGEHvernigMake QCHANGEHvar? QCHANGESubject/þf -# QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? - -# # TODO: Add support for "stilltu rauðan lit á ljósið í eldhúsinu" -# QCHANGESetRest -> -# # QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigSet -# # | QCHANGESubject/þf QCHANGEHvernigSet QCHANGEHvar? -# # | QCHANGEHvar? QCHANGESubject/þf QCHANGEHvernigSet -# # | QCHANGEHvar? QCHANGEHvernigSet QCHANGESubject/þf -# # | QCHANGEHvernigSet QCHANGESubject/þf QCHANGEHvar? -# # | QCHANGEHvernigSet QCHANGEHvar? QCHANGESubject/þf -# "á" QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? - -# QCHANGEChangeRest -> -# # QCHANGESubjectOne/þgf QCHANGEHvar? QCHANGEHvernigChange -# # | QCHANGESubjectOne/þgf QCHANGEHvernigChange QCHANGEHvar? -# # | QCHANGEHvar? QCHANGESubjectOne/þgf QCHANGEHvernigChange -# # | QCHANGEHvar? QCHANGEHvernigChange QCHANGESubjectOne/þgf -# # | QCHANGEHvernigChange QCHANGESubjectOne/þgf QCHANGEHvar? -# # | QCHANGEHvernigChange QCHANGEHvar? QCHANGESubjectOne/þgf - -# QCHANGELetRest -> -# QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigLet -# | QCHANGESubject/þf QCHANGEHvernigLet QCHANGEHvar? -# | QCHANGEHvar? QCHANGESubject/þf QCHANGEHvernigLet -# | QCHANGEHvar? QCHANGEHvernigLet QCHANGESubject/þf -# | QCHANGEHvernigLet QCHANGESubject/þf QCHANGEHvar? -# | QCHANGEHvernigLet QCHANGEHvar? QCHANGESubject/þf -# "vera" QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? - -# QCHANGETurnOnRest -> -# # QCHANGETurnOnLightsRest -# # | QCHANGEAHverju QCHANGEHvar? -# # | QCHANGEHvar? QCHANGEAHverju -# "á" QIoTSpeakerMusicWord/þgf QIoTSpeakerHvar? - -# # QCHANGETurnOnLightsRest -> -# # QCHANGELightSubject/þf QCHANGEHvar? -# # | QCHANGEHvar QCHANGELightSubject/þf? - -# # Would be good to add "slökktu á rauða litnum" functionality -# QCHANGETurnOffRest -> -# # QCHANGETurnOffLightsRest -# "á" QIoTSpeakerMusicWord/þgf QIoTSpeakerHvar? - -# # QCHANGETurnOffLightsRest -> -# # QCHANGELightSubject/þf QCHANGEHvar? -# # | QCHANGEHvar QCHANGELightSubject/þf? - -# # TODO: Make the subject categorization cleaner -# QCHANGEIncreaseOrDecreaseRest -> -# # QCHANGELightSubject/þf QCHANGEHvar? -# # | QCHANGEBrightnessSubject/þf QCHANGEHvar? -# QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? -# | "í" QIoTSpeakerMusicWord/þgf QIoTSpeakerHvar? - -# # QCHANGESubject/fall -> -# # QCHANGESubjectOne/fall -# # | QCHANGESubjectTwo/fall - -# QIoTMusicWord -> -# 'tónlist'/fall - -# # # TODO: Decide whether LightSubject/þgf should be accepted -# # QCHANGESubjectOne/fall -> -# # QCHANGELightSubject/fall -# # | QCHANGEColorSubject/fall -# # | QCHANGEBrightnessSubject/fall -# # | QCHANGESceneSubject/fall - -# # QCHANGESubjectTwo/fall -> -# # QCHANGEGroupNameSubject/fall # á bara að styðja "gerðu eldhúsið rautt", "gerðu eldhúsið rómó" "gerðu eldhúsið bjartara", t.d. - -# QIoTSpeakerHvar -> -# QIoTSpeakerLocationPreposition QIoTSpeakerGroupName/þgf - -# # QCHANGEHvernigMake -> -# # QCHANGEAnnadAndlag # gerðu litinn rauðan í eldhúsinu EÐA gerðu birtuna meiri í eldhúsinu -# # | QCHANGEAdHverju # gerðu litinn að rauðum í eldhúsinu -# # | QCHANGEThannigAd - -# # QCHANGEHvernigSet -> -# # QCHANGEAHvad -# # | QCHANGEThannigAd - -# # QCHANGEHvernigChange -> -# # QCHANGEIHvad -# # | QCHANGEThannigAd - -# # QCHANGEHvernigLet -> -# # QCHANGEBecome QCHANGESomethingOrSomehow -# # | QCHANGEBe QCHANGESomehow - -# # QCHANGEThannigAd -> -# # "þannig" "að"? pfn_nf QCHANGEBeOrBecomeSubjunctive QCHANGEAnnadAndlag - -# # I think these verbs only appear in these forms. -# # In which case these terminals should be deleted and a direct reference should be made in the relevant non-terminals. -# # QCHANGEBe -> -# # "vera" - -# # QCHANGEBecome -> -# # "verða" - -# # QCHANGEBeOrBecomeSubjunctive -> -# # "verði" -# # | "sé" - -# # QCHANGELightSubject/fall -> -# # QCHANGELight/fall - -# # QCHANGEColorSubject/fall -> -# # QCHANGEColorWord/fall QCHANGELight/ef? -# # | QCHANGEColorWord/fall "á" QCHANGELight/þgf - -# # QCHANGEBrightnessSubject/fall -> -# # QCHANGEBrightnessWord/fall QCHANGELight/ef? -# # | QCHANGEBrightnessWord/fall "á" QCHANGELight/þgf - -# # QCHANGESceneSubject/fall -> -# # QCHANGESceneWord/fall - -# # QCHANGEGroupNameSubject/fall -> -# # QCHANGEGroupName/fall - -# QIoTSpeakerLocationPreposition -> -# QIoTSpeakerLocationPrepositionFirstPart? QIoTSpeakerLocationPrepositionSecondPart - -# # The latter proverbs are grammatically incorrect, but common errors, both in speech and transcription. -# # The list provided is taken from StefnuAtv in Greynir.grammar. That includes "aftur:ao", which is not applicable here. -# QIoTSpeakerLocationPrepositionFirstPart -> -# StaðarAtv -# | "fram:ao" -# | "inn:ao" -# | "niður:ao" -# | "upp:ao" -# | "út:ao" - -# QIoTSpeakerLocationPrepositionSecondPart -> -# "á" | "í" - -# QIoTSpeakerGroupName/fall -> -# no/fall - -# # QCHANGELightName/fall -> -# # no/fall - -# # QCHANGEColorName -> -# # {" | ".join(f"'{color}:lo'" for color in _COLORS.keys())} - -# # QCHANGESceneName -> -# # no -# # | lo - -# # QCHANGEAnnadAndlag -> -# # QCHANGENewSetting/nf -# # | QCHANGESpyrjaHuldu/nf - -# # QCHANGEAdHverju -> -# # "að" QCHANGENewSetting/þgf - -# # QCHANGEAHvad -> -# # "á" QCHANGENewSetting/þf - -# # QCHANGEIHvad -> -# # "í" QCHANGENewSetting/þf - -# # QCHANGEAHverju -> -# # "á" QCHANGELight/þgf -# # | "á" QCHANGENewSetting/þgf - -# # QCHANGESomethingOrSomehow -> -# # QCHANGEAnnadAndlag -# # | QCHANGEAdHverju - -# # QCHANGESomehow -> -# # QCHANGEAnnadAndlag -# # | QCHANGEThannigAd - -# # QCHANGELight/fall -> -# # QCHANGELightName/fall -# # | QCHANGELightWord/fall - -# # # Should 'birta' be included -# # QCHANGELightWord/fall -> -# # 'ljós'/fall -# # | 'lýsing'/fall -# # | 'birta'/fall -# # | 'Birta'/fall - -# # QCHANGEColorWord/fall -> -# # 'litur'/fall -# # | 'litblær'/fall -# # | 'blær'/fall - -# # QCHANGEBrightnessWords/fall -> -# # 'bjartur'/fall -# # | QCHANGEBrightnessWord/fall - -# # QCHANGEBrightnessWord/fall -> -# # 'birta'/fall -# # | 'Birta'/fall -# # | 'birtustig'/fall - -# # QCHANGESceneWord/fall -> -# # 'sena'/fall -# # | 'stemning'/fall -# # | 'stemming'/fall -# # | 'stemmning'/fall - -# # # Need to ask Hulda how this works. -# # QCHANGESpyrjaHuldu/fall -> -# # # QCHANGEHuldaColor/fall -# # QCHANGEHuldaBrightness/fall -# # # | QCHANGEHuldaScene/fall - -# # # Do I need a "new light state" non-terminal? -# # QCHANGENewSetting/fall -> -# # QCHANGENewColor/fall -# # | QCHANGENewBrightness/fall -# # | QCHANGENewScene/fall - -# # # Missing "meira dimmt" -# # QCHANGEHuldaBrightness/fall -> -# # QCHANGEMoreBrighterOrHigher/fall QCHANGEBrightnessWords/fall? -# # | QCHANGELessDarkerOrLower/fall QCHANGEBrightnessWords/fall? - -# # #Unsure about whether to include /fall after QCHANGEColorName -# # QCHANGENewColor/fall -> -# # QCHANGEColorWord/fall QCHANGEColorName -# # | QCHANGEColorName QCHANGEColorWord/fall? - -# # QCHANGENewBrightness/fall -> -# # 'sá'/fall? QCHANGEBrightestOrDarkest/fall -# # | QCHANGEBrightestOrDarkest/fall QCHANGEBrightnessOrSettingWord/fall - -# # QCHANGENewScene/fall -> -# # QCHANGESceneWord/fall QCHANGESceneName -# # | QCHANGESceneName QCHANGESceneWord/fall? - -# # QCHANGEMoreBrighterOrHigher/fall -> -# # 'mikill:lo'_mst/fall -# # | 'bjartur:lo'_mst/fall -# # | 'ljós:lo'_mst/fall -# # | 'hár:lo'_mst/fall - -# # QCHANGELessDarkerOrLower/fall -> -# # 'lítill:lo'_mst/fall -# # | 'dökkur:lo'_mst/fall -# # | 'dimmur:lo'_mst/fall -# # | 'lágur:lo'_mst/fall - -# # QCHANGEBrightestOrDarkest/fall -> -# # QCHANGEBrightest/fall -# # | QCHANGEDarkest/fall - -# # QCHANGEBrightest/fall -> -# # 'bjartur:lo'_evb -# # | 'bjartur:lo'_esb -# # | 'ljós:lo'_evb -# # | 'ljós:lo'_esb - -# # QCHANGEDarkest/fall -> -# # 'dimmur:lo'_evb -# # | 'dimmur:lo'_esb -# # | 'dökkur:lo'_evb -# # | 'dökkur:lo'_esb - -# # QCHANGEBrightnessOrSettingWord/fall -> -# # QCHANGEBrightnessWord/fall -# # | QCHANGESettingWord/fall - -# # QCHANGESettingWord/fall -> -# # 'stilling'/fall - -# """ +_IoT_QTYPE = "IoT" +TOPIC_LEMMAS = [ + "tónlist", +] # def QIoTSpeakerIncreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: # result.action = "increase_volume" @@ -418,84 +60,434 @@ # result["hue_obj"]["bri_inc"] = 64 # result["hue_obj"]["on"] = True +def help_text(lemma: str) -> str: + """Help text to return when query.py is unable to parse a query but + one of the above lemmas is found in it""" + return "Ég skil þig ef þú segir til dæmis: {0}.".format( + random.choice( + ("Hækkaðu í tónlistinni", "Kveiktu á tónlist", "Láttu vera tónlist") + ) + ) + + +# This module wants to handle parse trees for queries +HANDLE_TREE = True + +# The grammar nonterminals this module wants to handle +QUERY_NONTERMINALS = {"QIoTSpeaker"} + +# The context-free grammar for the queries recognized by this plug-in module +# GRAMMAR = read_grammar_file("iot_hue") + +GRAMMAR = f""" + +/þgf = þgf +/ef = ef + +Query → + QIoTSpeaker '?'? + +QIoTSpeaker → + QIoTSpeakerQuery + +QIoTSpeakerQuery -> + QIoTSpeakerMakeVerb QIoTSpeakerMakeRest + | QIoTSpeakerSetVerb QIoTSpeakerSetRest + | QIoTSpeakerChangeVerb QIoTSpeakerChangeRest + | QIoTSpeakerLetVerb QIoTSpeakerLetRest + | QIoTSpeakerTurnOnVerb QIoTSpeakerTurnOnRest + | QIoTSpeakerTurnOffVerb QIoTSpeakerTurnOffRest + | QIoTSpeakerIncreaseOrDecreaseVerb QIoTSpeakerIncreaseOrDecreaseRest + +QIoTSpeakerMakeVerb -> + 'gera:so'_bh + +QIoTSpeakerSetVerb -> + 'setja:so'_bh + | 'stilla:so'_bh + +QIoTSpeakerChangeVerb -> + 'breyta:so'_bh + +QIoTSpeakerLetVerb -> + 'láta:so'_bh + +QIoTSpeakerTurnOnVerb -> + 'kveikja:so'_bh + +QIoTSpeakerTurnOffVerb -> + 'slökkva:so'_bh + +QIoTSpeakerIncreaseOrDecreaseVerb -> + QIoTSpeakerIncreaseVerb + | QIoTSpeakerDecreaseVerb + +QIoTSpeakerIncreaseVerb -> + 'hækka:so'_bh + | 'auka:so'_bh + +QIoTSpeakerDecreaseVerb -> + 'lækka:so'_bh + | 'minnka:so'_bh + +QCHANGEMakeRest -> + # QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigMake + # | QCHANGESubject/þf QCHANGEHvernigMake QCHANGEHvar? + # | QCHANGEHvar? QCHANGESubject/þf QCHANGEHvernigMake + # | QCHANGEHvar? QCHANGEHvernigMake QCHANGESubject/þf + # | QCHANGEHvernigMake QCHANGESubject/þf QCHANGEHvar? + # | QCHANGEHvernigMake QCHANGEHvar? QCHANGESubject/þf + QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? + +# TODO: Add support for "stilltu rauðan lit á ljósið í eldhúsinu" +QCHANGESetRest -> + # QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigSet + # | QCHANGESubject/þf QCHANGEHvernigSet QCHANGEHvar? + # | QCHANGEHvar? QCHANGESubject/þf QCHANGEHvernigSet + # | QCHANGEHvar? QCHANGEHvernigSet QCHANGESubject/þf + # | QCHANGEHvernigSet QCHANGESubject/þf QCHANGEHvar? + # | QCHANGEHvernigSet QCHANGEHvar? QCHANGESubject/þf + "á" QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? + +QCHANGEChangeRest -> + # QCHANGESubjectOne/þgf QCHANGEHvar? QCHANGEHvernigChange + # | QCHANGESubjectOne/þgf QCHANGEHvernigChange QCHANGEHvar? + # | QCHANGEHvar? QCHANGESubjectOne/þgf QCHANGEHvernigChange + # | QCHANGEHvar? QCHANGEHvernigChange QCHANGESubjectOne/þgf + # | QCHANGEHvernigChange QCHANGESubjectOne/þgf QCHANGEHvar? + # | QCHANGEHvernigChange QCHANGEHvar? QCHANGESubjectOne/þgf + +QCHANGELetRest -> + QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigLet + | QCHANGESubject/þf QCHANGEHvernigLet QCHANGEHvar? + | QCHANGEHvar? QCHANGESubject/þf QCHANGEHvernigLet + | QCHANGEHvar? QCHANGEHvernigLet QCHANGESubject/þf + | QCHANGEHvernigLet QCHANGESubject/þf QCHANGEHvar? + | QCHANGEHvernigLet QCHANGEHvar? QCHANGESubject/þf + "vera" QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? + +QCHANGETurnOnRest -> + # QCHANGETurnOnLightsRest + # | QCHANGEAHverju QCHANGEHvar? + # | QCHANGEHvar? QCHANGEAHverju + "á" QIoTSpeakerMusicWord/þgf QIoTSpeakerHvar? + +# QCHANGETurnOnLightsRest -> +# QCHANGELightSubject/þf QCHANGEHvar? +# | QCHANGEHvar QCHANGELightSubject/þf? + +# Would be good to add "slökktu á rauða litnum" functionality +QCHANGETurnOffRest -> + # QCHANGETurnOffLightsRest + "á" QIoTSpeakerMusicWord/þgf QIoTSpeakerHvar? + +# QCHANGETurnOffLightsRest -> +# QCHANGELightSubject/þf QCHANGEHvar? +# | QCHANGEHvar QCHANGELightSubject/þf? + +# TODO: Make the subject categorization cleaner +QCHANGEIncreaseOrDecreaseRest -> + # QCHANGELightSubject/þf QCHANGEHvar? + # | QCHANGEBrightnessSubject/þf QCHANGEHvar? + QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? + | "í" QIoTSpeakerMusicWord/þgf QIoTSpeakerHvar? + +# QCHANGESubject/fall -> +# QCHANGESubjectOne/fall +# | QCHANGESubjectTwo/fall + +QIoTMusicWord -> + 'tónlist'/fall + +# # TODO: Decide whether LightSubject/þgf should be accepted +# QCHANGESubjectOne/fall -> +# QCHANGELightSubject/fall +# | QCHANGEColorSubject/fall +# | QCHANGEBrightnessSubject/fall +# | QCHANGESceneSubject/fall + +# QCHANGESubjectTwo/fall -> +# QCHANGEGroupNameSubject/fall # á bara að styðja "gerðu eldhúsið rautt", "gerðu eldhúsið rómó" "gerðu eldhúsið bjartara", t.d. + +QIoTSpeakerHvar -> + QIoTSpeakerLocationPreposition QIoTSpeakerGroupName/þgf + +# QCHANGEHvernigMake -> +# QCHANGEAnnadAndlag # gerðu litinn rauðan í eldhúsinu EÐA gerðu birtuna meiri í eldhúsinu +# | QCHANGEAdHverju # gerðu litinn að rauðum í eldhúsinu +# | QCHANGEThannigAd + +# QCHANGEHvernigSet -> +# QCHANGEAHvad +# | QCHANGEThannigAd + +# QCHANGEHvernigChange -> +# QCHANGEIHvad +# | QCHANGEThannigAd + +# QCHANGEHvernigLet -> +# QCHANGEBecome QCHANGESomethingOrSomehow +# | QCHANGEBe QCHANGESomehow + +# QCHANGEThannigAd -> +# "þannig" "að"? pfn_nf QCHANGEBeOrBecomeSubjunctive QCHANGEAnnadAndlag + +# I think these verbs only appear in these forms. +# In which case these terminals should be deleted and a direct reference should be made in the relevant non-terminals. +# QCHANGEBe -> +# "vera" + +# QCHANGEBecome -> +# "verða" + +# QCHANGEBeOrBecomeSubjunctive -> +# "verði" +# | "sé" + +# QCHANGELightSubject/fall -> +# QCHANGELight/fall + +# QCHANGEColorSubject/fall -> +# QCHANGEColorWord/fall QCHANGELight/ef? +# | QCHANGEColorWord/fall "á" QCHANGELight/þgf + +# QCHANGEBrightnessSubject/fall -> +# QCHANGEBrightnessWord/fall QCHANGELight/ef? +# | QCHANGEBrightnessWord/fall "á" QCHANGELight/þgf + +# QCHANGESceneSubject/fall -> +# QCHANGESceneWord/fall + +# QCHANGEGroupNameSubject/fall -> +# QCHANGEGroupName/fall + +QIoTSpeakerLocationPreposition -> + QIoTSpeakerLocationPrepositionFirstPart? QIoTSpeakerLocationPrepositionSecondPart + +# The latter proverbs are grammatically incorrect, but common errors, both in speech and transcription. +# The list provided is taken from StefnuAtv in Greynir.grammar. That includes "aftur:ao", which is not applicable here. +QIoTSpeakerLocationPrepositionFirstPart -> + StaðarAtv + | "fram:ao" + | "inn:ao" + | "niður:ao" + | "upp:ao" + | "út:ao" + +QIoTSpeakerLocationPrepositionSecondPart -> + "á" | "í" + +QIoTSpeakerGroupName/fall -> + no/fall -# def QIoTSpeakerDecreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: -# result.action = "decrease_volume" -# if "hue_obj" not in result: -# result["hue_obj"] = {"bri_inc": -64} -# else: -# result["hue_obj"]["bri_inc"] = -64 - - -# def QIoTSpeakerGroupName(node: Node, params: QueryStateDict, result: Result) -> None: -# result["group_name"] = result._indefinite - - -# def sentence(state: QueryStateDict, result: Result) -> None: -# """Called when sentence processing is complete""" -# q: Query = state["query"] - -# q.set_qtype(result.get["qtype"]) - -# smartdevice_type = "smartSpeaker" - -# # Fetch relevant data from the device_data table to perform an action on the lights -# device_data = cast(Optional[DeviceData], q.client_data(smartdevice_type)) - -# selected_light: Optional[str] = None -# print("selected light:", selected_light) -# hue_credentials: Optional[Dict[str, str]] = None - -# if device_data is not None and smartdevice_type in device_data: -# dev = device_data[smartdevice_type] -# assert dev is not None -# selected_light = dev.get("selected_light") -# hue_credentials = dev.get("philips_hue") -# bridge_ip = hue_credentials.get("ipAddress") -# username = hue_credentials.get("username") - -# if not device_data or not hue_credentials: -# answer = "Það vantar að tengja Philips Hub-inn." -# q.set_answer(*gen_answer(answer)) -# return - -# # Successfully matched a query type -# print("bridge_ip: ", bridge_ip) -# print("username: ", username) -# print("selected light :", selected_light) -# print("hue credentials :", hue_credentials) - -# try: -# # kalla í javascripts stuff -# light_or_group_name = result.get("light_name", result.get("group_name", "")) -# color_name = result.get("color_name", "") -# print("GROUP NAME:", light_or_group_name) -# print("COLOR NAME:", color_name) -# print(result.hue_obj) -# q.set_answer( -# *gen_answer( -# "ég var að kveikja ljósin! " -# # + group_name -# # + " " -# # + color_name -# # + " " -# # + result.action -# # + " " -# # + str(result.hue_obj.get("hue", "enginn litur")) -# ) -# ) -# js = ( -# read_jsfile("IoT_Embla/fuse.js") -# + f"var BRIDGE_IP = '{bridge_ip}';var USERNAME = '{username}';" -# + read_jsfile("IoT_Embla/Philips_Hue/fuse_search.js") -# + read_jsfile("IoT_Embla/Philips_Hue/lights.js") -# + read_jsfile("IoT_Embla/Philips_Hue/set_lights.js") -# ) -# js += f"setLights('{light_or_group_name}', '{json.dumps(result.hue_obj)}');" -# q.set_command(js) -# except Exception as e: -# logging.warning("Exception while processing random query: {0}".format(e)) -# q.set_error("E_EXCEPTION: {0}".format(e)) -# raise - -# # f"var BRIDGE_IP = '192.168.1.68';var USERNAME = 'p3obluiXT13IbHMpp4X63ZvZnpNRdbqqMt723gy2';" +# QCHANGELightName/fall -> +# no/fall + +# QCHANGEColorName -> +# {" | ".join(f"'{color}:lo'" for color in _COLORS.keys())} + +# QCHANGESceneName -> +# no +# | lo + +# QCHANGEAnnadAndlag -> +# QCHANGENewSetting/nf +# | QCHANGESpyrjaHuldu/nf + +# QCHANGEAdHverju -> +# "að" QCHANGENewSetting/þgf + +# QCHANGEAHvad -> +# "á" QCHANGENewSetting/þf + +# QCHANGEIHvad -> +# "í" QCHANGENewSetting/þf + +# QCHANGEAHverju -> +# "á" QCHANGELight/þgf +# | "á" QCHANGENewSetting/þgf + +# QCHANGESomethingOrSomehow -> +# QCHANGEAnnadAndlag +# | QCHANGEAdHverju + +# QCHANGESomehow -> +# QCHANGEAnnadAndlag +# | QCHANGEThannigAd + +# QCHANGELight/fall -> +# QCHANGELightName/fall +# | QCHANGELightWord/fall + +# # Should 'birta' be included +# QCHANGELightWord/fall -> +# 'ljós'/fall +# | 'lýsing'/fall +# | 'birta'/fall +# | 'Birta'/fall + +# QCHANGEColorWord/fall -> +# 'litur'/fall +# | 'litblær'/fall +# | 'blær'/fall + +# QCHANGEBrightnessWords/fall -> +# 'bjartur'/fall +# | QCHANGEBrightnessWord/fall + +# QCHANGEBrightnessWord/fall -> +# 'birta'/fall +# | 'Birta'/fall +# | 'birtustig'/fall + +# QCHANGESceneWord/fall -> +# 'sena'/fall +# | 'stemning'/fall +# | 'stemming'/fall +# | 'stemmning'/fall + +# # Need to ask Hulda how this works. +# QCHANGESpyrjaHuldu/fall -> +# # QCHANGEHuldaColor/fall +# QCHANGEHuldaBrightness/fall +# # | QCHANGEHuldaScene/fall + +# # Do I need a "new light state" non-terminal? +# QCHANGENewSetting/fall -> +# QCHANGENewColor/fall +# | QCHANGENewBrightness/fall +# | QCHANGENewScene/fall + +# # Missing "meira dimmt" +# QCHANGEHuldaBrightness/fall -> +# QCHANGEMoreBrighterOrHigher/fall QCHANGEBrightnessWords/fall? +# | QCHANGELessDarkerOrLower/fall QCHANGEBrightnessWords/fall? + +# #Unsure about whether to include /fall after QCHANGEColorName +# QCHANGENewColor/fall -> +# QCHANGEColorWord/fall QCHANGEColorName +# | QCHANGEColorName QCHANGEColorWord/fall? + +# QCHANGENewBrightness/fall -> +# 'sá'/fall? QCHANGEBrightestOrDarkest/fall +# | QCHANGEBrightestOrDarkest/fall QCHANGEBrightnessOrSettingWord/fall + +# QCHANGENewScene/fall -> +# QCHANGESceneWord/fall QCHANGESceneName +# | QCHANGESceneName QCHANGESceneWord/fall? + +# QCHANGEMoreBrighterOrHigher/fall -> +# 'mikill:lo'_mst/fall +# | 'bjartur:lo'_mst/fall +# | 'ljós:lo'_mst/fall +# | 'hár:lo'_mst/fall + +# QCHANGELessDarkerOrLower/fall -> +# 'lítill:lo'_mst/fall +# | 'dökkur:lo'_mst/fall +# | 'dimmur:lo'_mst/fall +# | 'lágur:lo'_mst/fall + +# QCHANGEBrightestOrDarkest/fall -> +# QCHANGEBrightest/fall +# | QCHANGEDarkest/fall + +# QCHANGEBrightest/fall -> +# 'bjartur:lo'_evb +# | 'bjartur:lo'_esb +# | 'ljós:lo'_evb +# | 'ljós:lo'_esb + +# QCHANGEDarkest/fall -> +# 'dimmur:lo'_evb +# | 'dimmur:lo'_esb +# | 'dökkur:lo'_evb +# | 'dökkur:lo'_esb + +# QCHANGEBrightnessOrSettingWord/fall -> +# QCHANGEBrightnessWord/fall +# | QCHANGESettingWord/fall + +# QCHANGESettingWord/fall -> +# 'stilling'/fall + +""" + + +def QIoTSpeakerIncreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: + result.action = "increase_volume" + + +def QIoTSpeakerDecreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: + result.action = "decrease_volume" + + +def QIoTSpeakerGroupName(node: Node, params: QueryStateDict, result: Result) -> None: + result["group_name"] = result._indefinite + + +def sentence(state: QueryStateDict, result: Result) -> None: + """Called when sentence processing is complete""" + q: Query = state["query"] + + q.set_qtype(result.get["qtype"]) + + smartdevice_type = "smartSpeaker" + + # Fetch relevant data from the device_data table to perform an action on the lights + device_data = cast(Optional[DeviceData], q.client_data(smartdevice_type)) + + + if device_data is not None and smartdevice_type in device_data: + dev = device_data[smartdevice_type] + assert dev is not None + selected_light = dev.get("selected_light") + hue_credentials = dev.get("philips_hue") + bridge_ip = hue_credentials.get("ipAddress") + username = hue_credentials.get("username") + + if not device_data or not hue_credentials: + answer = "Það vantar að tengja Philips Hub-inn." + q.set_answer(*gen_answer(answer)) + return + + # Successfully matched a query type + print("bridge_ip: ", bridge_ip) + print("username: ", username) + print("selected light :", selected_light) + print("hue credentials :", hue_credentials) + + try: + # kalla í javascripts stuff + light_or_group_name = result.get("light_name", result.get("group_name", "")) + color_name = result.get("color_name", "") + print("GROUP NAME:", light_or_group_name) + print("COLOR NAME:", color_name) + print(result.hue_obj) + q.set_answer( + *gen_answer( + "ég var að kveikja ljósin! " + # + group_name + # + " " + # + color_name + # + " " + # + result.action + # + " " + # + str(result.hue_obj.get("hue", "enginn litur")) + ) + ) + js = ( + read_jsfile("IoT_Embla/fuse.js") + + f"var BRIDGE_IP = '{bridge_ip}';var USERNAME = '{username}';" + + read_jsfile("IoT_Embla/Philips_Hue/fuse_search.js") + + read_jsfile("IoT_Embla/Philips_Hue/lights.js") + + read_jsfile("IoT_Embla/Philips_Hue/set_lights.js") + ) + js += f"setLights('{light_or_group_name}', '{json.dumps(result.hue_obj)}');" + q.set_command(js) + except Exception as e: + logging.warning("Exception while processing random query: {0}".format(e)) + q.set_error("E_EXCEPTION: {0}".format(e)) + raise + + # f"var BRIDGE_IP = '192.168.1.68';var USERNAME = 'p3obluiXT13IbHMpp4X63ZvZnpNRdbqqMt723gy2';" From 43ba5616656bfe02100774346aa5330a0fb041a6 Mon Sep 17 00:00:00 2001 From: Logi E Date: Fri, 24 Jun 2022 14:20:35 +0000 Subject: [PATCH 098/371] Refactoring dialogue.py --- queries/dialogue.py | 165 +++++++++++++++++++-------- queries/fruitseller/fruitseller.toml | 14 ++- 2 files changed, 129 insertions(+), 50 deletions(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index 51cb90fc..0372bdea 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -21,18 +21,12 @@ DIALOGUE_KEY = "dialogue" DIALOGUE_DATA_KEY = "dialogue_data" EMPTY_DIALOGUE_DATA = "{}" +FINAL_RESOURCE_NAME = "Final" ResourceType = Union[str, int, float, bool, datetime.datetime, None] ListResourceType = List[ResourceType] -class DialogueStructureType(TypedDict): - """ """ - - dialogue_name: str - resources: List["Resource"] - - class ResourceState(IntEnum): """Enum representing the different states a dialogue resource can be in.""" @@ -53,7 +47,6 @@ class ResourceState(IntEnum): @dataclass class Resource: name: str = "" - required: bool = True data: Any = None state: ResourceState = ResourceState.INITIAL prompt: str = "" @@ -62,18 +55,34 @@ class Resource: repeat_prompt: Optional[str] = None confirm_prompt: Optional[str] = None cancel_prompt: Optional[str] = None + requires: List[str] = field(default_factory=list) _repeat_count: int = 0 def next_action(self) -> Any: raise NotImplementedError() - def generate_answer(self) -> str: + def generate_answer(self, dsm: "DialogueStateManager", result: Result) -> str: raise NotImplementedError() def update(self, new_data: Optional["Resource"]) -> None: if new_data: self.__dict__.update(new_data.__dict__) + def _execute_children( + self, dsm: "DialogueStateManager", result: Result + ) -> Optional[str]: + if "callbacks" in result: + while len(result.callbacks) > 0: + rnames, cb = result.callbacks.pop(0) + if self.name in rnames: + cb(self, result) + if self.requires: + for rname in self.requires: + resource = dsm.get_resource(rname) + if resource.state is not ResourceState.CONFIRMED: + return resource.generate_answer(dsm, result) + return None + def _list_items(items: Any) -> str: item_list: List[str] = [] @@ -92,9 +101,12 @@ class ListResource(Resource): def list_available_options(self) -> str: raise NotImplementedError() - def generate_answer(self) -> str: - print("State: ", self.state) - ans: str = "" + def generate_answer(self, dsm: "DialogueStateManager", result: Result) -> str: + ans: Optional[str] = self._execute_children(dsm, result) + if ans: + return ans + if self.state is ResourceState.INITIAL: + self.state = ResourceState.UNFULFILLED if self.state is ResourceState.UNFULFILLED: if self._repeat_count == 0 or not self.repeatable: ans = self.prompt @@ -108,6 +120,8 @@ def generate_answer(self) -> str: ans = ( f"{self.confirm_prompt.format(list_items = _list_items(self.data))}" ) + if ans is None: + raise ValueError("No answer generated") return ans @@ -123,14 +137,28 @@ class YesNoResource(Resource): yes_answer: Optional[str] = None no_answer: Optional[str] = None - def generate_answer(self) -> str: - ans = "" + def set_yes(self): + self.data = True + self.state = ResourceState.CONFIRMED + + def set_no(self): + self.data = False + self.state = ResourceState.CONFIRMED + + def generate_answer(self, dsm: "DialogueStateManager", result: Result) -> str: + ans: Optional[str] = self._execute_children(dsm, result) + if ans: + return ans + if self.state is ResourceState.INITIAL: + self.state = ResourceState.UNFULFILLED if self.data: if self.yes_answer: ans = self.yes_answer else: if self.no_answer: ans = self.no_answer + if ans is None: + raise ValueError("No answer generated") return ans @@ -154,8 +182,12 @@ def set_date(self, new_date: Optional[datetime.date] = None) -> None: def set_time(self, new_time: Optional[datetime.time] = None) -> None: self.data[1] = new_time - def generate_answer(self) -> str: - ans = "" + def generate_answer(self, dsm: "DialogueStateManager", result: Result) -> str: + ans: Optional[str] = self._execute_children(dsm, result) + if ans: + return ans + if self.state is ResourceState.INITIAL: + self.state = ResourceState.UNFULFILLED if self.state is ResourceState.UNFULFILLED: if self._repeat_count == 0 or not self.repeatable: ans = self.prompt @@ -185,6 +217,8 @@ def generate_answer(self) -> str: ) if self.state is ResourceState.CONFIRMED: ans = "Pöntunin er staðfest." + if ans is None: + raise ValueError("No answer generated") return ans @@ -193,16 +227,43 @@ class NumberResource(Resource): data: int = 0 +@dataclass +class OrResource(Resource): + data: Dict[str, Any] = field(default_factory=dict) + exclusive: bool = False # Only one of the resources should be fulfilled + + +@dataclass +class FinalResource(Resource): + data: Any = None + final_prompt: str = "" + + def generate_answer(self, dsm: "DialogueStateManager", result: Result) -> str: + ans: Optional[str] = self._execute_children(dsm, result) + if ans: + return ans + return self.final_prompt + + ############################## # RESOURCE CLASSES END # ############################## + +class DialogueStructureType(TypedDict): + """ """ + + dialogue_name: str + resources: List[Resource] + + _RESOURCE_TYPES: Mapping[str, Any] = { "Resource": Resource, "ListResource": ListResource, "YesNoResource": YesNoResource, "DatetimeResource": DatetimeResource, "NumberResource": NumberResource, + "FinalResource": FinalResource, } @@ -219,7 +280,6 @@ def _load_dialogue_structure(filename: str) -> DialogueStructureType: assert "name" in resource assert "type" in resource obj["resources"][i] = _RESOURCE_TYPES[resource["type"]](**resource) - print("OBJ:", obj) return cast(DialogueStructureType, obj) @@ -228,7 +288,9 @@ def __init__(self, dialogue_name: str, query: Query, result: Result): self._dialogue_name: str = dialogue_name self._q: Query = query self._result: Result = result + self._resources: Dict[str, Resource] = {} self._saved_state: DialogueStructureType = self._get_saved_dialogue_state() + self._data: Dict[str, Any] = {} # TODO: CALL STACK! # TODO: Delegate answering from a resource to another resource or to another dialogue # TODO: í ávaxtasamtali "ég vil panta flug" "viltu að ég geymi ávaxtapöntunina eða eyði henni?" ... @@ -244,12 +306,11 @@ def not_in_dialogue(self, start_dialogue_qtype: str) -> bool: def setup_dialogue(self): obj = _load_dialogue_structure(self._dialogue_name) print(obj) - self.resources: List[Resource] = [] for i, resource in enumerate(obj["resources"]): if self._saved_state and i < len(self._saved_state["resources"]): resource.update(self._saved_state["resources"][i]) resource.state = ResourceState(self._saved_state["resources"][i].state) - self.resources.append(resource) + self._resources[resource.name] = resource self.resourceState: Optional[Resource] = None self.ans: Optional[str] = None @@ -270,37 +331,49 @@ def update_dialogue_state(self): self._set_dialogue_state( { "dialogue_name": self._dialogue_name, - "resources": self.resources, + "resources": list(self._resources.values()), } ) + def get_resource(self, name: str) -> Resource: + return self._resources[name] + def generate_answer(self, result: Result) -> str: - i = 0 - while i < len(self.resources): - resource = self.resources[i] - if resource.required and resource.state is not ResourceState.CONFIRMED: - if resource.state is ResourceState.INITIAL: - resource.state = ResourceState.UNFULFILLED - if "callbacks" in result: - while len(result.callbacks) > 0: - r_name, cb = result.callbacks.pop(0) - if self.resources[i].name in r_name: - cb(resource, result) - else: - while i > -1: - i -= 1 - if self.resources[i].name in r_name: - cb(self.resources[i], result) - break - if ( - resource.state is ResourceState.CONFIRMED - and resource != self.resources[-1] - ): - i += 1 - continue - return self.resources[i].generate_answer() - i += 1 - return "Upp kom villa, reyndu aftur." + # if self._resources[FINAL_RESOURCE_NAME].state is not ResourceState.CONFIRMED: + ans = self._resources[FINAL_RESOURCE_NAME].generate_answer(self, result) + if self._resources[FINAL_RESOURCE_NAME].state is ResourceState.CONFIRMED: + # Final callback (performing some operation with the dialogue's data) + # should be called before ending dialogue + self.end_dialogue() + return ans + + # i = 0 + # while i < len(self.resources): + # resource = self.resources[i] + # if resource.required and resource.state is not ResourceState.CONFIRMED: + # if resource.state is ResourceState.INITIAL: + # resource.state = ResourceState.UNFULFILLED + # if "callbacks" in result: + # while len(result.callbacks) > 0: + # r_name, cb = result.callbacks.pop(0) + # if self.resources[i].name in r_name: + # cb(resource, result) + # else: + # while i > -1: + # i -= 1 + # if self.resources[i].name in r_name: + # cb(self.resources[i], result) + # break + # if ( + # resource.state is ResourceState.CONFIRMED + # and resource != self.resources[-1] + # ): + # self._data[resource.name] = resource.data + # i += 1 + # continue + # return self.resources[i].generate_answer() + # i += 1 + # return "Upp kom villa, reyndu aftur." def _get_saved_dialogue_state(self) -> DialogueStructureType: """Load the dialogue state for a client""" diff --git a/queries/fruitseller/fruitseller.toml b/queries/fruitseller/fruitseller.toml index 2c1fa180..573babd9 100644 --- a/queries/fruitseller/fruitseller.toml +++ b/queries/fruitseller/fruitseller.toml @@ -5,7 +5,7 @@ name = "Fruits" prompt = "Hvaða ávexti má bjóða þér?" type = "ListResource" repeatable = true -confirm_prompt = "Viltu staðfesta ávextina {list_items}?" +confirm_prompt = "Viltu staðfesta ávextina {list_items}?" repeat_prompt = "Pöntunin samanstendur af {list_items}. Verður það eitthvað fleira?" [[resources]] @@ -17,7 +17,13 @@ date_fulfilled_prompt = "Afhendingin verður {date}. Klukkan hvað viltu fá áv confirm_prompt = "Afhending pöntunar er {date_time}. Viltu staðfesta afhendinguna?" # [[resources]] -# name = "Final" -# type = "FinalResource" -# prompt = "Pöntunin þín er {fruits} og verður afhent {date_time}." +# name = "ConfirmOrder" +# type = "YesNoResource" +# prompt = "Viltu staðfesta þessa pöntun?" # requires = ["Fruits", "Date"] + +[[resources]] +name = "Final" +type = "FinalResource" +prompt = "Pöntunin þín er {fruits} og verður afhent {date_time}." +requires = ["Fruits", "Date"] From ceb54f5792abb11f359bff806b68fda2ebc32551 Mon Sep 17 00:00:00 2001 From: Logi E Date: Fri, 24 Jun 2022 15:48:50 +0000 Subject: [PATCH 099/371] Works! :) --- queries/dialogue.py | 100 ++++++++++++++---------- queries/fruitseller/fruitseller.toml | 2 +- queries/fruitseller_module.py | 111 +++++++++++---------------- 3 files changed, 106 insertions(+), 107 deletions(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index 0372bdea..5471787e 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -1,5 +1,5 @@ import json -from typing import Any, Dict, Mapping, Union, List, Optional, cast +from typing import Any, Callable, Dict, Mapping, Tuple, Union, List, Optional, cast from typing_extensions import TypedDict import os.path @@ -25,12 +25,13 @@ ResourceType = Union[str, int, float, bool, datetime.datetime, None] ListResourceType = List[ResourceType] +CallbackType = Callable[["Resource", Result], None] +CallbackTupleType = Tuple[Tuple[str, ...], CallbackType] class ResourceState(IntEnum): """Enum representing the different states a dialogue resource can be in.""" - INITIAL = auto() UNFULFILLED = auto() PARTIALLY_FULFILLED = auto() FULFILLED = auto() @@ -48,7 +49,7 @@ class ResourceState(IntEnum): class Resource: name: str = "" data: Any = None - state: ResourceState = ResourceState.INITIAL + state: ResourceState = ResourceState.UNFULFILLED prompt: str = "" type: str = "" repeatable: bool = False @@ -61,26 +62,49 @@ class Resource: def next_action(self) -> Any: raise NotImplementedError() - def generate_answer(self, dsm: "DialogueStateManager", result: Result) -> str: + def generate_answer( + self, dsm: "DialogueStateManager", result: Result + ) -> Optional[str]: raise NotImplementedError() def update(self, new_data: Optional["Resource"]) -> None: if new_data: self.__dict__.update(new_data.__dict__) - def _execute_children( - self, dsm: "DialogueStateManager", result: Result - ) -> Optional[str]: + def _execute_callbacks(self, dsm: "DialogueStateManager", result: Result) -> None: + if self.requires: + for rname in self.requires: + resource = dsm.get_resource(rname) + resource._execute_callbacks(dsm, result) + if "callbacks" in result: - while len(result.callbacks) > 0: - rnames, cb = result.callbacks.pop(0) + i = 0 + while i < len(result["callbacks"]): + rnames, cb = result.callbacks[i] if self.name in rnames: cb(self, result) + if len(rnames) > 1: + # Remove the current resource name from rnames + # if it is not the last one + result.callbacks[i] = ( + tuple(rn for rn in rnames if rn != self.name), + cb, + ) + else: + result.callbacks.pop(i) + i -= 1 + i += 1 + + def _get_child_answer( + self, dsm: "DialogueStateManager", result: Result + ) -> Optional[str]: if self.requires: for rname in self.requires: resource = dsm.get_resource(rname) if resource.state is not ResourceState.CONFIRMED: - return resource.generate_answer(dsm, result) + ans = resource.generate_answer(dsm, result) + if ans: + return ans return None @@ -101,12 +125,12 @@ class ListResource(Resource): def list_available_options(self) -> str: raise NotImplementedError() - def generate_answer(self, dsm: "DialogueStateManager", result: Result) -> str: - ans: Optional[str] = self._execute_children(dsm, result) + def generate_answer( + self, dsm: "DialogueStateManager", result: Result + ) -> Optional[str]: + ans: Optional[str] = self._get_child_answer(dsm, result) if ans: return ans - if self.state is ResourceState.INITIAL: - self.state = ResourceState.UNFULFILLED if self.state is ResourceState.UNFULFILLED: if self._repeat_count == 0 or not self.repeatable: ans = self.prompt @@ -120,8 +144,6 @@ def generate_answer(self, dsm: "DialogueStateManager", result: Result) -> str: ans = ( f"{self.confirm_prompt.format(list_items = _list_items(self.data))}" ) - if ans is None: - raise ValueError("No answer generated") return ans @@ -145,12 +167,12 @@ def set_no(self): self.data = False self.state = ResourceState.CONFIRMED - def generate_answer(self, dsm: "DialogueStateManager", result: Result) -> str: - ans: Optional[str] = self._execute_children(dsm, result) + def generate_answer( + self, dsm: "DialogueStateManager", result: Result + ) -> Optional[str]: + ans: Optional[str] = self._get_child_answer(dsm, result) if ans: return ans - if self.state is ResourceState.INITIAL: - self.state = ResourceState.UNFULFILLED if self.data: if self.yes_answer: ans = self.yes_answer @@ -182,22 +204,22 @@ def set_date(self, new_date: Optional[datetime.date] = None) -> None: def set_time(self, new_time: Optional[datetime.time] = None) -> None: self.data[1] = new_time - def generate_answer(self, dsm: "DialogueStateManager", result: Result) -> str: - ans: Optional[str] = self._execute_children(dsm, result) + def generate_answer( + self, dsm: "DialogueStateManager", result: Result + ) -> Optional[str]: + ans: Optional[str] = self._get_child_answer(dsm, result) if ans: return ans - if self.state is ResourceState.INITIAL: - self.state = ResourceState.UNFULFILLED if self.state is ResourceState.UNFULFILLED: if self._repeat_count == 0 or not self.repeatable: ans = self.prompt if self.state is ResourceState.PARTIALLY_FULFILLED: if self.data: - if len(self.data) > 0 and self.data[0] and self.date_fulfilled_prompt: + if self.has_date() and self.date_fulfilled_prompt: ans = self.date_fulfilled_prompt.format( date=self.data[0].strftime("%Y/%m/%d") ) - if len(self.data) > 1 and self.data[1] and self.time_fulfilled_prompt: + if self.has_time() and self.time_fulfilled_prompt: ans = self.time_fulfilled_prompt.format( time=self.data[1].strftime("%H:%M") ) @@ -205,9 +227,8 @@ def generate_answer(self, dsm: "DialogueStateManager", result: Result) -> str: if ( self.data and self.confirm_prompt - and len(self.data) == 2 - and self.data[0] - and self.data[1] + and self.has_date() + and self.has_time() ): ans = self.confirm_prompt.format( date_time=datetime.datetime.combine( @@ -215,10 +236,6 @@ def generate_answer(self, dsm: "DialogueStateManager", result: Result) -> str: cast(datetime.time, self.data[1]), ).strftime("%Y/%m/%d %H:%M") ) - if self.state is ResourceState.CONFIRMED: - ans = "Pöntunin er staðfest." - if ans is None: - raise ValueError("No answer generated") return ans @@ -239,10 +256,12 @@ class FinalResource(Resource): final_prompt: str = "" def generate_answer(self, dsm: "DialogueStateManager", result: Result) -> str: - ans: Optional[str] = self._execute_children(dsm, result) - if ans: - return ans - return self.final_prompt + self._execute_callbacks(dsm, result) + ans: Optional[str] = self._get_child_answer(dsm, result) + if ans is None: + self.state = ResourceState.CONFIRMED + ans = self.final_prompt + return ans ############################## @@ -305,7 +324,6 @@ def not_in_dialogue(self, start_dialogue_qtype: str) -> bool: def setup_dialogue(self): obj = _load_dialogue_structure(self._dialogue_name) - print(obj) for i, resource in enumerate(obj["resources"]): if self._saved_state and i < len(self._saved_state["resources"]): resource.update(self._saved_state["resources"][i]) @@ -345,14 +363,16 @@ def generate_answer(self, result: Result) -> str: # Final callback (performing some operation with the dialogue's data) # should be called before ending dialogue self.end_dialogue() + else: + self.update_dialogue_state() + if ans is None: + raise ValueError("No answer generated") return ans # i = 0 # while i < len(self.resources): # resource = self.resources[i] # if resource.required and resource.state is not ResourceState.CONFIRMED: - # if resource.state is ResourceState.INITIAL: - # resource.state = ResourceState.UNFULFILLED # if "callbacks" in result: # while len(result.callbacks) > 0: # r_name, cb = result.callbacks.pop(0) diff --git a/queries/fruitseller/fruitseller.toml b/queries/fruitseller/fruitseller.toml index 573babd9..842cdcf2 100644 --- a/queries/fruitseller/fruitseller.toml +++ b/queries/fruitseller/fruitseller.toml @@ -25,5 +25,5 @@ confirm_prompt = "Afhending pöntunar er {date_time}. Viltu staðfesta afhending [[resources]] name = "Final" type = "FinalResource" -prompt = "Pöntunin þín er {fruits} og verður afhent {date_time}." +final_prompt = "Pöntunin þín er {fruits} og verður afhent {date_time}." requires = ["Fruits", "Date"] diff --git a/queries/fruitseller_module.py b/queries/fruitseller_module.py index 6df73b6f..176201f6 100644 --- a/queries/fruitseller_module.py +++ b/queries/fruitseller_module.py @@ -64,7 +64,7 @@ QRemoveFruitQuery → "taktu" "út" QFruitList | "slepptu" QFruitList - | "ég" "vil" "sleppa" QFruitList + | "ég"? "vil"? "sleppa" QFruitList | "ég" "vill" "sleppa" QFruitList | "ég" "hætti" "við" QFruitList | "ég" "vil" "ekki" QFruitList @@ -88,7 +88,7 @@ QFruitOptionsQuery → "hvað" "er" "í" "boði" | "hverjir" "eru" "valmöguleikarnir" - | "hvaða" "valmöguleikar" "eru" "í" "boði" + | "hvaða" "valmöguleikar" "eru" "í" "boði" | "hvaða" "valmöguleikar" "eru" "til" | "hvaða" "ávexti" "ertu" "með" | "hvaða" "ávextir" "eru" "í" "boði" @@ -109,9 +109,9 @@ QNo → "nei" | "nei" "takk" -QCancelOrder → "ég" "hætti" "við" +QCancelOrder → "ég" "hætti" "við" | "ég" "vil" "hætta" "við" "pöntunina" - | "ég" "vill" "hætta" "við" "pöntunina" + | "ég" "vill" "hætta" "við" "pöntunina" QFruitDateQuery → QFruitDateTime @@ -139,16 +139,35 @@ def QFruitStartQuery(node: Node, params: QueryStateDict, result: Result): def QAddFruitQuery(node: Node, params: QueryStateDict, result: Result): + def _add_fruit(resource: Resource, result: Result) -> None: + if resource.data is None: + resource.data = [] + for number, name in result.queryfruits: + resource.data.append((number, name)) + resource.state = ResourceState.PARTIALLY_FULFILLED + if "callbacks" not in result: result["callbacks"] = [] - result.callbacks.append((("Fruits", ),_add_fruit)) + result.callbacks.append((("Fruits",), _add_fruit)) result.qtype = "QAddFruitQuery" def QRemoveFruitQuery(node: Node, params: QueryStateDict, result: Result): + def _remove_fruit(resource: Resource, result: Result) -> None: + if resource.data is not None: + for _, fruitname in result.queryfruits: + for number, name in resource.data: + if name == fruitname: + resource.data.remove([number, name]) + break + if len(resource.data) == 0: + resource.state = ResourceState.UNFULFILLED + else: + resource.state = ResourceState.PARTIALLY_FULFILLED + if "callbacks" not in result: result["callbacks"] = [] - result.callbacks.append((("Fruits", ), _remove_fruit)) + result.callbacks.append((("Fruits",), _remove_fruit)) result.qtype = "QRemoveFruitQuery" @@ -161,6 +180,14 @@ def QFruitOptionsQuery(node: Node, params: QueryStateDict, result: Result): def QYes(node: Node, params: QueryStateDict, result: Result): + def _parse_yes(resource: Resource, result: Result) -> None: + if resource.name == "Fruits": + if resource.state == ResourceState.FULFILLED: + resource.state = ResourceState.CONFIRMED + if resource.name == "Date": + if resource.state == ResourceState.FULFILLED: + resource.state = ResourceState.CONFIRMED + if "callbacks" not in result: result["callbacks"] = [] result.callbacks.append((("Fruits", "Date"), _parse_yes)) @@ -168,6 +195,13 @@ def QYes(node: Node, params: QueryStateDict, result: Result): def QNo(node: Node, params: QueryStateDict, result: Result): + def _parse_no(resource: Resource, result: Result) -> None: + if resource.name == "Fruits": + if resource.state == ResourceState.PARTIALLY_FULFILLED: + resource.state = ResourceState.FULFILLED + elif resource.state == ResourceState.FULFILLED: + resource.state = ResourceState.PARTIALLY_FULFILLED + if "callbacks" not in result: result["callbacks"] = [] result.callbacks.append((("Fruits", "Date"), _parse_no)) @@ -201,7 +235,6 @@ def QFruitDateTime(node: Node, params: QueryStateDict, result: Result) -> None: result.qtype = "bull" datetimenode = node.first_child(lambda n: True) assert isinstance(datetimenode, TerminalNode) - print(datetimenode.aux) now = datetime.datetime.now() y, m, d, h, min, _ = (i if i != 0 else None for i in json.loads(datetimenode.aux)) if y is None: @@ -218,14 +251,13 @@ def QFruitDateTime(node: Node, params: QueryStateDict, result: Result) -> None: result["delivery_date"] = datetime.date(y, m, d) def _dt_callback(resource: DatetimeResource, result: Result) -> None: - print("DATETIME SHOULD BE FULFILLED NOW") resource.set_date(result["delivery_date"]) resource.set_time(result["delivery_time"]) resource.state = ResourceState.FULFILLED if "callbacks" not in result: result["callbacks"] = [] - result.callbacks.append((("Date", ), _dt_callback)) + result.callbacks.append((("Date",), _dt_callback)) def QFruitDate(node: Node, params: QueryStateDict, result: Result) -> None: @@ -245,7 +277,6 @@ def QFruitDate(node: Node, params: QueryStateDict, result: Result) -> None: if m < now.month or (m == now.month and d < now.day): y += 1 result["delivery_date"] = datetime.date(day=d, month=m, year=y) - print("DELIVERY DATE:", result["delivery_date"]) def _dt_callback(resource: DatetimeResource, result: Result) -> None: resource.set_date(result["delivery_date"]) @@ -256,7 +287,7 @@ def _dt_callback(resource: DatetimeResource, result: Result) -> None: if "callbacks" not in result: result["callbacks"] = [] - result.callbacks.append((("Date", ), _dt_callback)) + result.callbacks.append((("Date",), _dt_callback)) return raise ValueError("No date in {0}".format(str(datenode))) @@ -270,7 +301,6 @@ def QFruitTime(node: Node, params: QueryStateDict, result: Result): hour, minute, _ = (int(i) for i in aux_str.split(", ")) result["delivery_time"] = datetime.time(hour, minute) - print("TIME IS: ", result["delivery_time"]) def _dt_callback(resource: DatetimeResource, result: Result) -> None: resource.set_time(result["delivery_time"]) @@ -281,53 +311,7 @@ def _dt_callback(resource: DatetimeResource, result: Result) -> None: if "callbacks" not in result: result["callbacks"] = [] - result.callbacks.append((("Date", ), _dt_callback)) - - -def _remove_fruit(resource: Resource, result: Result) -> None: - if resource.data is not None: - for _, fruitname in result.queryfruits: - for number, name in resource.data: - if name == fruitname: - resource.data.remove([number, name]) - break - if len(resource.data) == 0: - resource.state = ResourceState.UNFULFILLED - else: - resource.state = ResourceState.PARTIALLY_FULFILLED - - -def _add_fruit(resource: Resource, result: Result) -> None: - if resource.data is None: - resource.data = [] - for number, name in result.queryfruits: - resource.data.append((number, name)) - resource.state = ResourceState.PARTIALLY_FULFILLED - - -def _parse_no(resource: Resource, result: Result) -> None: - print("No callback") - if resource.name == "Fruits": - if resource.state == ResourceState.PARTIALLY_FULFILLED: - print("State is PARTIALLY_FULFILLED") - resource.state = ResourceState.FULFILLED - print("State after setting to FULFILLED") - elif resource.state == ResourceState.FULFILLED: - print("State is FULFILLED") - resource.state = ResourceState.PARTIALLY_FULFILLED - print("State after setting to PARTIALLY_FULFILLED") - - -def _parse_yes(resource: Resource, result: Result) -> None: - if resource.name == "Fruits": - if resource.state == ResourceState.FULFILLED: - resource.state = ResourceState.CONFIRMED - if resource.name == "Date": - print("CONFIRMING DATETIME...", resource) - if resource.state == ResourceState.FULFILLED: - print("CONFIRMING DATETIME... 2") - resource.state = ResourceState.CONFIRMED - print("DATETIME CONFIRMED") + result.callbacks.append((("Date",), _dt_callback)) def sentence(state: QueryStateDict, result: Result) -> None: @@ -338,20 +322,15 @@ def sentence(state: QueryStateDict, result: Result) -> None: if dsm.not_in_dialogue(_START_DIALOGUE_QTYPE): q.set_error("E_QUERY_NOT_UNDERSTOOD") return - print("Fyrir dsm") + dsm.setup_dialogue() - print("Eftir dms") + # Successfully matched a query type try: if result.qtype == _START_DIALOGUE_QTYPE: dsm.start_dialogue() ans = dsm.generate_answer(result) - dsm.update_dialogue_state() - print("woohoo") - - if result.qtype == "OrderComplete" or result.qtype == "CancelOrder": - dsm.end_dialogue() q.set_answer(*gen_answer(ans)) return From 45e3e52c948c03fe1572e86c735daf3068d0df6f Mon Sep 17 00:00:00 2001 From: Logi E Date: Mon, 27 Jun 2022 11:45:36 +0000 Subject: [PATCH 100/371] Refactoring, introducing new ways to set answer --- queries/dialogue.py | 368 +++++++++++++-------------- queries/fruitseller/fruitseller.toml | 21 +- queries/fruitseller_module.py | 72 +++++- 3 files changed, 250 insertions(+), 211 deletions(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index 5471787e..a91eda68 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -14,12 +14,12 @@ from tree import Result from query import Query, ClientDataDict -from reynir import NounPhrase -from queries import natlang_seq, sing_or_plur # Global key for storing client data for dialogues DIALOGUE_KEY = "dialogue" DIALOGUE_DATA_KEY = "dialogue_data" +DIALOGUE_NAME_KEY = "dialogue_name" +DIALOGUE_RESOURCES_KEY = "resources" EMPTY_DIALOGUE_DATA = "{}" FINAL_RESOURCE_NAME = "Final" @@ -48,74 +48,64 @@ class ResourceState(IntEnum): @dataclass class Resource: name: str = "" + type: str = "" data: Any = None state: ResourceState = ResourceState.UNFULFILLED - prompt: str = "" - type: str = "" - repeatable: bool = False - repeat_prompt: Optional[str] = None - confirm_prompt: Optional[str] = None - cancel_prompt: Optional[str] = None requires: List[str] = field(default_factory=list) - _repeat_count: int = 0 + prompts: Mapping[str, str] = field(default_factory=dict) + _answer: Optional[str] = None + + def set_answer(self, answer_name: str, **kwargs: str) -> None: + print("SETTING ANSWER:", answer_name, kwargs, self.name) + self._answer = self.prompts[answer_name].format(**kwargs) + print("ANSWER SET AS:", self._answer) + + def get_answer(self, dsm: "DialogueStateManager") -> str: + print("CURRENT RESOURCE:", self.name, "ANSWER:", self._answer) + if self._answer is not None: + return self._answer + ans: Optional[str] = None + if self.requires: + for rname in self.requires: + resource = dsm.get_resource(rname) + if not resource.is_confirmed: + ans = resource.get_answer(dsm) + if ans: + break + assert ans is not None, "No answer was generated" + return ans - def next_action(self) -> Any: - raise NotImplementedError() + @property + def is_unfulfilled(self) -> bool: + return self.state is ResourceState.UNFULFILLED + + @property + def is_partially_fulfilled(self) -> bool: + return self.state is ResourceState.PARTIALLY_FULFILLED + + @property + def is_fulfilled(self) -> bool: + return self.state is ResourceState.FULFILLED + + @property + def is_confirmed(self) -> bool: + return self.state is ResourceState.CONFIRMED - def generate_answer( - self, dsm: "DialogueStateManager", result: Result - ) -> Optional[str]: + @property + def is_paused(self) -> bool: + return self.state is ResourceState.PAUSED + + @property + def is_skipped(self) -> bool: + return self.state is ResourceState.SKIPPED + + def next_action(self) -> Any: raise NotImplementedError() def update(self, new_data: Optional["Resource"]) -> None: if new_data: self.__dict__.update(new_data.__dict__) - def _execute_callbacks(self, dsm: "DialogueStateManager", result: Result) -> None: - if self.requires: - for rname in self.requires: - resource = dsm.get_resource(rname) - resource._execute_callbacks(dsm, result) - - if "callbacks" in result: - i = 0 - while i < len(result["callbacks"]): - rnames, cb = result.callbacks[i] - if self.name in rnames: - cb(self, result) - if len(rnames) > 1: - # Remove the current resource name from rnames - # if it is not the last one - result.callbacks[i] = ( - tuple(rn for rn in rnames if rn != self.name), - cb, - ) - else: - result.callbacks.pop(i) - i -= 1 - i += 1 - - def _get_child_answer( - self, dsm: "DialogueStateManager", result: Result - ) -> Optional[str]: - if self.requires: - for rname in self.requires: - resource = dsm.get_resource(rname) - if resource.state is not ResourceState.CONFIRMED: - ans = resource.generate_answer(dsm, result) - if ans: - return ans - return None - - -def _list_items(items: Any) -> str: - item_list: List[str] = [] - for num, name in items: - # TODO: get general plural form - plural_name: str = NounPhrase(name).dative or name - item_list.append(sing_or_plur(num, name, plural_name)) - return natlang_seq(item_list) - @dataclass class ListResource(Resource): @@ -125,26 +115,23 @@ class ListResource(Resource): def list_available_options(self) -> str: raise NotImplementedError() - def generate_answer( - self, dsm: "DialogueStateManager", result: Result - ) -> Optional[str]: - ans: Optional[str] = self._get_child_answer(dsm, result) - if ans: - return ans - if self.state is ResourceState.UNFULFILLED: - if self._repeat_count == 0 or not self.repeatable: - ans = self.prompt - if self.state is ResourceState.PARTIALLY_FULFILLED: - if self.repeat_prompt: - ans = ( - f"{self.repeat_prompt.format(list_items = _list_items(self.data))}" - ) - if self.state is ResourceState.FULFILLED: - if self.confirm_prompt: - ans = ( - f"{self.confirm_prompt.format(list_items = _list_items(self.data))}" - ) - return ans + # def generate_answer( + # self, dsm: "DialogueStateManager", result: Result + # ) -> Optional[str]: + # ans: Optional[str] = self._get_child_answer(dsm, result) + # if ans: + # return ans + # if self.state is ResourceState.UNFULFILLED: + # ans = self.prompts["initial"] + # if self.state is ResourceState.PARTIALLY_FULFILLED: + # ans = ( + # f"{self.prompts['repeat'].format(list_items = _list_items(self.data))}" + # ) + # if self.state is ResourceState.FULFILLED: + # ans = ( + # f"{self.prompts['confirm'].format(list_items = _list_items(self.data))}" + # ) + # return ans # TODO: @@ -156,8 +143,6 @@ def generate_answer( @dataclass class YesNoResource(Resource): data: bool = False - yes_answer: Optional[str] = None - no_answer: Optional[str] = None def set_yes(self): self.data = True @@ -167,21 +152,19 @@ def set_no(self): self.data = False self.state = ResourceState.CONFIRMED - def generate_answer( - self, dsm: "DialogueStateManager", result: Result - ) -> Optional[str]: - ans: Optional[str] = self._get_child_answer(dsm, result) - if ans: - return ans - if self.data: - if self.yes_answer: - ans = self.yes_answer - else: - if self.no_answer: - ans = self.no_answer - if ans is None: - raise ValueError("No answer generated") - return ans + # def generate_answer( + # self, dsm: "DialogueStateManager", result: Result + # ) -> Optional[str]: + # ans: Optional[str] = self._get_child_answer(dsm, result) + # if ans: + # return ans + # if self.data: + # ans = self.prompts["yes_answer"] + # else: + # ans = self.prompts["no_answer"] + # if ans is None: + # raise ValueError("No answer generated") + # return ans @dataclass @@ -189,8 +172,14 @@ class DatetimeResource(Resource): data: List[Union[Optional[datetime.date], Optional[datetime.time]]] = field( default_factory=lambda: [None, None] ) - date_fulfilled_prompt: Optional[str] = None - time_fulfilled_prompt: Optional[str] = None + + @property + def date(self) -> Optional[datetime.date]: + return self.data[0] + + @property + def time(self) -> Optional[datetime.time]: + return self.data[1] def has_date(self) -> bool: return isinstance(self.data[0], datetime.date) @@ -204,39 +193,33 @@ def set_date(self, new_date: Optional[datetime.date] = None) -> None: def set_time(self, new_time: Optional[datetime.time] = None) -> None: self.data[1] = new_time - def generate_answer( - self, dsm: "DialogueStateManager", result: Result - ) -> Optional[str]: - ans: Optional[str] = self._get_child_answer(dsm, result) - if ans: - return ans - if self.state is ResourceState.UNFULFILLED: - if self._repeat_count == 0 or not self.repeatable: - ans = self.prompt - if self.state is ResourceState.PARTIALLY_FULFILLED: - if self.data: - if self.has_date() and self.date_fulfilled_prompt: - ans = self.date_fulfilled_prompt.format( - date=self.data[0].strftime("%Y/%m/%d") - ) - if self.has_time() and self.time_fulfilled_prompt: - ans = self.time_fulfilled_prompt.format( - time=self.data[1].strftime("%H:%M") - ) - if self.state is ResourceState.FULFILLED: - if ( - self.data - and self.confirm_prompt - and self.has_date() - and self.has_time() - ): - ans = self.confirm_prompt.format( - date_time=datetime.datetime.combine( - cast(datetime.date, self.data[0]), - cast(datetime.time, self.data[1]), - ).strftime("%Y/%m/%d %H:%M") - ) - return ans + # def generate_answer( + # self, dsm: "DialogueStateManager", result: Result + # ) -> Optional[str]: + # ans: Optional[str] = self._get_child_answer(dsm, result) + # if ans: + # return ans + # if self.state is ResourceState.UNFULFILLED: + # ans = self.prompts["initial"] + # if self.state is ResourceState.PARTIALLY_FULFILLED: + # if self.data: + # if self.has_date(): + # ans = self.prompts["date_fulfilled"].format( + # date=self.data[0].strftime("%Y/%m/%d") + # ) + # if self.has_time() and self.prompts["time_fulfilled"]: + # ans = self.prompts["time_fulfilled"].format( + # time=self.data[1].strftime("%H:%M") + # ) + # if self.state is ResourceState.FULFILLED: + # if self.data and self.has_date() and self.has_time(): + # ans = self.prompts["confirm"].format( + # date_time=datetime.datetime.combine( + # cast(datetime.date, self.data[0]), + # cast(datetime.time, self.data[1]), + # ).strftime("%Y/%m/%d %H:%M") + # ) + # return ans @dataclass @@ -253,15 +236,6 @@ class OrResource(Resource): @dataclass class FinalResource(Resource): data: Any = None - final_prompt: str = "" - - def generate_answer(self, dsm: "DialogueStateManager", result: Result) -> str: - self._execute_callbacks(dsm, result) - ans: Optional[str] = self._get_child_answer(dsm, result) - if ans is None: - self.state = ResourceState.CONFIRMED - ans = self.final_prompt - return ans ############################## @@ -293,20 +267,28 @@ def _load_dialogue_structure(filename: str) -> DialogueStructureType: with open(fpath, mode="r") as file: f = file.read() obj: Dict[str, Any] = tomllib.loads(f) # type:ignore - assert "dialogue_name" in obj - assert "resources" in obj - for i, resource in enumerate(obj["resources"]): + assert DIALOGUE_NAME_KEY in obj + assert DIALOGUE_RESOURCES_KEY in obj + for i, resource in enumerate(obj[DIALOGUE_RESOURCES_KEY]): assert "name" in resource assert "type" in resource - obj["resources"][i] = _RESOURCE_TYPES[resource["type"]](**resource) + obj[DIALOGUE_RESOURCES_KEY][i] = _RESOURCE_TYPES[resource["type"]](**resource) return cast(DialogueStructureType, obj) class DialogueStateManager: - def __init__(self, dialogue_name: str, query: Query, result: Result): + def __init__( + self, + dialogue_name: str, + start_dialogue_qtype: str, + query: Query, + result: Result, + ): self._dialogue_name: str = dialogue_name + self._start_qtype: str = start_dialogue_qtype self._q: Query = query self._result: Result = result + print("CALLBACKS:", self._result.get("callbacks")) self._resources: Dict[str, Resource] = {} self._saved_state: DialogueStructureType = self._get_saved_dialogue_state() self._data: Dict[str, Any] = {} @@ -314,20 +296,21 @@ def __init__(self, dialogue_name: str, query: Query, result: Result): # TODO: Delegate answering from a resource to another resource or to another dialogue # TODO: í ávaxtasamtali "ég vil panta flug" "viltu að ég geymi ávaxtapöntunina eða eyði henni?" ... - def not_in_dialogue(self, start_dialogue_qtype: str) -> bool: + def not_in_dialogue(self) -> bool: """Check if the client is in or wants to start this dialogue""" - qt = self._result.get("qtype") return ( - qt != start_dialogue_qtype - and self._saved_state.get("dialogue_name") != self._dialogue_name + self._result.get("qtype") != self._start_qtype + and self._saved_state.get(DIALOGUE_NAME_KEY) != self._dialogue_name ) - def setup_dialogue(self): + def setup_dialogue(self) -> None: obj = _load_dialogue_structure(self._dialogue_name) - for i, resource in enumerate(obj["resources"]): - if self._saved_state and i < len(self._saved_state["resources"]): - resource.update(self._saved_state["resources"][i]) - resource.state = ResourceState(self._saved_state["resources"][i].state) + for i, resource in enumerate(obj[DIALOGUE_RESOURCES_KEY]): + if self._saved_state and i < len(self._saved_state[DIALOGUE_RESOURCES_KEY]): + resource.update(self._saved_state[DIALOGUE_RESOURCES_KEY][i]) + resource.state = ResourceState( + self._saved_state[DIALOGUE_RESOURCES_KEY][i].state + ) self._resources[resource.name] = resource self.resourceState: Optional[Resource] = None @@ -338,8 +321,8 @@ def start_dialogue(self): # New empty dialogue state, with correct dialogue name self._set_dialogue_state( { - "dialogue_name": self._dialogue_name, - "resources": [], + DIALOGUE_NAME_KEY: self._dialogue_name, + DIALOGUE_RESOURCES_KEY: [], } ) @@ -348,18 +331,34 @@ def update_dialogue_state(self): # Save resources to client data self._set_dialogue_state( { - "dialogue_name": self._dialogue_name, - "resources": list(self._resources.values()), + DIALOGUE_NAME_KEY: self._dialogue_name, + DIALOGUE_RESOURCES_KEY: list(self._resources.values()), } ) def get_resource(self, name: str) -> Resource: return self._resources[name] - def generate_answer(self, result: Result) -> str: - # if self._resources[FINAL_RESOURCE_NAME].state is not ResourceState.CONFIRMED: - ans = self._resources[FINAL_RESOURCE_NAME].generate_answer(self, result) - if self._resources[FINAL_RESOURCE_NAME].state is ResourceState.CONFIRMED: + # def get_result(self) -> Result: + # return self._result + + # def get_callbacks(self) -> Optional[List[CallbackTupleType]]: + # return self._result.get("callbacks") + + def get_answer(self) -> str: + # Executing callbacks + self._execute_callbacks() + + answer_key: Optional[Tuple[str, str]] = self._result.get("answer_key") + print("BLAAAA, answer key", answer_key) + if answer_key: + # Quick way of setting response (instead of using a callback) + self._resources[answer_key[0]].set_answer(answer_key[1]) + print("USED ANSWER KEY", answer_key) + + ans = self._resources[FINAL_RESOURCE_NAME].get_answer(self) + print("GOT ANSWER:", ans) + if self._resources[FINAL_RESOURCE_NAME].is_confirmed: # Final callback (performing some operation with the dialogue's data) # should be called before ending dialogue self.end_dialogue() @@ -369,39 +368,26 @@ def generate_answer(self, result: Result) -> str: raise ValueError("No answer generated") return ans - # i = 0 - # while i < len(self.resources): - # resource = self.resources[i] - # if resource.required and resource.state is not ResourceState.CONFIRMED: - # if "callbacks" in result: - # while len(result.callbacks) > 0: - # r_name, cb = result.callbacks.pop(0) - # if self.resources[i].name in r_name: - # cb(resource, result) - # else: - # while i > -1: - # i -= 1 - # if self.resources[i].name in r_name: - # cb(self.resources[i], result) - # break - # if ( - # resource.state is ResourceState.CONFIRMED - # and resource != self.resources[-1] - # ): - # self._data[resource.name] = resource.data - # i += 1 - # continue - # return self.resources[i].generate_answer() - # i += 1 - # return "Upp kom villa, reyndu aftur." + def _execute_callbacks(self) -> None: + # if resource.requires: + # for rname in resource.requires: + # req_resource = self._resources[rname] + # self._execute_callbacks(req_resource) + + cbs = self._result.get("callbacks") + print("CBS:", cbs) + if cbs: + for rnames, cb in cbs: + for rn in rnames: + cb(self._resources[rn], self._result) def _get_saved_dialogue_state(self) -> DialogueStructureType: """Load the dialogue state for a client""" cd = self._q.client_data(DIALOGUE_KEY) # Return empty DialogueStructureType in case no dialogue state exists ds: DialogueStructureType = { - "dialogue_name": "", - "resources": [], + DIALOGUE_NAME_KEY: "", + DIALOGUE_RESOURCES_KEY: [], } if cd: ds_str = cd.get(DIALOGUE_DATA_KEY) @@ -431,6 +417,10 @@ def default(self, o: Any) -> Any: if isinstance(o, Resource): # CLASSES THAT INHERIT FROM RESOURCE d = o.__dict__.copy() + for key in list(d.keys()): + # Skip serializing attributes that start with an underscore + if key.startswith("_"): + del d[key] d["__type__"] = o.__class__.__name__ return d if isinstance(o, datetime.date): diff --git a/queries/fruitseller/fruitseller.toml b/queries/fruitseller/fruitseller.toml index 842cdcf2..bde3b4e5 100644 --- a/queries/fruitseller/fruitseller.toml +++ b/queries/fruitseller/fruitseller.toml @@ -2,19 +2,21 @@ dialogue_name = "fruitseller" [[resources]] name = "Fruits" -prompt = "Hvaða ávexti má bjóða þér?" type = "ListResource" -repeatable = true -confirm_prompt = "Viltu staðfesta ávextina {list_items}?" -repeat_prompt = "Pöntunin samanstendur af {list_items}. Verður það eitthvað fleira?" +# TODO: Keep singular and plural forms of fruits (or options more generally) for formatting answer +prompts.initial = "Hvaða ávexti má bjóða þér?" +prompts.options = "Ávextirnir sem eru í boði eru appelsínur, bananar, epli og perur." +prompts.empty = "Karfan er núna tóm. Hvaða ávexti má bjóða þér?" +prompts.confirm = "Viltu staðfesta ávextina {list_items}?" +prompts.repeat = "Pöntunin samanstendur af {list_items}. Verður það eitthvað fleira?" [[resources]] name = "Date" type = "DatetimeResource" -prompt = "Hvenær viltu fá ávextina?" -time_fulfilled_prompt = "Afhendingin verður klukkan {time}. Hvaða dagsetningu viltu fá ávextina?" -date_fulfilled_prompt = "Afhendingin verður {date}. Klukkan hvað viltu fá ávextina?" -confirm_prompt = "Afhending pöntunar er {date_time}. Viltu staðfesta afhendinguna?" +prompts.initial = "Hvenær viltu fá ávextina?" +prompts.time_fulfilled = "Afhendingin verður klukkan {time}. Hvaða dagsetningu viltu fá ávextina?" +prompts.date_fulfilled = "Afhendingin verður {date}. Klukkan hvað viltu fá ávextina?" +prompts.confirm = "Afhending pöntunar er {date_time}. Viltu staðfesta afhendinguna?" # [[resources]] # name = "ConfirmOrder" @@ -25,5 +27,6 @@ confirm_prompt = "Afhending pöntunar er {date_time}. Viltu staðfesta afhending [[resources]] name = "Final" type = "FinalResource" -final_prompt = "Pöntunin þín er {fruits} og verður afhent {date_time}." requires = ["Fruits", "Date"] +prompts.final = "Pöntunin þín er {fruits} og verður afhent {date_time}." +prompts.cancelled = "Móttekið, hætti við pöntunina." diff --git a/queries/fruitseller_module.py b/queries/fruitseller_module.py index 176201f6..81f60ac4 100644 --- a/queries/fruitseller_module.py +++ b/queries/fruitseller_module.py @@ -1,14 +1,14 @@ +from typing import Any, List, cast import json import logging import datetime -from typing import cast from query import Query, QueryStateDict from tree import Result, Node, TerminalNode -from queries import gen_answer, parse_num +from reynir import NounPhrase +from queries import gen_answer, parse_num, natlang_seq, sing_or_plur from queries.dialogue import ( DatetimeResource, - ListResource, Resource, ResourceState, DialogueStateManager, @@ -28,7 +28,9 @@ QFruitSeller '?'? QFruitSeller → - QFruitStartQuery | QFruitQuery | QFruitDateQuery + QFruitStartQuery + | QFruitQuery + | QFruitDateQuery QFruitStartQuery → "ávöxtur" @@ -134,8 +136,18 @@ _DIALOGUE_NAME = "fruitseller" +def _list_items(items: Any) -> str: + item_list: List[str] = [] + for num, name in items: + # TODO: get general plural form + plural_name: str = NounPhrase(name).dative or name + item_list.append(sing_or_plur(num, name, plural_name)) + return natlang_seq(item_list) + + def QFruitStartQuery(node: Node, params: QueryStateDict, result: Result): result.qtype = _START_DIALOGUE_QTYPE + result.answer_key = ("Fruits", "initial") def QAddFruitQuery(node: Node, params: QueryStateDict, result: Result): @@ -145,6 +157,9 @@ def _add_fruit(resource: Resource, result: Result) -> None: for number, name in result.queryfruits: resource.data.append((number, name)) resource.state = ResourceState.PARTIALLY_FULFILLED + print("INSIDE ADD FRUITS CALLBACK", resource.name) + resource.set_answer("repeat", list_items=_list_items(resource.data)) + print("ANSWER IS:", resource._answer) if "callbacks" not in result: result["callbacks"] = [] @@ -162,8 +177,10 @@ def _remove_fruit(resource: Resource, result: Result) -> None: break if len(resource.data) == 0: resource.state = ResourceState.UNFULFILLED + resource.set_answer("empty") else: resource.state = ResourceState.PARTIALLY_FULFILLED + resource.set_answer("repeat", list_items=_list_items(resource.data)) if "callbacks" not in result: result["callbacks"] = [] @@ -173,20 +190,24 @@ def _remove_fruit(resource: Resource, result: Result) -> None: def QCancelOrder(node: Node, params: QueryStateDict, result: Result): result.qtype = "QCancelOrder" + result.answer_key = ("Final", "cancelled") def QFruitOptionsQuery(node: Node, params: QueryStateDict, result: Result): result.qtype = "QFruitOptionsQuery" + result.answer_key = "options" def QYes(node: Node, params: QueryStateDict, result: Result): def _parse_yes(resource: Resource, result: Result) -> None: if resource.name == "Fruits": - if resource.state == ResourceState.FULFILLED: + if resource.is_fulfilled: resource.state = ResourceState.CONFIRMED + result.answer_key = ("Date", "initial") if resource.name == "Date": - if resource.state == ResourceState.FULFILLED: + if resource.is_fulfilled: resource.state = ResourceState.CONFIRMED + result.answer_key = ("Final", "final") if "callbacks" not in result: result["callbacks"] = [] @@ -197,10 +218,12 @@ def _parse_yes(resource: Resource, result: Result) -> None: def QNo(node: Node, params: QueryStateDict, result: Result): def _parse_no(resource: Resource, result: Result) -> None: if resource.name == "Fruits": - if resource.state == ResourceState.PARTIALLY_FULFILLED: + if resource.is_partially_fulfilled: resource.state = ResourceState.FULFILLED - elif resource.state == ResourceState.FULFILLED: + resource.set_answer("confirm", list_items=_list_items(resource.data)) + elif resource.is_fulfilled: resource.state = ResourceState.PARTIALLY_FULFILLED + resource.set_answer("repeat", list_items=_list_items(resource.data)) if "callbacks" not in result: result["callbacks"] = [] @@ -254,6 +277,12 @@ def _dt_callback(resource: DatetimeResource, result: Result) -> None: resource.set_date(result["delivery_date"]) resource.set_time(result["delivery_time"]) resource.state = ResourceState.FULFILLED + resource.set_answer( + "confirm", + date_time=datetime.datetime.combine(resource.date, resource.time).strftime( + "%Y/%m/%d %H:%M" + ), + ) if "callbacks" not in result: result["callbacks"] = [] @@ -282,8 +311,17 @@ def _dt_callback(resource: DatetimeResource, result: Result) -> None: resource.set_date(result["delivery_date"]) if resource.has_time(): resource.state = ResourceState.FULFILLED + resource.set_answer( + "confirm", + date_time=datetime.datetime.combine( + resource.date, resource.time + ).strftime("%Y/%m/%d %H:%M"), + ) else: resource.state = ResourceState.PARTIALLY_FULFILLED + resource.set_answer( + "date_fulfilled", date=resource.date.strftime("%Y/%m/%d") + ) if "callbacks" not in result: result["callbacks"] = [] @@ -306,8 +344,17 @@ def _dt_callback(resource: DatetimeResource, result: Result) -> None: resource.set_time(result["delivery_time"]) if resource.has_date(): resource.state = ResourceState.FULFILLED + resource.set_answer( + "confirm", + date_time=datetime.datetime.combine( + resource.date, resource.time + ).strftime("%Y/%m/%d %H:%M"), + ) else: resource.state = ResourceState.PARTIALLY_FULFILLED + resource.set_answer( + "time_fulfilled", time=resource.time.strftime("%H:%M") + ) if "callbacks" not in result: result["callbacks"] = [] @@ -317,20 +364,19 @@ def _dt_callback(resource: DatetimeResource, result: Result) -> None: def sentence(state: QueryStateDict, result: Result) -> None: """Called when sentence processing is complete""" q: Query = state["query"] - dsm = DialogueStateManager(_DIALOGUE_NAME, q, result) + dsm = DialogueStateManager(_DIALOGUE_NAME, _START_DIALOGUE_QTYPE, q, result) - if dsm.not_in_dialogue(_START_DIALOGUE_QTYPE): + if dsm.not_in_dialogue(): q.set_error("E_QUERY_NOT_UNDERSTOOD") return - dsm.setup_dialogue() - # Successfully matched a query type try: + dsm.setup_dialogue() if result.qtype == _START_DIALOGUE_QTYPE: dsm.start_dialogue() - ans = dsm.generate_answer(result) + ans = dsm.get_answer() q.set_answer(*gen_answer(ans)) return From c040e60198211166f4e449bccb414c568eba4c78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Mon, 27 Jun 2022 12:42:08 +0000 Subject: [PATCH 101/371] Fixed fruit options query --- queries/fruitseller_module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/queries/fruitseller_module.py b/queries/fruitseller_module.py index 81f60ac4..dc00a48a 100644 --- a/queries/fruitseller_module.py +++ b/queries/fruitseller_module.py @@ -195,7 +195,7 @@ def QCancelOrder(node: Node, params: QueryStateDict, result: Result): def QFruitOptionsQuery(node: Node, params: QueryStateDict, result: Result): result.qtype = "QFruitOptionsQuery" - result.answer_key = "options" + result.answer_key = ("Fruits", "options") def QYes(node: Node, params: QueryStateDict, result: Result): From 64e35709cff8dd56953d0435a35fce10a4848227 Mon Sep 17 00:00:00 2001 From: Logi E Date: Mon, 27 Jun 2022 13:35:39 +0000 Subject: [PATCH 102/371] Callback functions now rely on filter functions rather than a tuple --- queries/dialogue.py | 101 +++++++++++++-------------- queries/fruitseller/fruitseller.toml | 3 +- queries/fruitseller_module.py | 34 ++++++--- 3 files changed, 74 insertions(+), 64 deletions(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index a91eda68..a68d2476 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -1,16 +1,16 @@ -import json from typing import Any, Callable, Dict, Mapping, Tuple, Union, List, Optional, cast from typing_extensions import TypedDict import os.path +import json import datetime +from enum import IntEnum, auto +from dataclasses import dataclass, field try: import tomllib except ModuleNotFoundError: import tomli as tomllib -from enum import IntEnum, auto -from dataclasses import dataclass, field from tree import Result from query import Query, ClientDataDict @@ -23,10 +23,14 @@ EMPTY_DIALOGUE_DATA = "{}" FINAL_RESOURCE_NAME = "Final" -ResourceType = Union[str, int, float, bool, datetime.datetime, None] -ListResourceType = List[ResourceType] +# Resource types +ResourceDataType = Union[str, int, float, bool, datetime.datetime, None] +ListResourceType = List[ResourceDataType] + +# Types for use in callbacks CallbackType = Callable[["Resource", Result], None] -CallbackTupleType = Tuple[Tuple[str, ...], CallbackType] +FilterFuncType = Callable[["Resource"], bool] +CallbackTupleType = Tuple[FilterFuncType, CallbackType] class ResourceState(IntEnum): @@ -60,7 +64,7 @@ def set_answer(self, answer_name: str, **kwargs: str) -> None: self._answer = self.prompts[answer_name].format(**kwargs) print("ANSWER SET AS:", self._answer) - def get_answer(self, dsm: "DialogueStateManager") -> str: + def get_answer(self, dsm: "DialogueStateManager") -> Optional[str]: print("CURRENT RESOURCE:", self.name, "ANSWER:", self._answer) if self._answer is not None: return self._answer @@ -72,7 +76,7 @@ def get_answer(self, dsm: "DialogueStateManager") -> str: ans = resource.get_answer(dsm) if ans: break - assert ans is not None, "No answer was generated" + # assert ans is not None, "No answer was generated from resource " + self.name return ans @property @@ -182,10 +186,10 @@ def time(self) -> Optional[datetime.time]: return self.data[1] def has_date(self) -> bool: - return isinstance(self.data[0], datetime.date) + return len(self.data) > 0 and isinstance(self.data[0], datetime.date) def has_time(self) -> bool: - return isinstance(self.data[1], datetime.time) + return len(self.data) > 1 and isinstance(self.data[1], datetime.time) def set_date(self, new_date: Optional[datetime.date] = None) -> None: self.data[0] = new_date @@ -193,33 +197,36 @@ def set_date(self, new_date: Optional[datetime.date] = None) -> None: def set_time(self, new_time: Optional[datetime.time] = None) -> None: self.data[1] = new_time - # def generate_answer( - # self, dsm: "DialogueStateManager", result: Result - # ) -> Optional[str]: - # ans: Optional[str] = self._get_child_answer(dsm, result) - # if ans: - # return ans - # if self.state is ResourceState.UNFULFILLED: - # ans = self.prompts["initial"] - # if self.state is ResourceState.PARTIALLY_FULFILLED: - # if self.data: - # if self.has_date(): - # ans = self.prompts["date_fulfilled"].format( - # date=self.data[0].strftime("%Y/%m/%d") - # ) - # if self.has_time() and self.prompts["time_fulfilled"]: - # ans = self.prompts["time_fulfilled"].format( - # time=self.data[1].strftime("%H:%M") - # ) - # if self.state is ResourceState.FULFILLED: - # if self.data and self.has_date() and self.has_time(): - # ans = self.prompts["confirm"].format( - # date_time=datetime.datetime.combine( - # cast(datetime.date, self.data[0]), - # cast(datetime.time, self.data[1]), - # ).strftime("%Y/%m/%d %H:%M") - # ) - # return ans + def get_answer(self, dsm: "DialogueStateManager") -> Optional[str]: + ans: Optional[str] = super().get_answer(dsm) + if ans: + return ans + + if self.state is ResourceState.CONFIRMED: + return None + + if self.state is ResourceState.UNFULFILLED: + ans = self.prompts["initial"] + + if self.state is ResourceState.PARTIALLY_FULFILLED: + if self.has_date(): + ans = self.prompts["date_fulfilled"].format( + date=self.data[0].strftime("%Y/%m/%d") + ) + if self.has_time() and self.prompts["time_fulfilled"]: + ans = self.prompts["time_fulfilled"].format( + time=self.data[1].strftime("%H:%M") + ) + + if self.state is ResourceState.FULFILLED: + if self.has_date() and self.has_time(): + ans = self.prompts["confirm"].format( + date_time=datetime.datetime.combine( + cast(datetime.date, self.data[0]), + cast(datetime.time, self.data[1]), + ).strftime("%Y/%m/%d %H:%M") + ) + return ans @dataclass @@ -339,12 +346,6 @@ def update_dialogue_state(self): def get_resource(self, name: str) -> Resource: return self._resources[name] - # def get_result(self) -> Result: - # return self._result - - # def get_callbacks(self) -> Optional[List[CallbackTupleType]]: - # return self._result.get("callbacks") - def get_answer(self) -> str: # Executing callbacks self._execute_callbacks() @@ -369,17 +370,13 @@ def get_answer(self) -> str: return ans def _execute_callbacks(self) -> None: - # if resource.requires: - # for rname in resource.requires: - # req_resource = self._resources[rname] - # self._execute_callbacks(req_resource) - - cbs = self._result.get("callbacks") + cbs: Optional[List[CallbackTupleType]] = self._result.get("callbacks") print("CBS:", cbs) if cbs: - for rnames, cb in cbs: - for rn in rnames: - cb(self._resources[rn], self._result) + for filter_func, cb in cbs: + for resource in self._resources.values(): + if filter_func(resource): + cb(resource, self._result) def _get_saved_dialogue_state(self) -> DialogueStructureType: """Load the dialogue state for a client""" diff --git a/queries/fruitseller/fruitseller.toml b/queries/fruitseller/fruitseller.toml index bde3b4e5..e9a95dc7 100644 --- a/queries/fruitseller/fruitseller.toml +++ b/queries/fruitseller/fruitseller.toml @@ -13,6 +13,7 @@ prompts.repeat = "Pöntunin samanstendur af {list_items}. Verður það eitthva [[resources]] name = "Date" type = "DatetimeResource" +requires = ["Fruits"] prompts.initial = "Hvenær viltu fá ávextina?" prompts.time_fulfilled = "Afhendingin verður klukkan {time}. Hvaða dagsetningu viltu fá ávextina?" prompts.date_fulfilled = "Afhendingin verður {date}. Klukkan hvað viltu fá ávextina?" @@ -27,6 +28,6 @@ prompts.confirm = "Afhending pöntunar er {date_time}. Viltu staðfesta afhendin [[resources]] name = "Final" type = "FinalResource" -requires = ["Fruits", "Date"] +requires = ["Date"] prompts.final = "Pöntunin þín er {fruits} og verður afhent {date_time}." prompts.cancelled = "Móttekið, hætti við pöntunina." diff --git a/queries/fruitseller_module.py b/queries/fruitseller_module.py index dc00a48a..8cf63b91 100644 --- a/queries/fruitseller_module.py +++ b/queries/fruitseller_module.py @@ -1,4 +1,4 @@ -from typing import Any, List, cast +from typing import Any, Callable, List, cast import json import logging import datetime @@ -107,9 +107,9 @@ QFruit → 'banani' | 'epli' | 'pera' | 'appelsína' -QYes → "já" | "já" "takk" | "já" "þakka" "þér" "fyrir" | "já" "takk" "kærlega" "fyrir"? +QYes → "já" "já"* | "já" "takk" | "játakk" | "já" "þakka" "þér" "fyrir" | "já" "takk" "kærlega" "fyrir"? | "jább" "takk"? -QNo → "nei" | "nei" "takk" +QNo → "nei" "takk"? | "nei" "nei"* | "neitakk" QCancelOrder → "ég" "hætti" "við" | "ég" "vil" "hætta" "við" "pöntunina" @@ -163,7 +163,8 @@ def _add_fruit(resource: Resource, result: Result) -> None: if "callbacks" not in result: result["callbacks"] = [] - result.callbacks.append((("Fruits",), _add_fruit)) + filter_func: Callable[[Resource], bool] = lambda r: r.name == "Fruits" + result.callbacks.append((filter_func, _add_fruit)) result.qtype = "QAddFruitQuery" @@ -184,7 +185,8 @@ def _remove_fruit(resource: Resource, result: Result) -> None: if "callbacks" not in result: result["callbacks"] = [] - result.callbacks.append((("Fruits",), _remove_fruit)) + filter_func: Callable[[Resource], bool] = lambda r: r.name == "Fruits" + result.callbacks.append((filter_func, _remove_fruit)) result.qtype = "QRemoveFruitQuery" @@ -203,7 +205,6 @@ def _parse_yes(resource: Resource, result: Result) -> None: if resource.name == "Fruits": if resource.is_fulfilled: resource.state = ResourceState.CONFIRMED - result.answer_key = ("Date", "initial") if resource.name == "Date": if resource.is_fulfilled: resource.state = ResourceState.CONFIRMED @@ -211,7 +212,10 @@ def _parse_yes(resource: Resource, result: Result) -> None: if "callbacks" not in result: result["callbacks"] = [] - result.callbacks.append((("Fruits", "Date"), _parse_yes)) + filter_func: Callable[[Resource], bool] = ( + lambda r: r.name in ("Fruits", "Date") and not r.is_confirmed + ) + result.callbacks.append((filter_func, _parse_yes)) result.qtype = "QYes" @@ -227,7 +231,10 @@ def _parse_no(resource: Resource, result: Result) -> None: if "callbacks" not in result: result["callbacks"] = [] - result.callbacks.append((("Fruits", "Date"), _parse_no)) + filter_func: Callable[[Resource], bool] = ( + lambda r: r.name in ("Fruits", "Date") and not r.is_confirmed + ) + result.callbacks.append((filter_func, _parse_no)) result.qtype = "QNo" @@ -286,7 +293,8 @@ def _dt_callback(resource: DatetimeResource, result: Result) -> None: if "callbacks" not in result: result["callbacks"] = [] - result.callbacks.append((("Date",), _dt_callback)) + filter_func: Callable[[Resource], bool] = lambda r: r.name == "Date" + result.callbacks.append((filter_func, _dt_callback)) def QFruitDate(node: Node, params: QueryStateDict, result: Result) -> None: @@ -325,7 +333,8 @@ def _dt_callback(resource: DatetimeResource, result: Result) -> None: if "callbacks" not in result: result["callbacks"] = [] - result.callbacks.append((("Date",), _dt_callback)) + filter_func: Callable[[Resource], bool] = lambda r: r.name == "Date" + result.callbacks.append((filter_func, _dt_callback)) return raise ValueError("No date in {0}".format(str(datenode))) @@ -358,7 +367,8 @@ def _dt_callback(resource: DatetimeResource, result: Result) -> None: if "callbacks" not in result: result["callbacks"] = [] - result.callbacks.append((("Date",), _dt_callback)) + filter_func: Callable[[Resource], bool] = lambda r: r.name == "Date" + result.callbacks.append((filter_func, _dt_callback)) def sentence(state: QueryStateDict, result: Result) -> None: @@ -377,6 +387,8 @@ def sentence(state: QueryStateDict, result: Result) -> None: dsm.start_dialogue() ans = dsm.get_answer() + if not ans: + raise ValueError("No answer generated!") q.set_answer(*gen_answer(ans)) return From 31c9b1338c1e5307a0f93303fccfbed66431c331 Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Mon, 27 Jun 2022 14:12:56 +0000 Subject: [PATCH 103/371] Sonos storing correctly to DB Sonos storing correctly to DB --- queries/iot_connect.py | 112 +++-- queries/iot_speakers.py | 942 ++++++++++++++++++++-------------------- query.py | 11 + 3 files changed, 551 insertions(+), 514 deletions(-) diff --git a/queries/iot_connect.py b/queries/iot_connect.py index 37c9c3d1..80433383 100644 --- a/queries/iot_connect.py +++ b/queries/iot_connect.py @@ -22,7 +22,8 @@ of random numbers, e.g. "Kastaðu tengingi", "Nefndu tölu milli 5 og 10", etc. """ - +_BREAK_LENGTH = 5 # Seconds +_BREAK_SSML = ''.format(_BREAK_LENGTH) from typing import Dict, Mapping, Optional, cast from typing_extensions import TypedDict @@ -176,60 +177,87 @@ def sentence(state: QueryStateDict, result: Result) -> None: elif result.qtype == "create_speaker_token": code = str(q.client_data("sonos_code")) - sonos_credentials_dict = {} sonos_encoded_credentials = read_api_key("SonosEncodedCredentials") response = create_token(code, sonos_encoded_credentials, host) if response.status_code != 200: print("Error:", response.status_code) print(response.text) + print("Invalid request usually means that the code is invalid") return response_json = response.json() - sonos_credentials_dict.update( - { - "access_token": response_json["access_token"], - "refresh_token": response_json["refresh_token"], - } - ) - response = get_households(sonos_credentials_dict["access_token"]) - if response.status_code != 200: - print("Error:", response.status_code) - print(response.text) - return - response_json = response.json() - sonos_credentials_dict.update( - { - "household_id": response_json["households"][0]["id"], - } - ) - response = get_groups( - sonos_credentials_dict["household_id"], - sonos_credentials_dict["access_token"], - ) - if response.status_code != 200: - print("Error:", response.status_code) - print(response.text) - return - response_json = response.json() - sonos_credentials_dict.update( - { - "group_id": response_json["groups"][0]["id"], - "player_id": response_json["players"][0]["id"], - } + access_token, refresh_token = ( + response_json["access_token"], + response_json["refresh_token"], ) - q.store_query_data( - str(q.client_id), "sonos_credentials", sonos_credentials_dict - ) - answer = "Ég bjó til tóka frá Sonos" + data_dict = create_sonos_data_dict(access_token, q) + cred_dict = create_sonos_cred_dict(access_token, refresh_token, q) + store_sonos_data_and_credentials(data_dict, cred_dict, q) + answer = "Ég bjó til tóka frá Sónos" voice_answer = answer - audio_clip( - text_to_audio_url(answer), - sonos_credentials_dict["player_id"], - sonos_credentials_dict["access_token"], - ) + # voice_answer = f"Ég ætla að tengja Sónos hátalarann. Hlustaðu vel. {_BREAK_SSML} Ég tengdi Sónos hátalarann. Góða skemmtun." + # sonos_voice_clip = ( + # f"{_BREAK_SSML} Hæ!, ég er búin að tengja þennan Sónos hátalara." + # ) + # audio_clip( + # text_to_audio_url(sonos_voice_clip), + # sonos_dict["player_id"], + # sonos_dict["access_token"], + # ) q.set_answer(response, answer, voice_answer) return +def create_sonos_data_dict(access_token, q): + data_dict = {} + households = get_households(access_token).json() + data_dict.update(households) + groups_list = [] + players_list = [] + for i in range(len(households)): + groups_object = get_groups( + households["households"][i]["id"], access_token + ).json() + groups_raw = groups_object["groups"] + players_raw = groups_object["players"] + groups_list += create_grouplist_for_db(groups_raw) + players_list += create_playerlist_for_db(players_raw) + + data_dict["groups"] = groups_list + data_dict["players"] = players_list + return data_dict + + +def create_sonos_cred_dict(access_token, refresh_token, q): + cred_dict = {} + cred_dict.update( + { + "access_token": access_token, + "refresh_token": refresh_token, + } + ) + return cred_dict + + +def store_sonos_data_and_credentials(data_dict, cred_dict, q): + sonos_dict = {} + sonos_dict["sonos"] = {"credentials": cred_dict, "data": data_dict} + q.update_client_data("IoT_Speakers", sonos_dict) + + +def create_grouplist_for_db(groups): + groups_list = [] + for i in range(len(groups)): + groups_list.append({groups[i]["name"]: groups[i]["id"]}) + return groups_list + + +def create_playerlist_for_db(players): + player_list = [] + for i in range(len(players)): + player_list.append({players[i]["name"]: players[i]["id"]}) + return player_list + + # put this in a separate file def get_households(token): """ diff --git a/queries/iot_speakers.py b/queries/iot_speakers.py index 1c2ea51c..f5f4290a 100644 --- a/queries/iot_speakers.py +++ b/queries/iot_speakers.py @@ -1,493 +1,491 @@ -""" +# """ - Greynir: Natural language processing for Icelandic +# Greynir: Natural language processing for Icelandic - Randomness query response module +# Randomness query response module - Copyright (C) 2022 Miðeind ehf. +# Copyright (C) 2022 Miðeind ehf. - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. - You should have received a copy of the GNU General Public License - along with this program. If not, see http://www.gnu.org/licenses/. +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. - This query module handles queries related to the generation - of random numbers, e.g. "Kastaðu tengingi", "Nefndu tölu milli 5 og 10", etc. +# This query module handles queries related to the generation +# of random numbers, e.g. "Kastaðu tengingi", "Nefndu tölu milli 5 og 10", etc. -""" +# """ -# TODO: add "láttu", "hafðu", "litaðu", "kveiktu" functionality. -# TODO: make the objects of sentences more modular, so that the same structure doesn't need to be written for each action -# TODO: ditto the previous comment. make the initial non-terminals general and go into specifics at the terminal level instead. -# TODO: substituion klósett, baðherbergi hugmyndÆ senda lista i javascript og profa i röð -# TODO: Embla stores old javascript code cached which has caused errors -# TODO: Cut down javascript sent to Embla -# TODO: Two specified groups or lights. -# TODO: No specified location -# TODO: Fix scene issues +# # TODO: add "láttu", "hafðu", "litaðu", "kveiktu" functionality. +# # TODO: make the objects of sentences more modular, so that the same structure doesn't need to be written for each action +# # TODO: ditto the previous comment. make the initial non-terminals general and go into specifics at the terminal level instead. +# # TODO: substituion klósett, baðherbergi hugmyndÆ senda lista i javascript og profa i röð +# # TODO: Embla stores old javascript code cached which has caused errors +# # TODO: Cut down javascript sent to Embla +# # TODO: Two specified groups or lights. +# # TODO: No specified location +# # TODO: Fix scene issues -from typing import Dict, Mapping, Optional, cast -from typing_extensions import TypedDict +# from typing import Dict, Mapping, Optional, cast +# from typing_extensions import TypedDict -import logging -import random -import json -import flask +# import logging +# import random +# import json +# import flask -from query import Query, QueryStateDict, AnswerTuple -from queries import gen_answer, read_jsfile, read_grammar_file -from tree import Result, Node +# from query import Query, QueryStateDict, AnswerTuple +# from queries import gen_answer, read_jsfile, read_grammar_file +# from tree import Result, Node -_IoT_QTYPE = "IoT" +# _IoT_QTYPE = "IoT" -TOPIC_LEMMAS = [ - "tónlist", -] +# TOPIC_LEMMAS = [ +# "tónlist", +# ] -# def QIoTSpeakerIncreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: -# result.action = "increase_volume" -# if "hue_obj" not in result: -# result["hue_obj"] = {"on": True, "bri_inc": 64} -# else: -# result["hue_obj"]["bri_inc"] = 64 -# result["hue_obj"]["on"] = True - -def help_text(lemma: str) -> str: - """Help text to return when query.py is unable to parse a query but - one of the above lemmas is found in it""" - return "Ég skil þig ef þú segir til dæmis: {0}.".format( - random.choice( - ("Hækkaðu í tónlistinni", "Kveiktu á tónlist", "Láttu vera tónlist") - ) - ) - - -# This module wants to handle parse trees for queries -HANDLE_TREE = True - -# The grammar nonterminals this module wants to handle -QUERY_NONTERMINALS = {"QIoTSpeaker"} - -# The context-free grammar for the queries recognized by this plug-in module -# GRAMMAR = read_grammar_file("iot_hue") - -GRAMMAR = f""" - -/þgf = þgf -/ef = ef - -Query → - QIoTSpeaker '?'? - -QIoTSpeaker → - QIoTSpeakerQuery - -QIoTSpeakerQuery -> - QIoTSpeakerMakeVerb QIoTSpeakerMakeRest - | QIoTSpeakerSetVerb QIoTSpeakerSetRest - | QIoTSpeakerChangeVerb QIoTSpeakerChangeRest - | QIoTSpeakerLetVerb QIoTSpeakerLetRest - | QIoTSpeakerTurnOnVerb QIoTSpeakerTurnOnRest - | QIoTSpeakerTurnOffVerb QIoTSpeakerTurnOffRest - | QIoTSpeakerIncreaseOrDecreaseVerb QIoTSpeakerIncreaseOrDecreaseRest - -QIoTSpeakerMakeVerb -> - 'gera:so'_bh - -QIoTSpeakerSetVerb -> - 'setja:so'_bh - | 'stilla:so'_bh - -QIoTSpeakerChangeVerb -> - 'breyta:so'_bh - -QIoTSpeakerLetVerb -> - 'láta:so'_bh - -QIoTSpeakerTurnOnVerb -> - 'kveikja:so'_bh - -QIoTSpeakerTurnOffVerb -> - 'slökkva:so'_bh - -QIoTSpeakerIncreaseOrDecreaseVerb -> - QIoTSpeakerIncreaseVerb - | QIoTSpeakerDecreaseVerb - -QIoTSpeakerIncreaseVerb -> - 'hækka:so'_bh - | 'auka:so'_bh - -QIoTSpeakerDecreaseVerb -> - 'lækka:so'_bh - | 'minnka:so'_bh - -QCHANGEMakeRest -> - # QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigMake - # | QCHANGESubject/þf QCHANGEHvernigMake QCHANGEHvar? - # | QCHANGEHvar? QCHANGESubject/þf QCHANGEHvernigMake - # | QCHANGEHvar? QCHANGEHvernigMake QCHANGESubject/þf - # | QCHANGEHvernigMake QCHANGESubject/þf QCHANGEHvar? - # | QCHANGEHvernigMake QCHANGEHvar? QCHANGESubject/þf - QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? - -# TODO: Add support for "stilltu rauðan lit á ljósið í eldhúsinu" -QCHANGESetRest -> - # QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigSet - # | QCHANGESubject/þf QCHANGEHvernigSet QCHANGEHvar? - # | QCHANGEHvar? QCHANGESubject/þf QCHANGEHvernigSet - # | QCHANGEHvar? QCHANGEHvernigSet QCHANGESubject/þf - # | QCHANGEHvernigSet QCHANGESubject/þf QCHANGEHvar? - # | QCHANGEHvernigSet QCHANGEHvar? QCHANGESubject/þf - "á" QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? - -QCHANGEChangeRest -> - # QCHANGESubjectOne/þgf QCHANGEHvar? QCHANGEHvernigChange - # | QCHANGESubjectOne/þgf QCHANGEHvernigChange QCHANGEHvar? - # | QCHANGEHvar? QCHANGESubjectOne/þgf QCHANGEHvernigChange - # | QCHANGEHvar? QCHANGEHvernigChange QCHANGESubjectOne/þgf - # | QCHANGEHvernigChange QCHANGESubjectOne/þgf QCHANGEHvar? - # | QCHANGEHvernigChange QCHANGEHvar? QCHANGESubjectOne/þgf - -QCHANGELetRest -> - QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigLet - | QCHANGESubject/þf QCHANGEHvernigLet QCHANGEHvar? - | QCHANGEHvar? QCHANGESubject/þf QCHANGEHvernigLet - | QCHANGEHvar? QCHANGEHvernigLet QCHANGESubject/þf - | QCHANGEHvernigLet QCHANGESubject/þf QCHANGEHvar? - | QCHANGEHvernigLet QCHANGEHvar? QCHANGESubject/þf - "vera" QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? - -QCHANGETurnOnRest -> - # QCHANGETurnOnLightsRest - # | QCHANGEAHverju QCHANGEHvar? - # | QCHANGEHvar? QCHANGEAHverju - "á" QIoTSpeakerMusicWord/þgf QIoTSpeakerHvar? - -# QCHANGETurnOnLightsRest -> -# QCHANGELightSubject/þf QCHANGEHvar? -# | QCHANGEHvar QCHANGELightSubject/þf? - -# Would be good to add "slökktu á rauða litnum" functionality -QCHANGETurnOffRest -> - # QCHANGETurnOffLightsRest - "á" QIoTSpeakerMusicWord/þgf QIoTSpeakerHvar? - -# QCHANGETurnOffLightsRest -> -# QCHANGELightSubject/þf QCHANGEHvar? -# | QCHANGEHvar QCHANGELightSubject/þf? - -# TODO: Make the subject categorization cleaner -QCHANGEIncreaseOrDecreaseRest -> - # QCHANGELightSubject/þf QCHANGEHvar? - # | QCHANGEBrightnessSubject/þf QCHANGEHvar? - QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? - | "í" QIoTSpeakerMusicWord/þgf QIoTSpeakerHvar? - -# QCHANGESubject/fall -> -# QCHANGESubjectOne/fall -# | QCHANGESubjectTwo/fall - -QIoTMusicWord -> - 'tónlist'/fall - -# # TODO: Decide whether LightSubject/þgf should be accepted -# QCHANGESubjectOne/fall -> -# QCHANGELightSubject/fall -# | QCHANGEColorSubject/fall -# | QCHANGEBrightnessSubject/fall -# | QCHANGESceneSubject/fall - -# QCHANGESubjectTwo/fall -> -# QCHANGEGroupNameSubject/fall # á bara að styðja "gerðu eldhúsið rautt", "gerðu eldhúsið rómó" "gerðu eldhúsið bjartara", t.d. - -QIoTSpeakerHvar -> - QIoTSpeakerLocationPreposition QIoTSpeakerGroupName/þgf - -# QCHANGEHvernigMake -> -# QCHANGEAnnadAndlag # gerðu litinn rauðan í eldhúsinu EÐA gerðu birtuna meiri í eldhúsinu -# | QCHANGEAdHverju # gerðu litinn að rauðum í eldhúsinu -# | QCHANGEThannigAd - -# QCHANGEHvernigSet -> -# QCHANGEAHvad -# | QCHANGEThannigAd - -# QCHANGEHvernigChange -> -# QCHANGEIHvad -# | QCHANGEThannigAd - -# QCHANGEHvernigLet -> -# QCHANGEBecome QCHANGESomethingOrSomehow -# | QCHANGEBe QCHANGESomehow - -# QCHANGEThannigAd -> -# "þannig" "að"? pfn_nf QCHANGEBeOrBecomeSubjunctive QCHANGEAnnadAndlag - -# I think these verbs only appear in these forms. -# In which case these terminals should be deleted and a direct reference should be made in the relevant non-terminals. -# QCHANGEBe -> -# "vera" - -# QCHANGEBecome -> -# "verða" - -# QCHANGEBeOrBecomeSubjunctive -> -# "verði" -# | "sé" - -# QCHANGELightSubject/fall -> -# QCHANGELight/fall - -# QCHANGEColorSubject/fall -> -# QCHANGEColorWord/fall QCHANGELight/ef? -# | QCHANGEColorWord/fall "á" QCHANGELight/þgf - -# QCHANGEBrightnessSubject/fall -> -# QCHANGEBrightnessWord/fall QCHANGELight/ef? -# | QCHANGEBrightnessWord/fall "á" QCHANGELight/þgf - -# QCHANGESceneSubject/fall -> -# QCHANGESceneWord/fall - -# QCHANGEGroupNameSubject/fall -> -# QCHANGEGroupName/fall - -QIoTSpeakerLocationPreposition -> - QIoTSpeakerLocationPrepositionFirstPart? QIoTSpeakerLocationPrepositionSecondPart - -# The latter proverbs are grammatically incorrect, but common errors, both in speech and transcription. -# The list provided is taken from StefnuAtv in Greynir.grammar. That includes "aftur:ao", which is not applicable here. -QIoTSpeakerLocationPrepositionFirstPart -> - StaðarAtv - | "fram:ao" - | "inn:ao" - | "niður:ao" - | "upp:ao" - | "út:ao" - -QIoTSpeakerLocationPrepositionSecondPart -> - "á" | "í" - -QIoTSpeakerGroupName/fall -> - no/fall - -# QCHANGELightName/fall -> +# # def QIoTSpeakerIncreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: +# # result.action = "increase_volume" +# # if "hue_obj" not in result: +# # result["hue_obj"] = {"on": True, "bri_inc": 64} +# # else: +# # result["hue_obj"]["bri_inc"] = 64 +# # result["hue_obj"]["on"] = True + + +# def help_text(lemma: str) -> str: +# """Help text to return when query.py is unable to parse a query but +# one of the above lemmas is found in it""" +# return "Ég skil þig ef þú segir til dæmis: {0}.".format( +# random.choice( +# ("Hækkaðu í tónlistinni", "Kveiktu á tónlist", "Láttu vera tónlist") +# ) +# ) + + +# # This module wants to handle parse trees for queries +# HANDLE_TREE = True + +# # The grammar nonterminals this module wants to handle +# QUERY_NONTERMINALS = {"QIoTSpeaker"} + +# # The context-free grammar for the queries recognized by this plug-in module +# # GRAMMAR = read_grammar_file("iot_hue") + +# GRAMMAR = f""" + +# /þgf = þgf +# /ef = ef + +# Query → +# QIoTSpeaker '?'? + +# QIoTSpeaker → +# QIoTSpeakerQuery + +# # QIoTSpeakerQuery -> +# # QIoTSpeakerMakeVerb QIoTSpeakerMakeRest +# # | QIoTSpeakerSetVerb QIoTSpeakerSetRest +# # | QIoTSpeakerChangeVerb QIoTSpeakerChangeRest +# # | QIoTSpeakerLetVerb QIoTSpeakerLetRest +# # | QIoTSpeakerTurnOnVerb QIoTSpeakerTurnOnRest +# # | QIoTSpeakerTurnOffVerb QIoTSpeakerTurnOffRest +# # | QIoTSpeakerIncreaseOrDecreaseVerb QIoTSpeakerIncreaseOrDecreaseRest + +# QIoTSpeakerMakeVerb -> +# 'gera:so'_bh + +# QIoTSpeakerSetVerb -> +# 'setja:so'_bh +# | 'stilla:so'_bh + +# QIoTSpeakerChangeVerb -> +# 'breyta:so'_bh + +# QIoTSpeakerLetVerb -> +# 'láta:so'_bh + +# QIoTSpeakerTurnOnVerb -> +# 'kveikja:so'_bh + +# QIoTSpeakerTurnOffVerb -> +# 'slökkva:so'_bh + +# QIoTSpeakerIncreaseOrDecreaseVerb -> +# QIoTSpeakerIncreaseVerb +# | QIoTSpeakerDecreaseVerb + +# QIoTSpeakerIncreaseVerb -> +# 'hækka:so'_bh +# | 'auka:so'_bh + +# QIoTSpeakerDecreaseVerb -> +# 'lækka:so'_bh +# | 'minnka:so'_bh + +# # QCHANGEMakeRest -> +# # QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigMake +# # | QCHANGESubject/þf QCHANGEHvernigMake QCHANGEHvar? +# # | QCHANGEHvar? QCHANGESubject/þf QCHANGEHvernigMake +# # | QCHANGEHvar? QCHANGEHvernigMake QCHANGESubject/þf +# # | QCHANGEHvernigMake QCHANGESubject/þf QCHANGEHvar? +# # | QCHANGEHvernigMake QCHANGEHvar? QCHANGESubject/þf +# QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? + +# # TODO: Add support for "stilltu rauðan lit á ljósið í eldhúsinu" +# QCHANGESetRest -> +# # QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigSet +# # | QCHANGESubject/þf QCHANGEHvernigSet QCHANGEHvar? +# # | QCHANGEHvar? QCHANGESubject/þf QCHANGEHvernigSet +# # | QCHANGEHvar? QCHANGEHvernigSet QCHANGESubject/þf +# # | QCHANGEHvernigSet QCHANGESubject/þf QCHANGEHvar? +# # | QCHANGEHvernigSet QCHANGEHvar? QCHANGESubject/þf +# "á" QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? + +# QCHANGEChangeRest -> +# # QCHANGESubjectOne/þgf QCHANGEHvar? QCHANGEHvernigChange +# # | QCHANGESubjectOne/þgf QCHANGEHvernigChange QCHANGEHvar? +# # | QCHANGEHvar? QCHANGESubjectOne/þgf QCHANGEHvernigChange +# # | QCHANGEHvar? QCHANGEHvernigChange QCHANGESubjectOne/þgf +# # | QCHANGEHvernigChange QCHANGESubjectOne/þgf QCHANGEHvar? +# # | QCHANGEHvernigChange QCHANGEHvar? QCHANGESubjectOne/þgf + +# QCHANGELetRest -> +# QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigLet +# | QCHANGESubject/þf QCHANGEHvernigLet QCHANGEHvar? +# | QCHANGEHvar? QCHANGESubject/þf QCHANGEHvernigLet +# | QCHANGEHvar? QCHANGEHvernigLet QCHANGESubject/þf +# | QCHANGEHvernigLet QCHANGESubject/þf QCHANGEHvar? +# | QCHANGEHvernigLet QCHANGEHvar? QCHANGESubject/þf +# "vera" QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? + +# QCHANGETurnOnRest -> +# # QCHANGETurnOnLightsRest +# # | QCHANGEAHverju QCHANGEHvar? +# # | QCHANGEHvar? QCHANGEAHverju +# "á" QIoTSpeakerMusicWord/þgf QIoTSpeakerHvar? + +# # QCHANGETurnOnLightsRest -> +# # QCHANGELightSubject/þf QCHANGEHvar? +# # | QCHANGEHvar QCHANGELightSubject/þf? + +# # Would be good to add "slökktu á rauða litnum" functionality +# QCHANGETurnOffRest -> +# # QCHANGETurnOffLightsRest +# "á" QIoTSpeakerMusicWord/þgf QIoTSpeakerHvar? + +# # QCHANGETurnOffLightsRest -> +# # QCHANGELightSubject/þf QCHANGEHvar? +# # | QCHANGEHvar QCHANGELightSubject/þf? + +# # TODO: Make the subject categorization cleaner +# QCHANGEIncreaseOrDecreaseRest -> +# # QCHANGELightSubject/þf QCHANGEHvar? +# # | QCHANGEBrightnessSubject/þf QCHANGEHvar? +# QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? +# | "í" QIoTSpeakerMusicWord/þgf QIoTSpeakerHvar? + +# # QCHANGESubject/fall -> +# # QCHANGESubjectOne/fall +# # | QCHANGESubjectTwo/fall + +# QIoTMusicWord -> +# 'tónlist'/fall + +# # # TODO: Decide whether LightSubject/þgf should be accepted +# # QCHANGESubjectOne/fall -> +# # QCHANGELightSubject/fall +# # | QCHANGEColorSubject/fall +# # | QCHANGEBrightnessSubject/fall +# # | QCHANGESceneSubject/fall + +# # QCHANGESubjectTwo/fall -> +# # QCHANGEGroupNameSubject/fall # á bara að styðja "gerðu eldhúsið rautt", "gerðu eldhúsið rómó" "gerðu eldhúsið bjartara", t.d. + +# QIoTSpeakerHvar -> +# QIoTSpeakerLocationPreposition QIoTSpeakerGroupName/þgf + +# # QCHANGEHvernigMake -> +# # QCHANGEAnnadAndlag # gerðu litinn rauðan í eldhúsinu EÐA gerðu birtuna meiri í eldhúsinu +# # | QCHANGEAdHverju # gerðu litinn að rauðum í eldhúsinu +# # | QCHANGEThannigAd + +# # QCHANGEHvernigSet -> +# # QCHANGEAHvad +# # | QCHANGEThannigAd + +# # QCHANGEHvernigChange -> +# # QCHANGEIHvad +# # | QCHANGEThannigAd + +# # QCHANGEHvernigLet -> +# # QCHANGEBecome QCHANGESomethingOrSomehow +# # | QCHANGEBe QCHANGESomehow + +# # QCHANGEThannigAd -> +# # "þannig" "að"? pfn_nf QCHANGEBeOrBecomeSubjunctive QCHANGEAnnadAndlag + +# # I think these verbs only appear in these forms. +# # In which case these terminals should be deleted and a direct reference should be made in the relevant non-terminals. +# # QCHANGEBe -> +# # "vera" + +# # QCHANGEBecome -> +# # "verða" + +# # QCHANGEBeOrBecomeSubjunctive -> +# # "verði" +# # | "sé" + +# # QCHANGELightSubject/fall -> +# # QCHANGELight/fall + +# # QCHANGEColorSubject/fall -> +# # QCHANGEColorWord/fall QCHANGELight/ef? +# # | QCHANGEColorWord/fall "á" QCHANGELight/þgf + +# # QCHANGEBrightnessSubject/fall -> +# # QCHANGEBrightnessWord/fall QCHANGELight/ef? +# # | QCHANGEBrightnessWord/fall "á" QCHANGELight/þgf + +# # QCHANGESceneSubject/fall -> +# # QCHANGESceneWord/fall + +# # QCHANGEGroupNameSubject/fall -> +# # QCHANGEGroupName/fall + +# QIoTSpeakerLocationPreposition -> +# QIoTSpeakerLocationPrepositionFirstPart? QIoTSpeakerLocationPrepositionSecondPart + +# # The latter proverbs are grammatically incorrect, but common errors, both in speech and transcription. +# # The list provided is taken from StefnuAtv in Greynir.grammar. That includes "aftur:ao", which is not applicable here. +# QIoTSpeakerLocationPrepositionFirstPart -> +# StaðarAtv +# | "fram:ao" +# | "inn:ao" +# | "niður:ao" +# | "upp:ao" +# | "út:ao" + +# QIoTSpeakerLocationPrepositionSecondPart -> +# "á" | "í" + +# QIoTSpeakerGroupName/fall -> # no/fall -# QCHANGEColorName -> -# {" | ".join(f"'{color}:lo'" for color in _COLORS.keys())} - -# QCHANGESceneName -> -# no -# | lo - -# QCHANGEAnnadAndlag -> -# QCHANGENewSetting/nf -# | QCHANGESpyrjaHuldu/nf - -# QCHANGEAdHverju -> -# "að" QCHANGENewSetting/þgf - -# QCHANGEAHvad -> -# "á" QCHANGENewSetting/þf - -# QCHANGEIHvad -> -# "í" QCHANGENewSetting/þf - -# QCHANGEAHverju -> -# "á" QCHANGELight/þgf -# | "á" QCHANGENewSetting/þgf - -# QCHANGESomethingOrSomehow -> -# QCHANGEAnnadAndlag -# | QCHANGEAdHverju - -# QCHANGESomehow -> -# QCHANGEAnnadAndlag -# | QCHANGEThannigAd - -# QCHANGELight/fall -> -# QCHANGELightName/fall -# | QCHANGELightWord/fall - -# # Should 'birta' be included -# QCHANGELightWord/fall -> -# 'ljós'/fall -# | 'lýsing'/fall -# | 'birta'/fall -# | 'Birta'/fall - -# QCHANGEColorWord/fall -> -# 'litur'/fall -# | 'litblær'/fall -# | 'blær'/fall - -# QCHANGEBrightnessWords/fall -> -# 'bjartur'/fall -# | QCHANGEBrightnessWord/fall +# # QCHANGELightName/fall -> +# # no/fall + + +# # QCHANGESceneName -> +# # no +# # | lo + +# # QCHANGEAnnadAndlag -> +# # QCHANGENewSetting/nf +# # | QCHANGESpyrjaHuldu/nf + +# # QCHANGEAdHverju -> +# # "að" QCHANGENewSetting/þgf + +# # QCHANGEAHvad -> +# # "á" QCHANGENewSetting/þf + +# # QCHANGEIHvad -> +# # "í" QCHANGENewSetting/þf + +# # QCHANGEAHverju -> +# # "á" QCHANGELight/þgf +# # | "á" QCHANGENewSetting/þgf + +# # QCHANGESomethingOrSomehow -> +# # QCHANGEAnnadAndlag +# # | QCHANGEAdHverju + +# # QCHANGESomehow -> +# # QCHANGEAnnadAndlag +# # | QCHANGEThannigAd + +# # QCHANGELight/fall -> +# # QCHANGELightName/fall +# # | QCHANGELightWord/fall + +# # # Should 'birta' be included +# # QCHANGELightWord/fall -> +# # 'ljós'/fall +# # | 'lýsing'/fall +# # | 'birta'/fall +# # | 'Birta'/fall + +# # QCHANGEColorWord/fall -> +# # 'litur'/fall +# # | 'litblær'/fall +# # | 'blær'/fall + +# # QCHANGEBrightnessWords/fall -> +# # 'bjartur'/fall +# # | QCHANGEBrightnessWord/fall + +# # QCHANGEBrightnessWord/fall -> +# # 'birta'/fall +# # | 'Birta'/fall +# # | 'birtustig'/fall + +# # QCHANGESceneWord/fall -> +# # 'sena'/fall +# # | 'stemning'/fall +# # | 'stemming'/fall +# # | 'stemmning'/fall + +# # # Need to ask Hulda how this works. +# # QCHANGESpyrjaHuldu/fall -> +# # # QCHANGEHuldaColor/fall +# # QCHANGEHuldaBrightness/fall +# # # | QCHANGEHuldaScene/fall + +# # # Do I need a "new light state" non-terminal? +# # QCHANGENewSetting/fall -> +# # QCHANGENewColor/fall +# # | QCHANGENewBrightness/fall +# # | QCHANGENewScene/fall + +# # # Missing "meira dimmt" +# # QCHANGEHuldaBrightness/fall -> +# # QCHANGEMoreBrighterOrHigher/fall QCHANGEBrightnessWords/fall? +# # | QCHANGELessDarkerOrLower/fall QCHANGEBrightnessWords/fall? + +# # #Unsure about whether to include /fall after QCHANGEColorName +# # QCHANGENewColor/fall -> +# # QCHANGEColorWord/fall QCHANGEColorName +# # | QCHANGEColorName QCHANGEColorWord/fall? + +# # QCHANGENewBrightness/fall -> +# # 'sá'/fall? QCHANGEBrightestOrDarkest/fall +# # | QCHANGEBrightestOrDarkest/fall QCHANGEBrightnessOrSettingWord/fall + +# # QCHANGENewScene/fall -> +# # QCHANGESceneWord/fall QCHANGESceneName +# # | QCHANGESceneName QCHANGESceneWord/fall? + +# # QCHANGEMoreBrighterOrHigher/fall -> +# # 'mikill:lo'_mst/fall +# # | 'bjartur:lo'_mst/fall +# # | 'ljós:lo'_mst/fall +# # | 'hár:lo'_mst/fall + +# # QCHANGELessDarkerOrLower/fall -> +# # 'lítill:lo'_mst/fall +# # | 'dökkur:lo'_mst/fall +# # | 'dimmur:lo'_mst/fall +# # | 'lágur:lo'_mst/fall + +# # QCHANGEBrightestOrDarkest/fall -> +# # QCHANGEBrightest/fall +# # | QCHANGEDarkest/fall + +# # QCHANGEBrightest/fall -> +# # 'bjartur:lo'_evb +# # | 'bjartur:lo'_esb +# # | 'ljós:lo'_evb +# # | 'ljós:lo'_esb + +# # QCHANGEDarkest/fall -> +# # 'dimmur:lo'_evb +# # | 'dimmur:lo'_esb +# # | 'dökkur:lo'_evb +# # | 'dökkur:lo'_esb + +# # QCHANGEBrightnessOrSettingWord/fall -> +# # QCHANGEBrightnessWord/fall +# # | QCHANGESettingWord/fall + +# # QCHANGESettingWord/fall -> +# # 'stilling'/fall + +# """ -# QCHANGEBrightnessWord/fall -> -# 'birta'/fall -# | 'Birta'/fall -# | 'birtustig'/fall -# QCHANGESceneWord/fall -> -# 'sena'/fall -# | 'stemning'/fall -# | 'stemming'/fall -# | 'stemmning'/fall - -# # Need to ask Hulda how this works. -# QCHANGESpyrjaHuldu/fall -> -# # QCHANGEHuldaColor/fall -# QCHANGEHuldaBrightness/fall -# # | QCHANGEHuldaScene/fall - -# # Do I need a "new light state" non-terminal? -# QCHANGENewSetting/fall -> -# QCHANGENewColor/fall -# | QCHANGENewBrightness/fall -# | QCHANGENewScene/fall +# def QIoTSpeakerIncreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: +# result.action = "increase_volume" -# # Missing "meira dimmt" -# QCHANGEHuldaBrightness/fall -> -# QCHANGEMoreBrighterOrHigher/fall QCHANGEBrightnessWords/fall? -# | QCHANGELessDarkerOrLower/fall QCHANGEBrightnessWords/fall? -# #Unsure about whether to include /fall after QCHANGEColorName -# QCHANGENewColor/fall -> -# QCHANGEColorWord/fall QCHANGEColorName -# | QCHANGEColorName QCHANGEColorWord/fall? - -# QCHANGENewBrightness/fall -> -# 'sá'/fall? QCHANGEBrightestOrDarkest/fall -# | QCHANGEBrightestOrDarkest/fall QCHANGEBrightnessOrSettingWord/fall - -# QCHANGENewScene/fall -> -# QCHANGESceneWord/fall QCHANGESceneName -# | QCHANGESceneName QCHANGESceneWord/fall? - -# QCHANGEMoreBrighterOrHigher/fall -> -# 'mikill:lo'_mst/fall -# | 'bjartur:lo'_mst/fall -# | 'ljós:lo'_mst/fall -# | 'hár:lo'_mst/fall - -# QCHANGELessDarkerOrLower/fall -> -# 'lítill:lo'_mst/fall -# | 'dökkur:lo'_mst/fall -# | 'dimmur:lo'_mst/fall -# | 'lágur:lo'_mst/fall - -# QCHANGEBrightestOrDarkest/fall -> -# QCHANGEBrightest/fall -# | QCHANGEDarkest/fall - -# QCHANGEBrightest/fall -> -# 'bjartur:lo'_evb -# | 'bjartur:lo'_esb -# | 'ljós:lo'_evb -# | 'ljós:lo'_esb - -# QCHANGEDarkest/fall -> -# 'dimmur:lo'_evb -# | 'dimmur:lo'_esb -# | 'dökkur:lo'_evb -# | 'dökkur:lo'_esb - -# QCHANGEBrightnessOrSettingWord/fall -> -# QCHANGEBrightnessWord/fall -# | QCHANGESettingWord/fall - -# QCHANGESettingWord/fall -> -# 'stilling'/fall - -""" - - -def QIoTSpeakerIncreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: - result.action = "increase_volume" - - -def QIoTSpeakerDecreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: - result.action = "decrease_volume" - - -def QIoTSpeakerGroupName(node: Node, params: QueryStateDict, result: Result) -> None: - result["group_name"] = result._indefinite - - -def sentence(state: QueryStateDict, result: Result) -> None: - """Called when sentence processing is complete""" - q: Query = state["query"] - - q.set_qtype(result.get["qtype"]) - - smartdevice_type = "smartSpeaker" - - # Fetch relevant data from the device_data table to perform an action on the lights - device_data = cast(Optional[DeviceData], q.client_data(smartdevice_type)) - - - if device_data is not None and smartdevice_type in device_data: - dev = device_data[smartdevice_type] - assert dev is not None - selected_light = dev.get("selected_light") - hue_credentials = dev.get("philips_hue") - bridge_ip = hue_credentials.get("ipAddress") - username = hue_credentials.get("username") - - if not device_data or not hue_credentials: - answer = "Það vantar að tengja Philips Hub-inn." - q.set_answer(*gen_answer(answer)) - return - - # Successfully matched a query type - print("bridge_ip: ", bridge_ip) - print("username: ", username) - print("selected light :", selected_light) - print("hue credentials :", hue_credentials) - - try: - # kalla í javascripts stuff - light_or_group_name = result.get("light_name", result.get("group_name", "")) - color_name = result.get("color_name", "") - print("GROUP NAME:", light_or_group_name) - print("COLOR NAME:", color_name) - print(result.hue_obj) - q.set_answer( - *gen_answer( - "ég var að kveikja ljósin! " - # + group_name - # + " " - # + color_name - # + " " - # + result.action - # + " " - # + str(result.hue_obj.get("hue", "enginn litur")) - ) - ) - js = ( - read_jsfile("IoT_Embla/fuse.js") - + f"var BRIDGE_IP = '{bridge_ip}';var USERNAME = '{username}';" - + read_jsfile("IoT_Embla/Philips_Hue/fuse_search.js") - + read_jsfile("IoT_Embla/Philips_Hue/lights.js") - + read_jsfile("IoT_Embla/Philips_Hue/set_lights.js") - ) - js += f"setLights('{light_or_group_name}', '{json.dumps(result.hue_obj)}');" - q.set_command(js) - except Exception as e: - logging.warning("Exception while processing random query: {0}".format(e)) - q.set_error("E_EXCEPTION: {0}".format(e)) - raise - - # f"var BRIDGE_IP = '192.168.1.68';var USERNAME = 'p3obluiXT13IbHMpp4X63ZvZnpNRdbqqMt723gy2';" +# def QIoTSpeakerDecreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: +# result.action = "decrease_volume" + + +# def QIoTSpeakerGroupName(node: Node, params: QueryStateDict, result: Result) -> None: +# result["group_name"] = result._indefinite + + +# def sentence(state: QueryStateDict, result: Result) -> None: +# """Called when sentence processing is complete""" +# q: Query = state["query"] + +# q.set_qtype(result.get["qtype"]) + +# smartdevice_type = "smartSpeaker" + +# # Fetch relevant data from the device_data table to perform an action on the lights +# device_data = cast(Optional[DeviceData], q.client_data(smartdevice_type)) + +# if device_data is not None and smartdevice_type in device_data: +# dev = device_data[smartdevice_type] +# assert dev is not None +# selected_light = dev.get("selected_light") +# hue_credentials = dev.get("philips_hue") +# bridge_ip = hue_credentials.get("ipAddress") +# username = hue_credentials.get("username") + +# if not device_data or not hue_credentials: +# answer = "Það vantar að tengja Philips Hub-inn." +# q.set_answer(*gen_answer(answer)) +# return + +# # Successfully matched a query type +# print("bridge_ip: ", bridge_ip) +# print("username: ", username) +# print("selected light :", selected_light) +# print("hue credentials :", hue_credentials) + +# try: +# # kalla í javascripts stuff +# light_or_group_name = result.get("light_name", result.get("group_name", "")) +# color_name = result.get("color_name", "") +# print("GROUP NAME:", light_or_group_name) +# print("COLOR NAME:", color_name) +# print(result.hue_obj) +# q.set_answer( +# *gen_answer( +# "ég var að kveikja ljósin! " +# # + group_name +# # + " " +# # + color_name +# # + " " +# # + result.action +# # + " " +# # + str(result.hue_obj.get("hue", "enginn litur")) +# ) +# ) +# js = ( +# read_jsfile("IoT_Embla/fuse.js") +# + f"var BRIDGE_IP = '{bridge_ip}';var USERNAME = '{username}';" +# + read_jsfile("IoT_Embla/Philips_Hue/fuse_search.js") +# + read_jsfile("IoT_Embla/Philips_Hue/lights.js") +# + read_jsfile("IoT_Embla/Philips_Hue/set_lights.js") +# ) +# js += f"setLights('{light_or_group_name}', '{json.dumps(result.hue_obj)}');" +# q.set_command(js) +# except Exception as e: +# logging.warning("Exception while processing random query: {0}".format(e)) +# q.set_error("E_EXCEPTION: {0}".format(e)) +# raise + +# # f"var BRIDGE_IP = '192.168.1.68';var USERNAME = 'p3obluiXT13IbHMpp4X63ZvZnpNRdbqqMt723gy2';" diff --git a/query.py b/query.py index 7d41bb3c..126b6783 100755 --- a/query.py +++ b/query.py @@ -892,6 +892,17 @@ def set_client_data(self, key: str, data: ClientDataDict) -> None: return Query.store_query_data(self.client_id, key, data) + def update_client_data(self, key: str, new_data: ClientDataDict) -> None: + print("new_data :", new_data) + stored_data = self.client_data(key) + print("stored_data before update:", stored_data) + if stored_data is None: + self.set_client_data(key, new_data) + return + stored_data.update(new_data) + print("stored_data :", stored_data) + self.set_client_data(key, stored_data) + @staticmethod def store_query_data(client_id: str, key: str, data: ClientDataDict) -> bool: """Save client query data in the database, under the given key""" From e9c4d64f38b9be3c19ff88bafa2a358a1a456227 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Mon, 27 Jun 2022 14:37:19 +0000 Subject: [PATCH 104/371] added answer functions in fruitseller_module to generate answers for each state --- queries/dialogue.py | 6 ++- queries/fruitseller_module.py | 72 ++++++++++++++++++++++++++++++++++- 2 files changed, 76 insertions(+), 2 deletions(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index a68d2476..bfdbdf03 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -42,6 +42,7 @@ class ResourceState(IntEnum): CONFIRMED = auto() PAUSED = auto() SKIPPED = auto() + CANCELLED = auto() ########################## @@ -346,6 +347,9 @@ def update_dialogue_state(self): def get_resource(self, name: str) -> Resource: return self._resources[name] + def get_result(self) -> Result: + return self._result + def get_answer(self) -> str: # Executing callbacks self._execute_callbacks() @@ -366,7 +370,7 @@ def get_answer(self) -> str: else: self.update_dialogue_state() if ans is None: - raise ValueError("No answer generated") + ans = self._resources[FINAL_RESOURCE_NAME].prompts["final"] return ans def _execute_callbacks(self) -> None: diff --git a/queries/fruitseller_module.py b/queries/fruitseller_module.py index 8cf63b91..3d073fcd 100644 --- a/queries/fruitseller_module.py +++ b/queries/fruitseller_module.py @@ -1,4 +1,4 @@ -from typing import Any, Callable, List, cast +from typing import Any, Callable, List, Optional, cast import json import logging import datetime @@ -135,6 +135,68 @@ _START_DIALOGUE_QTYPE = "QFruitStartQuery" _DIALOGUE_NAME = "fruitseller" +def _generate_fruit_answer(resource: Resource, dsm: DialogueStateManager) -> Optional[str]: + ans: Optional[str] = None + if dsm.get_result()["fruitsEmpty"]: + ans = resource.prompts["empty"] + elif dsm.get_result()["fruitOptions"]: + ans = resource.prompts["options"] + if resource.state is ResourceState.CONFIRMED: + return None + if resource.state is ResourceState.UNFULFILLED: + ans = resource.prompts["initial"] + elif resource.state is ResourceState.PARTIALLY_FULFILLED: + ans = ( + f"{resource.prompts['repeat'].format(list_items = _list_items(resource.data))}" + ) + elif resource.state is ResourceState.FULFILLED: + ans = ( + f"{resource.prompts['confirm'].format(list_items = _list_items(resource.data))}" + ) + return ans + +def _generate_date_answer(resource: DatetimeResource, dsm: DialogueStateManager) -> Optional[str]: + ans: Optional[str] = None + if resource.state is ResourceState.CONFIRMED: + return None + + if resource.state is ResourceState.UNFULFILLED: + ans = resource.prompts["initial"] + + elif resource.state is ResourceState.PARTIALLY_FULFILLED: + if resource.has_date(): + ans = resource.prompts["date_fulfilled"].format( + date = resource.data[0].strftime("%Y/%m/%d") + ) + if resource.has_time() and resource.prompts["time_fulfilled"]: + ans = resource.prompts["time_fulfilled"].format( + time = resource.data[1].strftime("%H:%M") + ) + + elif resource.state is ResourceState.FULFILLED: + if resource.has_date() and resource.has_time(): + ans = resource.prompts["confirm"].format( + date_time=datetime.datetime.combine( + cast(datetime.date, resource.data[0]), + cast(datetime.time, resource.data[1]), + ).strftime("%Y/%m/%d %H:%M") + ) + return ans + +def _generate_final_answer(resource: Resource, dsm: DialogueStateManager) -> Optional[str]: + ans: Optional[str] = None + if resource.state is ResourceState.CONFIRMED: + date_resource = dsm.get_resource("Date") + ans = resource.prompts["final"].format( + fruits = _list_items(dsm.get_resource("Fruits").data), + date_time = datetime.datetime.combine( + cast(datetime.date, date_resource.data[0]), + cast(datetime.time, date_resource.data[1]), + ).strftime("%Y/%m/%d %H:%M") + ) + elif resource.state is ResourceState.CANCELLED: + ans = resource.prompts["cancelled"] + return ans def _list_items(items: Any) -> str: item_list: List[str] = [] @@ -179,6 +241,7 @@ def _remove_fruit(resource: Resource, result: Result) -> None: if len(resource.data) == 0: resource.state = ResourceState.UNFULFILLED resource.set_answer("empty") + result.fruitsEmpty = True else: resource.state = ResourceState.PARTIALLY_FULFILLED resource.set_answer("repeat", list_items=_list_items(resource.data)) @@ -191,13 +254,20 @@ def _remove_fruit(resource: Resource, result: Result) -> None: def QCancelOrder(node: Node, params: QueryStateDict, result: Result): + def _cancel_order(resource: Resource, result: Result) -> None: + resource.state = ResourceState.CANCELLED result.qtype = "QCancelOrder" result.answer_key = ("Final", "cancelled") + if "callbacks" not in result: + result["callbacks"] = [] + filter_func: Callable[[Resource], bool] = lambda r: r.name == "Final" + result.callbacks.append((filter_func, _cancel_order)) def QFruitOptionsQuery(node: Node, params: QueryStateDict, result: Result): result.qtype = "QFruitOptionsQuery" result.answer_key = ("Fruits", "options") + result.fruitOptions = True def QYes(node: Node, params: QueryStateDict, result: Result): From 8473067a81e5e92b3ca7ce20f3621215c0cab31f Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Mon, 27 Jun 2022 16:12:07 +0000 Subject: [PATCH 105/371] fixing sentence function for speaker functionality --- queries/iot_speakers.py | 48 +++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/queries/iot_speakers.py b/queries/iot_speakers.py index 1c2ea51c..e1ae26eb 100644 --- a/queries/iot_speakers.py +++ b/queries/iot_speakers.py @@ -435,27 +435,33 @@ def sentence(state: QueryStateDict, result: Result) -> None: smartdevice_type = "smartSpeaker" # Fetch relevant data from the device_data table to perform an action on the lights - device_data = cast(Optional[DeviceData], q.client_data(smartdevice_type)) - - - if device_data is not None and smartdevice_type in device_data: - dev = device_data[smartdevice_type] - assert dev is not None - selected_light = dev.get("selected_light") - hue_credentials = dev.get("philips_hue") - bridge_ip = hue_credentials.get("ipAddress") - username = hue_credentials.get("username") - - if not device_data or not hue_credentials: - answer = "Það vantar að tengja Philips Hub-inn." - q.set_answer(*gen_answer(answer)) - return - - # Successfully matched a query type - print("bridge_ip: ", bridge_ip) - print("username: ", username) - print("selected light :", selected_light) - print("hue credentials :", hue_credentials) + sonos_code = q.client_data("sonos_code") + device_data = q.client_data("sonos_credentials").json() + + # TODO: Need to add check for if there are no registered devices to an account, probably when initilazing the querydata + if device_data is not None: + try: + access_token = device_data.get("access_token") + refresh_token = device_data.get("refresh_token") + except: + answer = "Mig vantar Sonos-tóka til að framkvæma þessa aðgerð." + try: + household_id = device_data.get("household_id") + group_id = device_data.get("group_id") + player_id = device_data.get("player_id") + except: + answer = "Mig vantar auðkenni Sonos-tækjanna þinna." + else: + answer = "Mig vantar upplýsingar um Sonos-tækin þín." + + + # Successfully fetched data from the device_data table + print("access_token: " + access_token) + print("refresh_token: " + refresh_token) + print("household_id: " + household_id) + print("group_id: " + group_id) + print("player_id: " + player_id) + try: # kalla í javascripts stuff From 07635b33a9075c4a26656884d1ae1a903e2af9b6 Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Mon, 27 Jun 2022 16:29:01 +0000 Subject: [PATCH 106/371] uncommented iot_speakers --- queries/iot_speakers.py | 940 ++++++++++++++++++++-------------------- 1 file changed, 470 insertions(+), 470 deletions(-) diff --git a/queries/iot_speakers.py b/queries/iot_speakers.py index f5f4290a..4ab9bde7 100644 --- a/queries/iot_speakers.py +++ b/queries/iot_speakers.py @@ -1,491 +1,491 @@ -# """ +""" -# Greynir: Natural language processing for Icelandic + Greynir: Natural language processing for Icelandic -# Randomness query response module + Randomness query response module -# Copyright (C) 2022 Miðeind ehf. + Copyright (C) 2022 Miðeind ehf. -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. -# You should have received a copy of the GNU General Public License -# along with this program. If not, see http://www.gnu.org/licenses/. + You should have received a copy of the GNU General Public License + along with this program. If not, see http://www.gnu.org/licenses/. -# This query module handles queries related to the generation -# of random numbers, e.g. "Kastaðu tengingi", "Nefndu tölu milli 5 og 10", etc. + This query module handles queries related to the generation + of random numbers, e.g. "Kastaðu tengingi", "Nefndu tölu milli 5 og 10", etc. -# """ +""" -# # TODO: add "láttu", "hafðu", "litaðu", "kveiktu" functionality. -# # TODO: make the objects of sentences more modular, so that the same structure doesn't need to be written for each action -# # TODO: ditto the previous comment. make the initial non-terminals general and go into specifics at the terminal level instead. -# # TODO: substituion klósett, baðherbergi hugmyndÆ senda lista i javascript og profa i röð -# # TODO: Embla stores old javascript code cached which has caused errors -# # TODO: Cut down javascript sent to Embla -# # TODO: Two specified groups or lights. -# # TODO: No specified location -# # TODO: Fix scene issues +# TODO: add "láttu", "hafðu", "litaðu", "kveiktu" functionality. +# TODO: make the objects of sentences more modular, so that the same structure doesn't need to be written for each action +# TODO: ditto the previous comment. make the initial non-terminals general and go into specifics at the terminal level instead. +# TODO: substituion klósett, baðherbergi hugmyndÆ senda lista i javascript og profa i röð +# TODO: Embla stores old javascript code cached which has caused errors +# TODO: Cut down javascript sent to Embla +# TODO: Two specified groups or lights. +# TODO: No specified location +# TODO: Fix scene issues -# from typing import Dict, Mapping, Optional, cast -# from typing_extensions import TypedDict +from typing import Dict, Mapping, Optional, cast +from typing_extensions import TypedDict -# import logging -# import random -# import json -# import flask +import logging +import random +import json +import flask -# from query import Query, QueryStateDict, AnswerTuple -# from queries import gen_answer, read_jsfile, read_grammar_file -# from tree import Result, Node +from query import Query, QueryStateDict, AnswerTuple +from queries import gen_answer, read_jsfile, read_grammar_file +from tree import Result, Node -# _IoT_QTYPE = "IoT" +_IoT_QTYPE = "IoT" -# TOPIC_LEMMAS = [ -# "tónlist", -# ] +TOPIC_LEMMAS = [ + "tónlist", +] -# # def QIoTSpeakerIncreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: -# # result.action = "increase_volume" -# # if "hue_obj" not in result: -# # result["hue_obj"] = {"on": True, "bri_inc": 64} -# # else: -# # result["hue_obj"]["bri_inc"] = 64 -# # result["hue_obj"]["on"] = True - - -# def help_text(lemma: str) -> str: -# """Help text to return when query.py is unable to parse a query but -# one of the above lemmas is found in it""" -# return "Ég skil þig ef þú segir til dæmis: {0}.".format( -# random.choice( -# ("Hækkaðu í tónlistinni", "Kveiktu á tónlist", "Láttu vera tónlist") -# ) -# ) - - -# # This module wants to handle parse trees for queries -# HANDLE_TREE = True - -# # The grammar nonterminals this module wants to handle -# QUERY_NONTERMINALS = {"QIoTSpeaker"} - -# # The context-free grammar for the queries recognized by this plug-in module -# # GRAMMAR = read_grammar_file("iot_hue") - -# GRAMMAR = f""" - -# /þgf = þgf -# /ef = ef - -# Query → -# QIoTSpeaker '?'? - -# QIoTSpeaker → -# QIoTSpeakerQuery - -# # QIoTSpeakerQuery -> -# # QIoTSpeakerMakeVerb QIoTSpeakerMakeRest -# # | QIoTSpeakerSetVerb QIoTSpeakerSetRest -# # | QIoTSpeakerChangeVerb QIoTSpeakerChangeRest -# # | QIoTSpeakerLetVerb QIoTSpeakerLetRest -# # | QIoTSpeakerTurnOnVerb QIoTSpeakerTurnOnRest -# # | QIoTSpeakerTurnOffVerb QIoTSpeakerTurnOffRest -# # | QIoTSpeakerIncreaseOrDecreaseVerb QIoTSpeakerIncreaseOrDecreaseRest - -# QIoTSpeakerMakeVerb -> -# 'gera:so'_bh - -# QIoTSpeakerSetVerb -> -# 'setja:so'_bh -# | 'stilla:so'_bh - -# QIoTSpeakerChangeVerb -> -# 'breyta:so'_bh - -# QIoTSpeakerLetVerb -> -# 'láta:so'_bh - -# QIoTSpeakerTurnOnVerb -> -# 'kveikja:so'_bh - -# QIoTSpeakerTurnOffVerb -> -# 'slökkva:so'_bh - -# QIoTSpeakerIncreaseOrDecreaseVerb -> -# QIoTSpeakerIncreaseVerb -# | QIoTSpeakerDecreaseVerb - -# QIoTSpeakerIncreaseVerb -> -# 'hækka:so'_bh -# | 'auka:so'_bh - -# QIoTSpeakerDecreaseVerb -> -# 'lækka:so'_bh -# | 'minnka:so'_bh - -# # QCHANGEMakeRest -> -# # QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigMake -# # | QCHANGESubject/þf QCHANGEHvernigMake QCHANGEHvar? -# # | QCHANGEHvar? QCHANGESubject/þf QCHANGEHvernigMake -# # | QCHANGEHvar? QCHANGEHvernigMake QCHANGESubject/þf -# # | QCHANGEHvernigMake QCHANGESubject/þf QCHANGEHvar? -# # | QCHANGEHvernigMake QCHANGEHvar? QCHANGESubject/þf -# QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? - -# # TODO: Add support for "stilltu rauðan lit á ljósið í eldhúsinu" -# QCHANGESetRest -> -# # QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigSet -# # | QCHANGESubject/þf QCHANGEHvernigSet QCHANGEHvar? -# # | QCHANGEHvar? QCHANGESubject/þf QCHANGEHvernigSet -# # | QCHANGEHvar? QCHANGEHvernigSet QCHANGESubject/þf -# # | QCHANGEHvernigSet QCHANGESubject/þf QCHANGEHvar? -# # | QCHANGEHvernigSet QCHANGEHvar? QCHANGESubject/þf -# "á" QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? - -# QCHANGEChangeRest -> -# # QCHANGESubjectOne/þgf QCHANGEHvar? QCHANGEHvernigChange -# # | QCHANGESubjectOne/þgf QCHANGEHvernigChange QCHANGEHvar? -# # | QCHANGEHvar? QCHANGESubjectOne/þgf QCHANGEHvernigChange -# # | QCHANGEHvar? QCHANGEHvernigChange QCHANGESubjectOne/þgf -# # | QCHANGEHvernigChange QCHANGESubjectOne/þgf QCHANGEHvar? -# # | QCHANGEHvernigChange QCHANGEHvar? QCHANGESubjectOne/þgf - -# QCHANGELetRest -> -# QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigLet -# | QCHANGESubject/þf QCHANGEHvernigLet QCHANGEHvar? -# | QCHANGEHvar? QCHANGESubject/þf QCHANGEHvernigLet -# | QCHANGEHvar? QCHANGEHvernigLet QCHANGESubject/þf -# | QCHANGEHvernigLet QCHANGESubject/þf QCHANGEHvar? -# | QCHANGEHvernigLet QCHANGEHvar? QCHANGESubject/þf -# "vera" QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? - -# QCHANGETurnOnRest -> -# # QCHANGETurnOnLightsRest -# # | QCHANGEAHverju QCHANGEHvar? -# # | QCHANGEHvar? QCHANGEAHverju -# "á" QIoTSpeakerMusicWord/þgf QIoTSpeakerHvar? - -# # QCHANGETurnOnLightsRest -> -# # QCHANGELightSubject/þf QCHANGEHvar? -# # | QCHANGEHvar QCHANGELightSubject/þf? - -# # Would be good to add "slökktu á rauða litnum" functionality -# QCHANGETurnOffRest -> -# # QCHANGETurnOffLightsRest -# "á" QIoTSpeakerMusicWord/þgf QIoTSpeakerHvar? - -# # QCHANGETurnOffLightsRest -> -# # QCHANGELightSubject/þf QCHANGEHvar? -# # | QCHANGEHvar QCHANGELightSubject/þf? - -# # TODO: Make the subject categorization cleaner -# QCHANGEIncreaseOrDecreaseRest -> -# # QCHANGELightSubject/þf QCHANGEHvar? -# # | QCHANGEBrightnessSubject/þf QCHANGEHvar? -# QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? -# | "í" QIoTSpeakerMusicWord/þgf QIoTSpeakerHvar? - -# # QCHANGESubject/fall -> -# # QCHANGESubjectOne/fall -# # | QCHANGESubjectTwo/fall - -# QIoTMusicWord -> -# 'tónlist'/fall - -# # # TODO: Decide whether LightSubject/þgf should be accepted -# # QCHANGESubjectOne/fall -> -# # QCHANGELightSubject/fall -# # | QCHANGEColorSubject/fall -# # | QCHANGEBrightnessSubject/fall -# # | QCHANGESceneSubject/fall - -# # QCHANGESubjectTwo/fall -> -# # QCHANGEGroupNameSubject/fall # á bara að styðja "gerðu eldhúsið rautt", "gerðu eldhúsið rómó" "gerðu eldhúsið bjartara", t.d. - -# QIoTSpeakerHvar -> -# QIoTSpeakerLocationPreposition QIoTSpeakerGroupName/þgf - -# # QCHANGEHvernigMake -> -# # QCHANGEAnnadAndlag # gerðu litinn rauðan í eldhúsinu EÐA gerðu birtuna meiri í eldhúsinu -# # | QCHANGEAdHverju # gerðu litinn að rauðum í eldhúsinu -# # | QCHANGEThannigAd - -# # QCHANGEHvernigSet -> -# # QCHANGEAHvad -# # | QCHANGEThannigAd - -# # QCHANGEHvernigChange -> -# # QCHANGEIHvad -# # | QCHANGEThannigAd - -# # QCHANGEHvernigLet -> -# # QCHANGEBecome QCHANGESomethingOrSomehow -# # | QCHANGEBe QCHANGESomehow - -# # QCHANGEThannigAd -> -# # "þannig" "að"? pfn_nf QCHANGEBeOrBecomeSubjunctive QCHANGEAnnadAndlag - -# # I think these verbs only appear in these forms. -# # In which case these terminals should be deleted and a direct reference should be made in the relevant non-terminals. -# # QCHANGEBe -> -# # "vera" - -# # QCHANGEBecome -> -# # "verða" - -# # QCHANGEBeOrBecomeSubjunctive -> -# # "verði" -# # | "sé" - -# # QCHANGELightSubject/fall -> -# # QCHANGELight/fall - -# # QCHANGEColorSubject/fall -> -# # QCHANGEColorWord/fall QCHANGELight/ef? -# # | QCHANGEColorWord/fall "á" QCHANGELight/þgf - -# # QCHANGEBrightnessSubject/fall -> -# # QCHANGEBrightnessWord/fall QCHANGELight/ef? -# # | QCHANGEBrightnessWord/fall "á" QCHANGELight/þgf - -# # QCHANGESceneSubject/fall -> -# # QCHANGESceneWord/fall - -# # QCHANGEGroupNameSubject/fall -> -# # QCHANGEGroupName/fall - -# QIoTSpeakerLocationPreposition -> -# QIoTSpeakerLocationPrepositionFirstPart? QIoTSpeakerLocationPrepositionSecondPart - -# # The latter proverbs are grammatically incorrect, but common errors, both in speech and transcription. -# # The list provided is taken from StefnuAtv in Greynir.grammar. That includes "aftur:ao", which is not applicable here. -# QIoTSpeakerLocationPrepositionFirstPart -> -# StaðarAtv -# | "fram:ao" -# | "inn:ao" -# | "niður:ao" -# | "upp:ao" -# | "út:ao" - -# QIoTSpeakerLocationPrepositionSecondPart -> -# "á" | "í" - -# QIoTSpeakerGroupName/fall -> +# def QIoTSpeakerIncreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: +# result.action = "increase_volume" +# if "hue_obj" not in result: +# result["hue_obj"] = {"on": True, "bri_inc": 64} +# else: +# result["hue_obj"]["bri_inc"] = 64 +# result["hue_obj"]["on"] = True + + +def help_text(lemma: str) -> str: + """Help text to return when query.py is unable to parse a query but + one of the above lemmas is found in it""" + return "Ég skil þig ef þú segir til dæmis: {0}.".format( + random.choice( + ("Hækkaðu í tónlistinni", "Kveiktu á tónlist", "Láttu vera tónlist") + ) + ) + + +# This module wants to handle parse trees for queries +HANDLE_TREE = True + +# The grammar nonterminals this module wants to handle +QUERY_NONTERMINALS = {"QIoTSpeaker"} + +# The context-free grammar for the queries recognized by this plug-in module +# GRAMMAR = read_grammar_file("iot_hue") + +GRAMMAR = f""" + +/þgf = þgf +/ef = ef + +Query → + QIoTSpeaker '?'? + +QIoTSpeaker → + QIoTSpeakerQuery + +# QIoTSpeakerQuery -> +# QIoTSpeakerMakeVerb QIoTSpeakerMakeRest +# | QIoTSpeakerSetVerb QIoTSpeakerSetRest +# | QIoTSpeakerChangeVerb QIoTSpeakerChangeRest +# | QIoTSpeakerLetVerb QIoTSpeakerLetRest +# | QIoTSpeakerTurnOnVerb QIoTSpeakerTurnOnRest +# | QIoTSpeakerTurnOffVerb QIoTSpeakerTurnOffRest +# | QIoTSpeakerIncreaseOrDecreaseVerb QIoTSpeakerIncreaseOrDecreaseRest + +QIoTSpeakerMakeVerb -> + 'gera:so'_bh + +QIoTSpeakerSetVerb -> + 'setja:so'_bh + | 'stilla:so'_bh + +QIoTSpeakerChangeVerb -> + 'breyta:so'_bh + +QIoTSpeakerLetVerb -> + 'láta:so'_bh + +QIoTSpeakerTurnOnVerb -> + 'kveikja:so'_bh + +QIoTSpeakerTurnOffVerb -> + 'slökkva:so'_bh + +QIoTSpeakerIncreaseOrDecreaseVerb -> + QIoTSpeakerIncreaseVerb + | QIoTSpeakerDecreaseVerb + +QIoTSpeakerIncreaseVerb -> + 'hækka:so'_bh + | 'auka:so'_bh + +QIoTSpeakerDecreaseVerb -> + 'lækka:so'_bh + | 'minnka:so'_bh + +# QCHANGEMakeRest -> + # QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigMake + # | QCHANGESubject/þf QCHANGEHvernigMake QCHANGEHvar? + # | QCHANGEHvar? QCHANGESubject/þf QCHANGEHvernigMake + # | QCHANGEHvar? QCHANGEHvernigMake QCHANGESubject/þf + # | QCHANGEHvernigMake QCHANGESubject/þf QCHANGEHvar? + # | QCHANGEHvernigMake QCHANGEHvar? QCHANGESubject/þf + QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? + +# TODO: Add support for "stilltu rauðan lit á ljósið í eldhúsinu" +QCHANGESetRest -> + # QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigSet + # | QCHANGESubject/þf QCHANGEHvernigSet QCHANGEHvar? + # | QCHANGEHvar? QCHANGESubject/þf QCHANGEHvernigSet + # | QCHANGEHvar? QCHANGEHvernigSet QCHANGESubject/þf + # | QCHANGEHvernigSet QCHANGESubject/þf QCHANGEHvar? + # | QCHANGEHvernigSet QCHANGEHvar? QCHANGESubject/þf + "á" QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? + +QCHANGEChangeRest -> + # QCHANGESubjectOne/þgf QCHANGEHvar? QCHANGEHvernigChange + # | QCHANGESubjectOne/þgf QCHANGEHvernigChange QCHANGEHvar? + # | QCHANGEHvar? QCHANGESubjectOne/þgf QCHANGEHvernigChange + # | QCHANGEHvar? QCHANGEHvernigChange QCHANGESubjectOne/þgf + # | QCHANGEHvernigChange QCHANGESubjectOne/þgf QCHANGEHvar? + # | QCHANGEHvernigChange QCHANGEHvar? QCHANGESubjectOne/þgf + +QCHANGELetRest -> + QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigLet + | QCHANGESubject/þf QCHANGEHvernigLet QCHANGEHvar? + | QCHANGEHvar? QCHANGESubject/þf QCHANGEHvernigLet + | QCHANGEHvar? QCHANGEHvernigLet QCHANGESubject/þf + | QCHANGEHvernigLet QCHANGESubject/þf QCHANGEHvar? + | QCHANGEHvernigLet QCHANGEHvar? QCHANGESubject/þf + "vera" QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? + +QCHANGETurnOnRest -> + # QCHANGETurnOnLightsRest + # | QCHANGEAHverju QCHANGEHvar? + # | QCHANGEHvar? QCHANGEAHverju + "á" QIoTSpeakerMusicWord/þgf QIoTSpeakerHvar? + +# QCHANGETurnOnLightsRest -> +# QCHANGELightSubject/þf QCHANGEHvar? +# | QCHANGEHvar QCHANGELightSubject/þf? + +# Would be good to add "slökktu á rauða litnum" functionality +QCHANGETurnOffRest -> + # QCHANGETurnOffLightsRest + "á" QIoTSpeakerMusicWord/þgf QIoTSpeakerHvar? + +# QCHANGETurnOffLightsRest -> +# QCHANGELightSubject/þf QCHANGEHvar? +# | QCHANGEHvar QCHANGELightSubject/þf? + +# TODO: Make the subject categorization cleaner +QCHANGEIncreaseOrDecreaseRest -> + # QCHANGELightSubject/þf QCHANGEHvar? + # | QCHANGEBrightnessSubject/þf QCHANGEHvar? + QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? + | "í" QIoTSpeakerMusicWord/þgf QIoTSpeakerHvar? + +# QCHANGESubject/fall -> +# QCHANGESubjectOne/fall +# | QCHANGESubjectTwo/fall + +QIoTMusicWord -> + 'tónlist'/fall + +# # TODO: Decide whether LightSubject/þgf should be accepted +# QCHANGESubjectOne/fall -> +# QCHANGELightSubject/fall +# | QCHANGEColorSubject/fall +# | QCHANGEBrightnessSubject/fall +# | QCHANGESceneSubject/fall + +# QCHANGESubjectTwo/fall -> +# QCHANGEGroupNameSubject/fall # á bara að styðja "gerðu eldhúsið rautt", "gerðu eldhúsið rómó" "gerðu eldhúsið bjartara", t.d. + +QIoTSpeakerHvar -> + QIoTSpeakerLocationPreposition QIoTSpeakerGroupName/þgf + +# QCHANGEHvernigMake -> +# QCHANGEAnnadAndlag # gerðu litinn rauðan í eldhúsinu EÐA gerðu birtuna meiri í eldhúsinu +# | QCHANGEAdHverju # gerðu litinn að rauðum í eldhúsinu +# | QCHANGEThannigAd + +# QCHANGEHvernigSet -> +# QCHANGEAHvad +# | QCHANGEThannigAd + +# QCHANGEHvernigChange -> +# QCHANGEIHvad +# | QCHANGEThannigAd + +# QCHANGEHvernigLet -> +# QCHANGEBecome QCHANGESomethingOrSomehow +# | QCHANGEBe QCHANGESomehow + +# QCHANGEThannigAd -> +# "þannig" "að"? pfn_nf QCHANGEBeOrBecomeSubjunctive QCHANGEAnnadAndlag + +# I think these verbs only appear in these forms. +# In which case these terminals should be deleted and a direct reference should be made in the relevant non-terminals. +# QCHANGEBe -> +# "vera" + +# QCHANGEBecome -> +# "verða" + +# QCHANGEBeOrBecomeSubjunctive -> +# "verði" +# | "sé" + +# QCHANGELightSubject/fall -> +# QCHANGELight/fall + +# QCHANGEColorSubject/fall -> +# QCHANGEColorWord/fall QCHANGELight/ef? +# | QCHANGEColorWord/fall "á" QCHANGELight/þgf + +# QCHANGEBrightnessSubject/fall -> +# QCHANGEBrightnessWord/fall QCHANGELight/ef? +# | QCHANGEBrightnessWord/fall "á" QCHANGELight/þgf + +# QCHANGESceneSubject/fall -> +# QCHANGESceneWord/fall + +# QCHANGEGroupNameSubject/fall -> +# QCHANGEGroupName/fall + +QIoTSpeakerLocationPreposition -> + QIoTSpeakerLocationPrepositionFirstPart? QIoTSpeakerLocationPrepositionSecondPart + +# The latter proverbs are grammatically incorrect, but common errors, both in speech and transcription. +# The list provided is taken from StefnuAtv in Greynir.grammar. That includes "aftur:ao", which is not applicable here. +QIoTSpeakerLocationPrepositionFirstPart -> + StaðarAtv + | "fram:ao" + | "inn:ao" + | "niður:ao" + | "upp:ao" + | "út:ao" + +QIoTSpeakerLocationPrepositionSecondPart -> + "á" | "í" + +QIoTSpeakerGroupName/fall -> + no/fall + +# QCHANGELightName/fall -> # no/fall -# # QCHANGELightName/fall -> -# # no/fall - - -# # QCHANGESceneName -> -# # no -# # | lo - -# # QCHANGEAnnadAndlag -> -# # QCHANGENewSetting/nf -# # | QCHANGESpyrjaHuldu/nf - -# # QCHANGEAdHverju -> -# # "að" QCHANGENewSetting/þgf - -# # QCHANGEAHvad -> -# # "á" QCHANGENewSetting/þf - -# # QCHANGEIHvad -> -# # "í" QCHANGENewSetting/þf - -# # QCHANGEAHverju -> -# # "á" QCHANGELight/þgf -# # | "á" QCHANGENewSetting/þgf - -# # QCHANGESomethingOrSomehow -> -# # QCHANGEAnnadAndlag -# # | QCHANGEAdHverju - -# # QCHANGESomehow -> -# # QCHANGEAnnadAndlag -# # | QCHANGEThannigAd - -# # QCHANGELight/fall -> -# # QCHANGELightName/fall -# # | QCHANGELightWord/fall - -# # # Should 'birta' be included -# # QCHANGELightWord/fall -> -# # 'ljós'/fall -# # | 'lýsing'/fall -# # | 'birta'/fall -# # | 'Birta'/fall - -# # QCHANGEColorWord/fall -> -# # 'litur'/fall -# # | 'litblær'/fall -# # | 'blær'/fall - -# # QCHANGEBrightnessWords/fall -> -# # 'bjartur'/fall -# # | QCHANGEBrightnessWord/fall - -# # QCHANGEBrightnessWord/fall -> -# # 'birta'/fall -# # | 'Birta'/fall -# # | 'birtustig'/fall - -# # QCHANGESceneWord/fall -> -# # 'sena'/fall -# # | 'stemning'/fall -# # | 'stemming'/fall -# # | 'stemmning'/fall - -# # # Need to ask Hulda how this works. -# # QCHANGESpyrjaHuldu/fall -> -# # # QCHANGEHuldaColor/fall -# # QCHANGEHuldaBrightness/fall -# # # | QCHANGEHuldaScene/fall - -# # # Do I need a "new light state" non-terminal? -# # QCHANGENewSetting/fall -> -# # QCHANGENewColor/fall -# # | QCHANGENewBrightness/fall -# # | QCHANGENewScene/fall - -# # # Missing "meira dimmt" -# # QCHANGEHuldaBrightness/fall -> -# # QCHANGEMoreBrighterOrHigher/fall QCHANGEBrightnessWords/fall? -# # | QCHANGELessDarkerOrLower/fall QCHANGEBrightnessWords/fall? - -# # #Unsure about whether to include /fall after QCHANGEColorName -# # QCHANGENewColor/fall -> -# # QCHANGEColorWord/fall QCHANGEColorName -# # | QCHANGEColorName QCHANGEColorWord/fall? - -# # QCHANGENewBrightness/fall -> -# # 'sá'/fall? QCHANGEBrightestOrDarkest/fall -# # | QCHANGEBrightestOrDarkest/fall QCHANGEBrightnessOrSettingWord/fall - -# # QCHANGENewScene/fall -> -# # QCHANGESceneWord/fall QCHANGESceneName -# # | QCHANGESceneName QCHANGESceneWord/fall? - -# # QCHANGEMoreBrighterOrHigher/fall -> -# # 'mikill:lo'_mst/fall -# # | 'bjartur:lo'_mst/fall -# # | 'ljós:lo'_mst/fall -# # | 'hár:lo'_mst/fall - -# # QCHANGELessDarkerOrLower/fall -> -# # 'lítill:lo'_mst/fall -# # | 'dökkur:lo'_mst/fall -# # | 'dimmur:lo'_mst/fall -# # | 'lágur:lo'_mst/fall - -# # QCHANGEBrightestOrDarkest/fall -> -# # QCHANGEBrightest/fall -# # | QCHANGEDarkest/fall - -# # QCHANGEBrightest/fall -> -# # 'bjartur:lo'_evb -# # | 'bjartur:lo'_esb -# # | 'ljós:lo'_evb -# # | 'ljós:lo'_esb - -# # QCHANGEDarkest/fall -> -# # 'dimmur:lo'_evb -# # | 'dimmur:lo'_esb -# # | 'dökkur:lo'_evb -# # | 'dökkur:lo'_esb - -# # QCHANGEBrightnessOrSettingWord/fall -> -# # QCHANGEBrightnessWord/fall -# # | QCHANGESettingWord/fall - -# # QCHANGESettingWord/fall -> -# # 'stilling'/fall - -# """ +# QCHANGESceneName -> +# no +# | lo + +# QCHANGEAnnadAndlag -> +# QCHANGENewSetting/nf +# | QCHANGESpyrjaHuldu/nf + +# QCHANGEAdHverju -> +# "að" QCHANGENewSetting/þgf + +# QCHANGEAHvad -> +# "á" QCHANGENewSetting/þf + +# QCHANGEIHvad -> +# "í" QCHANGENewSetting/þf + +# QCHANGEAHverju -> +# "á" QCHANGELight/þgf +# | "á" QCHANGENewSetting/þgf + +# QCHANGESomethingOrSomehow -> +# QCHANGEAnnadAndlag +# | QCHANGEAdHverju + +# QCHANGESomehow -> +# QCHANGEAnnadAndlag +# | QCHANGEThannigAd + +# QCHANGELight/fall -> +# QCHANGELightName/fall +# | QCHANGELightWord/fall -# def QIoTSpeakerIncreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: -# result.action = "increase_volume" +# # Should 'birta' be included +# QCHANGELightWord/fall -> +# 'ljós'/fall +# | 'lýsing'/fall +# | 'birta'/fall +# | 'Birta'/fall + +# QCHANGEColorWord/fall -> +# 'litur'/fall +# | 'litblær'/fall +# | 'blær'/fall + +# QCHANGEBrightnessWords/fall -> +# 'bjartur'/fall +# | QCHANGEBrightnessWord/fall + +# QCHANGEBrightnessWord/fall -> +# 'birta'/fall +# | 'Birta'/fall +# | 'birtustig'/fall + +# QCHANGESceneWord/fall -> +# 'sena'/fall +# | 'stemning'/fall +# | 'stemming'/fall +# | 'stemmning'/fall + +# # Need to ask Hulda how this works. +# QCHANGESpyrjaHuldu/fall -> +# # QCHANGEHuldaColor/fall +# QCHANGEHuldaBrightness/fall +# # | QCHANGEHuldaScene/fall + +# # Do I need a "new light state" non-terminal? +# QCHANGENewSetting/fall -> +# QCHANGENewColor/fall +# | QCHANGENewBrightness/fall +# | QCHANGENewScene/fall +# # Missing "meira dimmt" +# QCHANGEHuldaBrightness/fall -> +# QCHANGEMoreBrighterOrHigher/fall QCHANGEBrightnessWords/fall? +# | QCHANGELessDarkerOrLower/fall QCHANGEBrightnessWords/fall? -# def QIoTSpeakerDecreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: -# result.action = "decrease_volume" - - -# def QIoTSpeakerGroupName(node: Node, params: QueryStateDict, result: Result) -> None: -# result["group_name"] = result._indefinite - - -# def sentence(state: QueryStateDict, result: Result) -> None: -# """Called when sentence processing is complete""" -# q: Query = state["query"] - -# q.set_qtype(result.get["qtype"]) - -# smartdevice_type = "smartSpeaker" - -# # Fetch relevant data from the device_data table to perform an action on the lights -# device_data = cast(Optional[DeviceData], q.client_data(smartdevice_type)) - -# if device_data is not None and smartdevice_type in device_data: -# dev = device_data[smartdevice_type] -# assert dev is not None -# selected_light = dev.get("selected_light") -# hue_credentials = dev.get("philips_hue") -# bridge_ip = hue_credentials.get("ipAddress") -# username = hue_credentials.get("username") - -# if not device_data or not hue_credentials: -# answer = "Það vantar að tengja Philips Hub-inn." -# q.set_answer(*gen_answer(answer)) -# return - -# # Successfully matched a query type -# print("bridge_ip: ", bridge_ip) -# print("username: ", username) -# print("selected light :", selected_light) -# print("hue credentials :", hue_credentials) - -# try: -# # kalla í javascripts stuff -# light_or_group_name = result.get("light_name", result.get("group_name", "")) -# color_name = result.get("color_name", "") -# print("GROUP NAME:", light_or_group_name) -# print("COLOR NAME:", color_name) -# print(result.hue_obj) -# q.set_answer( -# *gen_answer( -# "ég var að kveikja ljósin! " -# # + group_name -# # + " " -# # + color_name -# # + " " -# # + result.action -# # + " " -# # + str(result.hue_obj.get("hue", "enginn litur")) -# ) -# ) -# js = ( -# read_jsfile("IoT_Embla/fuse.js") -# + f"var BRIDGE_IP = '{bridge_ip}';var USERNAME = '{username}';" -# + read_jsfile("IoT_Embla/Philips_Hue/fuse_search.js") -# + read_jsfile("IoT_Embla/Philips_Hue/lights.js") -# + read_jsfile("IoT_Embla/Philips_Hue/set_lights.js") -# ) -# js += f"setLights('{light_or_group_name}', '{json.dumps(result.hue_obj)}');" -# q.set_command(js) -# except Exception as e: -# logging.warning("Exception while processing random query: {0}".format(e)) -# q.set_error("E_EXCEPTION: {0}".format(e)) -# raise - -# # f"var BRIDGE_IP = '192.168.1.68';var USERNAME = 'p3obluiXT13IbHMpp4X63ZvZnpNRdbqqMt723gy2';" +# #Unsure about whether to include /fall after QCHANGEColorName +# QCHANGENewColor/fall -> +# QCHANGEColorWord/fall QCHANGEColorName +# | QCHANGEColorName QCHANGEColorWord/fall? + +# QCHANGENewBrightness/fall -> +# 'sá'/fall? QCHANGEBrightestOrDarkest/fall +# | QCHANGEBrightestOrDarkest/fall QCHANGEBrightnessOrSettingWord/fall + +# QCHANGENewScene/fall -> +# QCHANGESceneWord/fall QCHANGESceneName +# | QCHANGESceneName QCHANGESceneWord/fall? + +# QCHANGEMoreBrighterOrHigher/fall -> +# 'mikill:lo'_mst/fall +# | 'bjartur:lo'_mst/fall +# | 'ljós:lo'_mst/fall +# | 'hár:lo'_mst/fall + +# QCHANGELessDarkerOrLower/fall -> +# 'lítill:lo'_mst/fall +# | 'dökkur:lo'_mst/fall +# | 'dimmur:lo'_mst/fall +# | 'lágur:lo'_mst/fall + +# QCHANGEBrightestOrDarkest/fall -> +# QCHANGEBrightest/fall +# | QCHANGEDarkest/fall + +# QCHANGEBrightest/fall -> +# 'bjartur:lo'_evb +# | 'bjartur:lo'_esb +# | 'ljós:lo'_evb +# | 'ljós:lo'_esb + +# QCHANGEDarkest/fall -> +# 'dimmur:lo'_evb +# | 'dimmur:lo'_esb +# | 'dökkur:lo'_evb +# | 'dökkur:lo'_esb + +# QCHANGEBrightnessOrSettingWord/fall -> +# QCHANGEBrightnessWord/fall +# | QCHANGESettingWord/fall + +# QCHANGESettingWord/fall -> +# 'stilling'/fall + +""" + + +def QIoTSpeakerIncreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: + result.action = "increase_volume" + + +def QIoTSpeakerDecreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: + result.action = "decrease_volume" + + +def QIoTSpeakerGroupName(node: Node, params: QueryStateDict, result: Result) -> None: + result["group_name"] = result._indefinite + + +def sentence(state: QueryStateDict, result: Result) -> None: + """Called when sentence processing is complete""" + q: Query = state["query"] + + q.set_qtype(result.get["qtype"]) + + smartdevice_type = "smartSpeaker" + + # Fetch relevant data from the device_data table to perform an action on the lights + device_data = cast(Optional[DeviceData], q.client_data(smartdevice_type)) + + if device_data is not None and smartdevice_type in device_data: + dev = device_data[smartdevice_type] + assert dev is not None + selected_light = dev.get("selected_light") + hue_credentials = dev.get("philips_hue") + bridge_ip = hue_credentials.get("ipAddress") + username = hue_credentials.get("username") + + if not device_data or not hue_credentials: + answer = "Það vantar að tengja Philips Hub-inn." + q.set_answer(*gen_answer(answer)) + return + + # Successfully matched a query type + print("bridge_ip: ", bridge_ip) + print("username: ", username) + print("selected light :", selected_light) + print("hue credentials :", hue_credentials) + + try: + # kalla í javascripts stuff + light_or_group_name = result.get("light_name", result.get("group_name", "")) + color_name = result.get("color_name", "") + print("GROUP NAME:", light_or_group_name) + print("COLOR NAME:", color_name) + print(result.hue_obj) + q.set_answer( + *gen_answer( + "ég var að kveikja ljósin! " + # + group_name + # + " " + # + color_name + # + " " + # + result.action + # + " " + # + str(result.hue_obj.get("hue", "enginn litur")) + ) + ) + js = ( + read_jsfile("IoT_Embla/fuse.js") + + f"var BRIDGE_IP = '{bridge_ip}';var USERNAME = '{username}';" + + read_jsfile("IoT_Embla/Philips_Hue/fuse_search.js") + + read_jsfile("IoT_Embla/Philips_Hue/lights.js") + + read_jsfile("IoT_Embla/Philips_Hue/set_lights.js") + ) + js += f"setLights('{light_or_group_name}', '{json.dumps(result.hue_obj)}');" + q.set_command(js) + except Exception as e: + logging.warning("Exception while processing random query: {0}".format(e)) + q.set_error("E_EXCEPTION: {0}".format(e)) + raise + + # f"var BRIDGE_IP = '192.168.1.68';var USERNAME = 'p3obluiXT13IbHMpp4X63ZvZnpNRdbqqMt723gy2';" From 910c172e0c0ab03ab63a4232cc27523633765f43 Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Mon, 27 Jun 2022 16:58:21 +0000 Subject: [PATCH 107/371] fix merge --- queries/iot_connect.py | 10 +- queries/iot_hue.py | 12 +- queries/iot_speakers.py | 940 ++++++++++++------------ queries/js/IoT_Embla/Philips_Hue/hub.js | 9 +- routes/api.py | 2 +- 5 files changed, 486 insertions(+), 487 deletions(-) diff --git a/queries/iot_connect.py b/queries/iot_connect.py index 80433383..6c8ddaaf 100644 --- a/queries/iot_connect.py +++ b/queries/iot_connect.py @@ -186,8 +186,8 @@ def sentence(state: QueryStateDict, result: Result) -> None: return response_json = response.json() access_token, refresh_token = ( - response_json["access_token"], - response_json["refresh_token"], + response_json.get("access_token"), + response_json.get("refresh_token"), ) data_dict = create_sonos_data_dict(access_token, q) cred_dict = create_sonos_cred_dict(access_token, refresh_token, q) @@ -217,8 +217,8 @@ def create_sonos_data_dict(access_token, q): groups_object = get_groups( households["households"][i]["id"], access_token ).json() - groups_raw = groups_object["groups"] - players_raw = groups_object["players"] + groups_raw = groups_object.get("groups") + players_raw = groups_object.get("players") groups_list += create_grouplist_for_db(groups_raw) players_list += create_playerlist_for_db(players_raw) @@ -241,7 +241,7 @@ def create_sonos_cred_dict(access_token, refresh_token, q): def store_sonos_data_and_credentials(data_dict, cred_dict, q): sonos_dict = {} sonos_dict["sonos"] = {"credentials": cred_dict, "data": data_dict} - q.update_client_data("IoT_Speakers", sonos_dict) + q.update_client_data("iot_speakers", sonos_dict) def create_grouplist_for_db(groups): diff --git a/queries/iot_hue.py b/queries/iot_hue.py index c9090704..507eed34 100755 --- a/queries/iot_hue.py +++ b/queries/iot_hue.py @@ -591,7 +591,7 @@ def sentence(state: QueryStateDict, result: Result) -> None: q.set_qtype(result.qtype) - smartdevice_type = "smartlights" + smartdevice_type = "iot_lights" client_id = str(q.client_id) print("client_id:", client_id) @@ -604,12 +604,12 @@ def sentence(state: QueryStateDict, result: Result) -> None: print("selected light:", selected_light) hue_credentials: Optional[Dict[str, str]] = None - if device_data is not None and smartdevice_type in device_data: - dev = device_data[smartdevice_type] + if device_data is not None: + dev = device_data assert dev is not None - selected_light = dev.get("selected_light") - hue_credentials = dev.get("philips_hue") - bridge_ip = hue_credentials.get("ipAddress") + light = dev.get("philips_hue") + hue_credentials = light.get("credentials") + bridge_ip = hue_credentials.get("ip_address") username = hue_credentials.get("username") if not device_data or not hue_credentials: diff --git a/queries/iot_speakers.py b/queries/iot_speakers.py index b4a31d09..e8902ff2 100644 --- a/queries/iot_speakers.py +++ b/queries/iot_speakers.py @@ -1,496 +1,496 @@ -""" +# """ - Greynir: Natural language processing for Icelandic +# Greynir: Natural language processing for Icelandic - Randomness query response module +# Randomness query response module - Copyright (C) 2022 Miðeind ehf. +# Copyright (C) 2022 Miðeind ehf. - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. - You should have received a copy of the GNU General Public License - along with this program. If not, see http://www.gnu.org/licenses/. +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. - This query module handles queries related to the generation - of random numbers, e.g. "Kastaðu tengingi", "Nefndu tölu milli 5 og 10", etc. +# This query module handles queries related to the generation +# of random numbers, e.g. "Kastaðu tengingi", "Nefndu tölu milli 5 og 10", etc. -""" +# """ -# TODO: add "láttu", "hafðu", "litaðu", "kveiktu" functionality. -# TODO: make the objects of sentences more modular, so that the same structure doesn't need to be written for each action -# TODO: ditto the previous comment. make the initial non-terminals general and go into specifics at the terminal level instead. -# TODO: substituion klósett, baðherbergi hugmyndÆ senda lista i javascript og profa i röð -# TODO: Embla stores old javascript code cached which has caused errors -# TODO: Cut down javascript sent to Embla -# TODO: Two specified groups or lights. -# TODO: No specified location -# TODO: Fix scene issues +# # TODO: add "láttu", "hafðu", "litaðu", "kveiktu" functionality. +# # TODO: make the objects of sentences more modular, so that the same structure doesn't need to be written for each action +# # TODO: ditto the previous comment. make the initial non-terminals general and go into specifics at the terminal level instead. +# # TODO: substituion klósett, baðherbergi hugmyndÆ senda lista i javascript og profa i röð +# # TODO: Embla stores old javascript code cached which has caused errors +# # TODO: Cut down javascript sent to Embla +# # TODO: Two specified groups or lights. +# # TODO: No specified location +# # TODO: Fix scene issues -from typing import Dict, Mapping, Optional, cast -from typing_extensions import TypedDict +# from typing import Dict, Mapping, Optional, cast +# from typing_extensions import TypedDict -import logging -import random -import json -import flask +# import logging +# import random +# import json +# import flask -from query import Query, QueryStateDict, AnswerTuple -from queries import gen_answer, read_jsfile, read_grammar_file -from tree import Result, Node +# from query import Query, QueryStateDict, AnswerTuple +# from queries import gen_answer, read_jsfile, read_grammar_file +# from tree import Result, Node -_IoT_QTYPE = "IoT" +# _IoT_QTYPE = "IoT" + +# TOPIC_LEMMAS = [ +# "tónlist", +# ] + +# # def QIoTSpeakerIncreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: +# # result.action = "increase_volume" +# # if "hue_obj" not in result: +# # result["hue_obj"] = {"on": True, "bri_inc": 64} +# # else: +# # result["hue_obj"]["bri_inc"] = 64 +# # result["hue_obj"]["on"] = True + + +# def help_text(lemma: str) -> str: +# """Help text to return when query.py is unable to parse a query but +# one of the above lemmas is found in it""" +# return "Ég skil þig ef þú segir til dæmis: {0}.".format( +# random.choice( +# ("Hækkaðu í tónlistinni", "Kveiktu á tónlist", "Láttu vera tónlist") +# ) +# ) + + +# # This module wants to handle parse trees for queries +# HANDLE_TREE = True + +# # The grammar nonterminals this module wants to handle +# QUERY_NONTERMINALS = {"QIoTSpeaker"} + +# # The context-free grammar for the queries recognized by this plug-in module +# # GRAMMAR = read_grammar_file("iot_hue") + +# GRAMMAR = f""" + +# /þgf = þgf +# /ef = ef + +# Query → +# QIoTSpeaker '?'? + +# QIoTSpeaker → +# QIoTSpeakerQuery + +# # QIoTSpeakerQuery -> +# # QIoTSpeakerMakeVerb QIoTSpeakerMakeRest +# # | QIoTSpeakerSetVerb QIoTSpeakerSetRest +# # | QIoTSpeakerChangeVerb QIoTSpeakerChangeRest +# # | QIoTSpeakerLetVerb QIoTSpeakerLetRest +# # | QIoTSpeakerTurnOnVerb QIoTSpeakerTurnOnRest +# # | QIoTSpeakerTurnOffVerb QIoTSpeakerTurnOffRest +# # | QIoTSpeakerIncreaseOrDecreaseVerb QIoTSpeakerIncreaseOrDecreaseRest + +# QIoTSpeakerMakeVerb -> +# 'gera:so'_bh + +# QIoTSpeakerSetVerb -> +# 'setja:so'_bh +# | 'stilla:so'_bh + +# QIoTSpeakerChangeVerb -> +# 'breyta:so'_bh + +# QIoTSpeakerLetVerb -> +# 'láta:so'_bh + +# QIoTSpeakerTurnOnVerb -> +# 'kveikja:so'_bh + +# QIoTSpeakerTurnOffVerb -> +# 'slökkva:so'_bh + +# QIoTSpeakerIncreaseOrDecreaseVerb -> +# QIoTSpeakerIncreaseVerb +# | QIoTSpeakerDecreaseVerb + +# QIoTSpeakerIncreaseVerb -> +# 'hækka:so'_bh +# | 'auka:so'_bh + +# QIoTSpeakerDecreaseVerb -> +# 'lækka:so'_bh +# | 'minnka:so'_bh + +# # QCHANGEMakeRest -> +# # QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigMake +# # | QCHANGESubject/þf QCHANGEHvernigMake QCHANGEHvar? +# # | QCHANGEHvar? QCHANGESubject/þf QCHANGEHvernigMake +# # | QCHANGEHvar? QCHANGEHvernigMake QCHANGESubject/þf +# # | QCHANGEHvernigMake QCHANGESubject/þf QCHANGEHvar? +# # | QCHANGEHvernigMake QCHANGEHvar? QCHANGESubject/þf +# QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? + +# # TODO: Add support for "stilltu rauðan lit á ljósið í eldhúsinu" +# QCHANGESetRest -> +# # QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigSet +# # | QCHANGESubject/þf QCHANGEHvernigSet QCHANGEHvar? +# # | QCHANGEHvar? QCHANGESubject/þf QCHANGEHvernigSet +# # | QCHANGEHvar? QCHANGEHvernigSet QCHANGESubject/þf +# # | QCHANGEHvernigSet QCHANGESubject/þf QCHANGEHvar? +# # | QCHANGEHvernigSet QCHANGEHvar? QCHANGESubject/þf +# "á" QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? + +# QCHANGEChangeRest -> +# # QCHANGESubjectOne/þgf QCHANGEHvar? QCHANGEHvernigChange +# # | QCHANGESubjectOne/þgf QCHANGEHvernigChange QCHANGEHvar? +# # | QCHANGEHvar? QCHANGESubjectOne/þgf QCHANGEHvernigChange +# # | QCHANGEHvar? QCHANGEHvernigChange QCHANGESubjectOne/þgf +# # | QCHANGEHvernigChange QCHANGESubjectOne/þgf QCHANGEHvar? +# # | QCHANGEHvernigChange QCHANGEHvar? QCHANGESubjectOne/þgf + +# QCHANGELetRest -> +# QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigLet +# | QCHANGESubject/þf QCHANGEHvernigLet QCHANGEHvar? +# | QCHANGEHvar? QCHANGESubject/þf QCHANGEHvernigLet +# | QCHANGEHvar? QCHANGEHvernigLet QCHANGESubject/þf +# | QCHANGEHvernigLet QCHANGESubject/þf QCHANGEHvar? +# | QCHANGEHvernigLet QCHANGEHvar? QCHANGESubject/þf +# "vera" QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? + +# QCHANGETurnOnRest -> +# # QCHANGETurnOnLightsRest +# # | QCHANGEAHverju QCHANGEHvar? +# # | QCHANGEHvar? QCHANGEAHverju +# "á" QIoTSpeakerMusicWord/þgf QIoTSpeakerHvar? + +# # QCHANGETurnOnLightsRest -> +# # QCHANGELightSubject/þf QCHANGEHvar? +# # | QCHANGEHvar QCHANGELightSubject/þf? + +# # Would be good to add "slökktu á rauða litnum" functionality +# QCHANGETurnOffRest -> +# # QCHANGETurnOffLightsRest +# "á" QIoTSpeakerMusicWord/þgf QIoTSpeakerHvar? + +# # QCHANGETurnOffLightsRest -> +# # QCHANGELightSubject/þf QCHANGEHvar? +# # | QCHANGEHvar QCHANGELightSubject/þf? + +# # TODO: Make the subject categorization cleaner +# QCHANGEIncreaseOrDecreaseRest -> +# # QCHANGELightSubject/þf QCHANGEHvar? +# # | QCHANGEBrightnessSubject/þf QCHANGEHvar? +# QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? +# | "í" QIoTSpeakerMusicWord/þgf QIoTSpeakerHvar? + +# # QCHANGESubject/fall -> +# # QCHANGESubjectOne/fall +# # | QCHANGESubjectTwo/fall + +# QIoTMusicWord -> +# 'tónlist'/fall + +# # # TODO: Decide whether LightSubject/þgf should be accepted +# # QCHANGESubjectOne/fall -> +# # QCHANGELightSubject/fall +# # | QCHANGEColorSubject/fall +# # | QCHANGEBrightnessSubject/fall +# # | QCHANGESceneSubject/fall + +# # QCHANGESubjectTwo/fall -> +# # QCHANGEGroupNameSubject/fall # á bara að styðja "gerðu eldhúsið rautt", "gerðu eldhúsið rómó" "gerðu eldhúsið bjartara", t.d. + +# QIoTSpeakerHvar -> +# QIoTSpeakerLocationPreposition QIoTSpeakerGroupName/þgf + +# # QCHANGEHvernigMake -> +# # QCHANGEAnnadAndlag # gerðu litinn rauðan í eldhúsinu EÐA gerðu birtuna meiri í eldhúsinu +# # | QCHANGEAdHverju # gerðu litinn að rauðum í eldhúsinu +# # | QCHANGEThannigAd + +# # QCHANGEHvernigSet -> +# # QCHANGEAHvad +# # | QCHANGEThannigAd + +# # QCHANGEHvernigChange -> +# # QCHANGEIHvad +# # | QCHANGEThannigAd + +# # QCHANGEHvernigLet -> +# # QCHANGEBecome QCHANGESomethingOrSomehow +# # | QCHANGEBe QCHANGESomehow + +# # QCHANGEThannigAd -> +# # "þannig" "að"? pfn_nf QCHANGEBeOrBecomeSubjunctive QCHANGEAnnadAndlag + +# # I think these verbs only appear in these forms. +# # In which case these terminals should be deleted and a direct reference should be made in the relevant non-terminals. +# # QCHANGEBe -> +# # "vera" + +# # QCHANGEBecome -> +# # "verða" + +# # QCHANGEBeOrBecomeSubjunctive -> +# # "verði" +# # | "sé" + +# # QCHANGELightSubject/fall -> +# # QCHANGELight/fall + +# # QCHANGEColorSubject/fall -> +# # QCHANGEColorWord/fall QCHANGELight/ef? +# # | QCHANGEColorWord/fall "á" QCHANGELight/þgf + +# # QCHANGEBrightnessSubject/fall -> +# # QCHANGEBrightnessWord/fall QCHANGELight/ef? +# # | QCHANGEBrightnessWord/fall "á" QCHANGELight/þgf + +# # QCHANGESceneSubject/fall -> +# # QCHANGESceneWord/fall + +# # QCHANGEGroupNameSubject/fall -> +# # QCHANGEGroupName/fall + +# QIoTSpeakerLocationPreposition -> +# QIoTSpeakerLocationPrepositionFirstPart? QIoTSpeakerLocationPrepositionSecondPart + +# # The latter proverbs are grammatically incorrect, but common errors, both in speech and transcription. +# # The list provided is taken from StefnuAtv in Greynir.grammar. That includes "aftur:ao", which is not applicable here. +# QIoTSpeakerLocationPrepositionFirstPart -> +# StaðarAtv +# | "fram:ao" +# | "inn:ao" +# | "niður:ao" +# | "upp:ao" +# | "út:ao" + +# QIoTSpeakerLocationPrepositionSecondPart -> +# "á" | "í" + +# QIoTSpeakerGroupName/fall -> +# no/fall + +# # QCHANGELightName/fall -> +# # no/fall + + +# # QCHANGESceneName -> +# # no +# # | lo + +# # QCHANGEAnnadAndlag -> +# # QCHANGENewSetting/nf +# # | QCHANGESpyrjaHuldu/nf + +# # QCHANGEAdHverju -> +# # "að" QCHANGENewSetting/þgf + +# # QCHANGEAHvad -> +# # "á" QCHANGENewSetting/þf + +# # QCHANGEIHvad -> +# # "í" QCHANGENewSetting/þf + +# # QCHANGEAHverju -> +# # "á" QCHANGELight/þgf +# # | "á" QCHANGENewSetting/þgf + +# # QCHANGESomethingOrSomehow -> +# # QCHANGEAnnadAndlag +# # | QCHANGEAdHverju + +# # QCHANGESomehow -> +# # QCHANGEAnnadAndlag +# # | QCHANGEThannigAd + +# # QCHANGELight/fall -> +# # QCHANGELightName/fall +# # | QCHANGELightWord/fall + +# # # Should 'birta' be included +# # QCHANGELightWord/fall -> +# # 'ljós'/fall +# # | 'lýsing'/fall +# # | 'birta'/fall +# # | 'Birta'/fall + +# # QCHANGEColorWord/fall -> +# # 'litur'/fall +# # | 'litblær'/fall +# # | 'blær'/fall + +# # QCHANGEBrightnessWords/fall -> +# # 'bjartur'/fall +# # | QCHANGEBrightnessWord/fall + +# # QCHANGEBrightnessWord/fall -> +# # 'birta'/fall +# # | 'Birta'/fall +# # | 'birtustig'/fall + +# # QCHANGESceneWord/fall -> +# # 'sena'/fall +# # | 'stemning'/fall +# # | 'stemming'/fall +# # | 'stemmning'/fall + +# # # Need to ask Hulda how this works. +# # QCHANGESpyrjaHuldu/fall -> +# # # QCHANGEHuldaColor/fall +# # QCHANGEHuldaBrightness/fall +# # # | QCHANGEHuldaScene/fall + +# # # Do I need a "new light state" non-terminal? +# # QCHANGENewSetting/fall -> +# # QCHANGENewColor/fall +# # | QCHANGENewBrightness/fall +# # | QCHANGENewScene/fall + +# # # Missing "meira dimmt" +# # QCHANGEHuldaBrightness/fall -> +# # QCHANGEMoreBrighterOrHigher/fall QCHANGEBrightnessWords/fall? +# # | QCHANGELessDarkerOrLower/fall QCHANGEBrightnessWords/fall? + +# # #Unsure about whether to include /fall after QCHANGEColorName +# # QCHANGENewColor/fall -> +# # QCHANGEColorWord/fall QCHANGEColorName +# # | QCHANGEColorName QCHANGEColorWord/fall? + +# # QCHANGENewBrightness/fall -> +# # 'sá'/fall? QCHANGEBrightestOrDarkest/fall +# # | QCHANGEBrightestOrDarkest/fall QCHANGEBrightnessOrSettingWord/fall + +# # QCHANGENewScene/fall -> +# # QCHANGESceneWord/fall QCHANGESceneName +# # | QCHANGESceneName QCHANGESceneWord/fall? + +# # QCHANGEMoreBrighterOrHigher/fall -> +# # 'mikill:lo'_mst/fall +# # | 'bjartur:lo'_mst/fall +# # | 'ljós:lo'_mst/fall +# # | 'hár:lo'_mst/fall + +# # QCHANGELessDarkerOrLower/fall -> +# # 'lítill:lo'_mst/fall +# # | 'dökkur:lo'_mst/fall +# # | 'dimmur:lo'_mst/fall +# # | 'lágur:lo'_mst/fall + +# # QCHANGEBrightestOrDarkest/fall -> +# # QCHANGEBrightest/fall +# # | QCHANGEDarkest/fall + +# # QCHANGEBrightest/fall -> +# # 'bjartur:lo'_evb +# # | 'bjartur:lo'_esb +# # | 'ljós:lo'_evb +# # | 'ljós:lo'_esb + +# # QCHANGEDarkest/fall -> +# # 'dimmur:lo'_evb +# # | 'dimmur:lo'_esb +# # | 'dökkur:lo'_evb +# # | 'dökkur:lo'_esb + +# # QCHANGEBrightnessOrSettingWord/fall -> +# # QCHANGEBrightnessWord/fall +# # | QCHANGESettingWord/fall + +# # QCHANGESettingWord/fall -> +# # 'stilling'/fall + +# """ -TOPIC_LEMMAS = [ - "tónlist", -] # def QIoTSpeakerIncreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: # result.action = "increase_volume" -# if "hue_obj" not in result: -# result["hue_obj"] = {"on": True, "bri_inc": 64} -# else: -# result["hue_obj"]["bri_inc"] = 64 -# result["hue_obj"]["on"] = True - - -def help_text(lemma: str) -> str: - """Help text to return when query.py is unable to parse a query but - one of the above lemmas is found in it""" - return "Ég skil þig ef þú segir til dæmis: {0}.".format( - random.choice( - ("Hækkaðu í tónlistinni", "Kveiktu á tónlist", "Láttu vera tónlist") - ) - ) - - -# This module wants to handle parse trees for queries -HANDLE_TREE = True - -# The grammar nonterminals this module wants to handle -QUERY_NONTERMINALS = {"QIoTSpeaker"} - -# The context-free grammar for the queries recognized by this plug-in module -# GRAMMAR = read_grammar_file("iot_hue") - -GRAMMAR = f""" - -/þgf = þgf -/ef = ef - -Query → - QIoTSpeaker '?'? - -QIoTSpeaker → - QIoTSpeakerQuery - -# QIoTSpeakerQuery -> -# QIoTSpeakerMakeVerb QIoTSpeakerMakeRest -# | QIoTSpeakerSetVerb QIoTSpeakerSetRest -# | QIoTSpeakerChangeVerb QIoTSpeakerChangeRest -# | QIoTSpeakerLetVerb QIoTSpeakerLetRest -# | QIoTSpeakerTurnOnVerb QIoTSpeakerTurnOnRest -# | QIoTSpeakerTurnOffVerb QIoTSpeakerTurnOffRest -# | QIoTSpeakerIncreaseOrDecreaseVerb QIoTSpeakerIncreaseOrDecreaseRest - -QIoTSpeakerMakeVerb -> - 'gera:so'_bh - -QIoTSpeakerSetVerb -> - 'setja:so'_bh - | 'stilla:so'_bh - -QIoTSpeakerChangeVerb -> - 'breyta:so'_bh - -QIoTSpeakerLetVerb -> - 'láta:so'_bh - -QIoTSpeakerTurnOnVerb -> - 'kveikja:so'_bh - -QIoTSpeakerTurnOffVerb -> - 'slökkva:so'_bh - -QIoTSpeakerIncreaseOrDecreaseVerb -> - QIoTSpeakerIncreaseVerb - | QIoTSpeakerDecreaseVerb - -QIoTSpeakerIncreaseVerb -> - 'hækka:so'_bh - | 'auka:so'_bh - -QIoTSpeakerDecreaseVerb -> - 'lækka:so'_bh - | 'minnka:so'_bh - -# QCHANGEMakeRest -> - # QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigMake - # | QCHANGESubject/þf QCHANGEHvernigMake QCHANGEHvar? - # | QCHANGEHvar? QCHANGESubject/þf QCHANGEHvernigMake - # | QCHANGEHvar? QCHANGEHvernigMake QCHANGESubject/þf - # | QCHANGEHvernigMake QCHANGESubject/þf QCHANGEHvar? - # | QCHANGEHvernigMake QCHANGEHvar? QCHANGESubject/þf - QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? - -# TODO: Add support for "stilltu rauðan lit á ljósið í eldhúsinu" -QCHANGESetRest -> - # QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigSet - # | QCHANGESubject/þf QCHANGEHvernigSet QCHANGEHvar? - # | QCHANGEHvar? QCHANGESubject/þf QCHANGEHvernigSet - # | QCHANGEHvar? QCHANGEHvernigSet QCHANGESubject/þf - # | QCHANGEHvernigSet QCHANGESubject/þf QCHANGEHvar? - # | QCHANGEHvernigSet QCHANGEHvar? QCHANGESubject/þf - "á" QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? - -QCHANGEChangeRest -> - # QCHANGESubjectOne/þgf QCHANGEHvar? QCHANGEHvernigChange - # | QCHANGESubjectOne/þgf QCHANGEHvernigChange QCHANGEHvar? - # | QCHANGEHvar? QCHANGESubjectOne/þgf QCHANGEHvernigChange - # | QCHANGEHvar? QCHANGEHvernigChange QCHANGESubjectOne/þgf - # | QCHANGEHvernigChange QCHANGESubjectOne/þgf QCHANGEHvar? - # | QCHANGEHvernigChange QCHANGEHvar? QCHANGESubjectOne/þgf - -QCHANGELetRest -> - QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigLet - | QCHANGESubject/þf QCHANGEHvernigLet QCHANGEHvar? - | QCHANGEHvar? QCHANGESubject/þf QCHANGEHvernigLet - | QCHANGEHvar? QCHANGEHvernigLet QCHANGESubject/þf - | QCHANGEHvernigLet QCHANGESubject/þf QCHANGEHvar? - | QCHANGEHvernigLet QCHANGEHvar? QCHANGESubject/þf - "vera" QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? - -QCHANGETurnOnRest -> - # QCHANGETurnOnLightsRest - # | QCHANGEAHverju QCHANGEHvar? - # | QCHANGEHvar? QCHANGEAHverju - "á" QIoTSpeakerMusicWord/þgf QIoTSpeakerHvar? - -# QCHANGETurnOnLightsRest -> -# QCHANGELightSubject/þf QCHANGEHvar? -# | QCHANGEHvar QCHANGELightSubject/þf? - -# Would be good to add "slökktu á rauða litnum" functionality -QCHANGETurnOffRest -> - # QCHANGETurnOffLightsRest - "á" QIoTSpeakerMusicWord/þgf QIoTSpeakerHvar? - -# QCHANGETurnOffLightsRest -> -# QCHANGELightSubject/þf QCHANGEHvar? -# | QCHANGEHvar QCHANGELightSubject/þf? - -# TODO: Make the subject categorization cleaner -QCHANGEIncreaseOrDecreaseRest -> - # QCHANGELightSubject/þf QCHANGEHvar? - # | QCHANGEBrightnessSubject/þf QCHANGEHvar? - QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? - | "í" QIoTSpeakerMusicWord/þgf QIoTSpeakerHvar? - -# QCHANGESubject/fall -> -# QCHANGESubjectOne/fall -# | QCHANGESubjectTwo/fall - -QIoTMusicWord -> - 'tónlist'/fall - -# # TODO: Decide whether LightSubject/þgf should be accepted -# QCHANGESubjectOne/fall -> -# QCHANGELightSubject/fall -# | QCHANGEColorSubject/fall -# | QCHANGEBrightnessSubject/fall -# | QCHANGESceneSubject/fall - -# QCHANGESubjectTwo/fall -> -# QCHANGEGroupNameSubject/fall # á bara að styðja "gerðu eldhúsið rautt", "gerðu eldhúsið rómó" "gerðu eldhúsið bjartara", t.d. - -QIoTSpeakerHvar -> - QIoTSpeakerLocationPreposition QIoTSpeakerGroupName/þgf - -# QCHANGEHvernigMake -> -# QCHANGEAnnadAndlag # gerðu litinn rauðan í eldhúsinu EÐA gerðu birtuna meiri í eldhúsinu -# | QCHANGEAdHverju # gerðu litinn að rauðum í eldhúsinu -# | QCHANGEThannigAd - -# QCHANGEHvernigSet -> -# QCHANGEAHvad -# | QCHANGEThannigAd - -# QCHANGEHvernigChange -> -# QCHANGEIHvad -# | QCHANGEThannigAd - -# QCHANGEHvernigLet -> -# QCHANGEBecome QCHANGESomethingOrSomehow -# | QCHANGEBe QCHANGESomehow - -# QCHANGEThannigAd -> -# "þannig" "að"? pfn_nf QCHANGEBeOrBecomeSubjunctive QCHANGEAnnadAndlag - -# I think these verbs only appear in these forms. -# In which case these terminals should be deleted and a direct reference should be made in the relevant non-terminals. -# QCHANGEBe -> -# "vera" - -# QCHANGEBecome -> -# "verða" - -# QCHANGEBeOrBecomeSubjunctive -> -# "verði" -# | "sé" - -# QCHANGELightSubject/fall -> -# QCHANGELight/fall - -# QCHANGEColorSubject/fall -> -# QCHANGEColorWord/fall QCHANGELight/ef? -# | QCHANGEColorWord/fall "á" QCHANGELight/þgf - -# QCHANGEBrightnessSubject/fall -> -# QCHANGEBrightnessWord/fall QCHANGELight/ef? -# | QCHANGEBrightnessWord/fall "á" QCHANGELight/þgf - -# QCHANGESceneSubject/fall -> -# QCHANGESceneWord/fall - -# QCHANGEGroupNameSubject/fall -> -# QCHANGEGroupName/fall - -QIoTSpeakerLocationPreposition -> - QIoTSpeakerLocationPrepositionFirstPart? QIoTSpeakerLocationPrepositionSecondPart - -# The latter proverbs are grammatically incorrect, but common errors, both in speech and transcription. -# The list provided is taken from StefnuAtv in Greynir.grammar. That includes "aftur:ao", which is not applicable here. -QIoTSpeakerLocationPrepositionFirstPart -> - StaðarAtv - | "fram:ao" - | "inn:ao" - | "niður:ao" - | "upp:ao" - | "út:ao" - -QIoTSpeakerLocationPrepositionSecondPart -> - "á" | "í" - -QIoTSpeakerGroupName/fall -> - no/fall -# QCHANGELightName/fall -> -# no/fall +# def QIoTSpeakerDecreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: +# result.action = "decrease_volume" -# QCHANGESceneName -> -# no -# | lo - -# QCHANGEAnnadAndlag -> -# QCHANGENewSetting/nf -# | QCHANGESpyrjaHuldu/nf - -# QCHANGEAdHverju -> -# "að" QCHANGENewSetting/þgf - -# QCHANGEAHvad -> -# "á" QCHANGENewSetting/þf - -# QCHANGEIHvad -> -# "í" QCHANGENewSetting/þf - -# QCHANGEAHverju -> -# "á" QCHANGELight/þgf -# | "á" QCHANGENewSetting/þgf - -# QCHANGESomethingOrSomehow -> -# QCHANGEAnnadAndlag -# | QCHANGEAdHverju - -# QCHANGESomehow -> -# QCHANGEAnnadAndlag -# | QCHANGEThannigAd - -# QCHANGELight/fall -> -# QCHANGELightName/fall -# | QCHANGELightWord/fall -# # Should 'birta' be included -# QCHANGELightWord/fall -> -# 'ljós'/fall -# | 'lýsing'/fall -# | 'birta'/fall -# | 'Birta'/fall +# def QIoTSpeakerGroupName(node: Node, params: QueryStateDict, result: Result) -> None: +# result["group_name"] = result._indefinite -# QCHANGEColorWord/fall -> -# 'litur'/fall -# | 'litblær'/fall -# | 'blær'/fall -# QCHANGEBrightnessWords/fall -> -# 'bjartur'/fall -# | QCHANGEBrightnessWord/fall +# def sentence(state: QueryStateDict, result: Result) -> None: +# """Called when sentence processing is complete""" +# q: Query = state["query"] -# QCHANGEBrightnessWord/fall -> -# 'birta'/fall -# | 'Birta'/fall -# | 'birtustig'/fall +# q.set_qtype(result.get["qtype"]) -# QCHANGESceneWord/fall -> -# 'sena'/fall -# | 'stemning'/fall -# | 'stemming'/fall -# | 'stemmning'/fall +# smartdevice_type = "smartSpeaker" -# # Need to ask Hulda how this works. -# QCHANGESpyrjaHuldu/fall -> -# # QCHANGEHuldaColor/fall -# QCHANGEHuldaBrightness/fall -# # | QCHANGEHuldaScene/fall - -# # Do I need a "new light state" non-terminal? -# QCHANGENewSetting/fall -> -# QCHANGENewColor/fall -# | QCHANGENewBrightness/fall -# | QCHANGENewScene/fall - -# # Missing "meira dimmt" -# QCHANGEHuldaBrightness/fall -> -# QCHANGEMoreBrighterOrHigher/fall QCHANGEBrightnessWords/fall? -# | QCHANGELessDarkerOrLower/fall QCHANGEBrightnessWords/fall? - -# #Unsure about whether to include /fall after QCHANGEColorName -# QCHANGENewColor/fall -> -# QCHANGEColorWord/fall QCHANGEColorName -# | QCHANGEColorName QCHANGEColorWord/fall? - -# QCHANGENewBrightness/fall -> -# 'sá'/fall? QCHANGEBrightestOrDarkest/fall -# | QCHANGEBrightestOrDarkest/fall QCHANGEBrightnessOrSettingWord/fall - -# QCHANGENewScene/fall -> -# QCHANGESceneWord/fall QCHANGESceneName -# | QCHANGESceneName QCHANGESceneWord/fall? - -# QCHANGEMoreBrighterOrHigher/fall -> -# 'mikill:lo'_mst/fall -# | 'bjartur:lo'_mst/fall -# | 'ljós:lo'_mst/fall -# | 'hár:lo'_mst/fall - -# QCHANGELessDarkerOrLower/fall -> -# 'lítill:lo'_mst/fall -# | 'dökkur:lo'_mst/fall -# | 'dimmur:lo'_mst/fall -# | 'lágur:lo'_mst/fall - -# QCHANGEBrightestOrDarkest/fall -> -# QCHANGEBrightest/fall -# | QCHANGEDarkest/fall - -# QCHANGEBrightest/fall -> -# 'bjartur:lo'_evb -# | 'bjartur:lo'_esb -# | 'ljós:lo'_evb -# | 'ljós:lo'_esb - -# QCHANGEDarkest/fall -> -# 'dimmur:lo'_evb -# | 'dimmur:lo'_esb -# | 'dökkur:lo'_evb -# | 'dökkur:lo'_esb - -# QCHANGEBrightnessOrSettingWord/fall -> -# QCHANGEBrightnessWord/fall -# | QCHANGESettingWord/fall - -# QCHANGESettingWord/fall -> -# 'stilling'/fall - -""" - - -def QIoTSpeakerIncreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: - result.action = "increase_volume" - - -def QIoTSpeakerDecreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: - result.action = "decrease_volume" - - -def QIoTSpeakerGroupName(node: Node, params: QueryStateDict, result: Result) -> None: - result["group_name"] = result._indefinite - - -def sentence(state: QueryStateDict, result: Result) -> None: - """Called when sentence processing is complete""" - q: Query = state["query"] - - q.set_qtype(result.get["qtype"]) - - smartdevice_type = "smartSpeaker" - - # Fetch relevant data from the device_data table to perform an action on the lights - sonos_code = q.client_data("sonos_code") - device_data = q.client_data("sonos_credentials").json() - - # TODO: Need to add check for if there are no registered devices to an account, probably when initilazing the querydata - if device_data is not None: - try: - access_token = device_data.get("access_token") - refresh_token = device_data.get("refresh_token") - except: - answer = "Mig vantar Sonos-tóka til að framkvæma þessa aðgerð." - try: - household_id = device_data.get("household_id") - group_id = device_data.get("group_id") - player_id = device_data.get("player_id") - except: - answer = "Mig vantar auðkenni Sonos-tækjanna þinna." - else: - answer = "Mig vantar upplýsingar um Sonos-tækin þín." - - - # Successfully fetched data from the device_data table - print("access_token: " + access_token) - print("refresh_token: " + refresh_token) - print("household_id: " + household_id) - print("group_id: " + group_id) - print("player_id: " + player_id) - try: - # kalla í javascripts stuff - light_or_group_name = result.get("light_name", result.get("group_name", "")) - color_name = result.get("color_name", "") - print("GROUP NAME:", light_or_group_name) - print("COLOR NAME:", color_name) - print(result.hue_obj) - q.set_answer( - *gen_answer( - "ég var að kveikja ljósin! " - # + group_name - # + " " - # + color_name - # + " " - # + result.action - # + " " - # + str(result.hue_obj.get("hue", "enginn litur")) - ) - ) - js = ( - read_jsfile("IoT_Embla/fuse.js") - + f"var BRIDGE_IP = '{bridge_ip}';var USERNAME = '{username}';" - + read_jsfile("IoT_Embla/Philips_Hue/fuse_search.js") - + read_jsfile("IoT_Embla/Philips_Hue/lights.js") - + read_jsfile("IoT_Embla/Philips_Hue/set_lights.js") - ) - js += f"setLights('{light_or_group_name}', '{json.dumps(result.hue_obj)}');" - q.set_command(js) - except Exception as e: - logging.warning("Exception while processing random query: {0}".format(e)) - q.set_error("E_EXCEPTION: {0}".format(e)) - raise - - # f"var BRIDGE_IP = '192.168.1.68';var USERNAME = 'p3obluiXT13IbHMpp4X63ZvZnpNRdbqqMt723gy2';" +# # Fetch relevant data from the device_data table to perform an action on the lights +# sonos_code = q.client_data("sonos_code") +# device_data = q.client_data("sonos_credentials").json() + +# # TODO: Need to add check for if there are no registered devices to an account, probably when initilazing the querydata +# if device_data is not None: +# try: +# access_token = device_data.get("access_token") +# refresh_token = device_data.get("refresh_token") +# except: +# answer = "Mig vantar Sonos-tóka til að framkvæma þessa aðgerð." +# try: +# household_id = device_data.get("household_id") +# group_id = device_data.get("group_id") +# player_id = device_data.get("player_id") +# except: +# answer = "Mig vantar auðkenni Sonos-tækjanna þinna." +# else: +# answer = "Mig vantar upplýsingar um Sonos-tækin þín." + + +# # Successfully fetched data from the device_data table +# print("access_token: " + access_token) +# print("refresh_token: " + refresh_token) +# print("household_id: " + household_id) +# print("group_id: " + group_id) +# print("player_id: " + player_id) +# try: +# # kalla í javascripts stuff +# light_or_group_name = result.get("light_name", result.get("group_name", "")) +# color_name = result.get("color_name", "") +# print("GROUP NAME:", light_or_group_name) +# print("COLOR NAME:", color_name) +# print(result.hue_obj) +# q.set_answer( +# *gen_answer( +# "ég var að kveikja ljósin! " +# # + group_name +# # + " " +# # + color_name +# # + " " +# # + result.action +# # + " " +# # + str(result.hue_obj.get("hue", "enginn litur")) +# ) +# ) +# js = ( +# read_jsfile("IoT_Embla/fuse.js") +# + f"var BRIDGE_IP = '{bridge_ip}';var USERNAME = '{username}';" +# + read_jsfile("IoT_Embla/Philips_Hue/fuse_search.js") +# + read_jsfile("IoT_Embla/Philips_Hue/lights.js") +# + read_jsfile("IoT_Embla/Philips_Hue/set_lights.js") +# ) +# js += f"setLights('{light_or_group_name}', '{json.dumps(result.hue_obj)}');" +# q.set_command(js) +# except Exception as e: +# logging.warning("Exception while processing random query: {0}".format(e)) +# q.set_error("E_EXCEPTION: {0}".format(e)) +# raise + +# # f"var BRIDGE_IP = '192.168.1.68';var USERNAME = 'p3obluiXT13IbHMpp4X63ZvZnpNRdbqqMt723gy2';" diff --git a/queries/js/IoT_Embla/Philips_Hue/hub.js b/queries/js/IoT_Embla/Philips_Hue/hub.js index c9e5ec53..d56e7e35 100644 --- a/queries/js/IoT_Embla/Philips_Hue/hub.js +++ b/queries/js/IoT_Embla/Philips_Hue/hub.js @@ -72,13 +72,12 @@ async function connectHub(clientID, requestURL) { const data = { client_id: clientID, - key: "smartlights", + key: "iot_lights", data: { - smartlights: { - selected_light: "philips_hue", - philips_hue: { + philips_hue: { + credentials: { username: username.success.username, - ipAddress: deviceInfo.internalipaddress, + ip_address: deviceInfo.internalipaddress, }, }, }, diff --git a/routes/api.py b/routes/api.py index c45f418f..4ecc75a3 100755 --- a/routes/api.py +++ b/routes/api.py @@ -721,7 +721,7 @@ def upload_speech_audio(version: int = 1) -> Response: @routes.route("/connect_sonos.api", methods=["GET"]) @routes.route("/connect_sonos.api/v", methods=["GET", "POST"]) def sonos_code(version: int = 1) -> Response: - print("ahkklklfsklhkfl") + print("sonos code") args = request.args client_id = args.get("state") code = args.get("code") From 1df889960a45609164a7f32ed6f7f9d78a60a9a2 Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Mon, 27 Jun 2022 17:34:12 +0000 Subject: [PATCH 108/371] uncommented --- queries/iot_speakers.py | 938 ++++++++++++++++++++-------------------- 1 file changed, 469 insertions(+), 469 deletions(-) diff --git a/queries/iot_speakers.py b/queries/iot_speakers.py index e8902ff2..79b4fc9e 100644 --- a/queries/iot_speakers.py +++ b/queries/iot_speakers.py @@ -1,496 +1,496 @@ -# """ +""" -# Greynir: Natural language processing for Icelandic + Greynir: Natural language processing for Icelandic -# Randomness query response module + Randomness query response module -# Copyright (C) 2022 Miðeind ehf. + Copyright (C) 2022 Miðeind ehf. -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. -# You should have received a copy of the GNU General Public License -# along with this program. If not, see http://www.gnu.org/licenses/. + You should have received a copy of the GNU General Public License + along with this program. If not, see http://www.gnu.org/licenses/. -# This query module handles queries related to the generation -# of random numbers, e.g. "Kastaðu tengingi", "Nefndu tölu milli 5 og 10", etc. + This query module handles queries related to the generation + of random numbers, e.g. "Kastaðu tengingi", "Nefndu tölu milli 5 og 10", etc. -# """ +""" -# # TODO: add "láttu", "hafðu", "litaðu", "kveiktu" functionality. -# # TODO: make the objects of sentences more modular, so that the same structure doesn't need to be written for each action -# # TODO: ditto the previous comment. make the initial non-terminals general and go into specifics at the terminal level instead. -# # TODO: substituion klósett, baðherbergi hugmyndÆ senda lista i javascript og profa i röð -# # TODO: Embla stores old javascript code cached which has caused errors -# # TODO: Cut down javascript sent to Embla -# # TODO: Two specified groups or lights. -# # TODO: No specified location -# # TODO: Fix scene issues +# TODO: add "láttu", "hafðu", "litaðu", "kveiktu" functionality. +# TODO: make the objects of sentences more modular, so that the same structure doesn't need to be written for each action +# TODO: ditto the previous comment. make the initial non-terminals general and go into specifics at the terminal level instead. +# TODO: substituion klósett, baðherbergi hugmyndÆ senda lista i javascript og profa i röð +# TODO: Embla stores old javascript code cached which has caused errors +# TODO: Cut down javascript sent to Embla +# TODO: Two specified groups or lights. +# TODO: No specified location +# TODO: Fix scene issues -# from typing import Dict, Mapping, Optional, cast -# from typing_extensions import TypedDict +from typing import Dict, Mapping, Optional, cast +from typing_extensions import TypedDict -# import logging -# import random -# import json -# import flask +import logging +import random +import json +import flask -# from query import Query, QueryStateDict, AnswerTuple -# from queries import gen_answer, read_jsfile, read_grammar_file -# from tree import Result, Node +from query import Query, QueryStateDict, AnswerTuple +from queries import gen_answer, read_jsfile, read_grammar_file +from tree import Result, Node -# _IoT_QTYPE = "IoT" - -# TOPIC_LEMMAS = [ -# "tónlist", -# ] - -# # def QIoTSpeakerIncreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: -# # result.action = "increase_volume" -# # if "hue_obj" not in result: -# # result["hue_obj"] = {"on": True, "bri_inc": 64} -# # else: -# # result["hue_obj"]["bri_inc"] = 64 -# # result["hue_obj"]["on"] = True - - -# def help_text(lemma: str) -> str: -# """Help text to return when query.py is unable to parse a query but -# one of the above lemmas is found in it""" -# return "Ég skil þig ef þú segir til dæmis: {0}.".format( -# random.choice( -# ("Hækkaðu í tónlistinni", "Kveiktu á tónlist", "Láttu vera tónlist") -# ) -# ) - - -# # This module wants to handle parse trees for queries -# HANDLE_TREE = True - -# # The grammar nonterminals this module wants to handle -# QUERY_NONTERMINALS = {"QIoTSpeaker"} - -# # The context-free grammar for the queries recognized by this plug-in module -# # GRAMMAR = read_grammar_file("iot_hue") - -# GRAMMAR = f""" - -# /þgf = þgf -# /ef = ef - -# Query → -# QIoTSpeaker '?'? - -# QIoTSpeaker → -# QIoTSpeakerQuery - -# # QIoTSpeakerQuery -> -# # QIoTSpeakerMakeVerb QIoTSpeakerMakeRest -# # | QIoTSpeakerSetVerb QIoTSpeakerSetRest -# # | QIoTSpeakerChangeVerb QIoTSpeakerChangeRest -# # | QIoTSpeakerLetVerb QIoTSpeakerLetRest -# # | QIoTSpeakerTurnOnVerb QIoTSpeakerTurnOnRest -# # | QIoTSpeakerTurnOffVerb QIoTSpeakerTurnOffRest -# # | QIoTSpeakerIncreaseOrDecreaseVerb QIoTSpeakerIncreaseOrDecreaseRest - -# QIoTSpeakerMakeVerb -> -# 'gera:so'_bh - -# QIoTSpeakerSetVerb -> -# 'setja:so'_bh -# | 'stilla:so'_bh - -# QIoTSpeakerChangeVerb -> -# 'breyta:so'_bh - -# QIoTSpeakerLetVerb -> -# 'láta:so'_bh - -# QIoTSpeakerTurnOnVerb -> -# 'kveikja:so'_bh - -# QIoTSpeakerTurnOffVerb -> -# 'slökkva:so'_bh - -# QIoTSpeakerIncreaseOrDecreaseVerb -> -# QIoTSpeakerIncreaseVerb -# | QIoTSpeakerDecreaseVerb - -# QIoTSpeakerIncreaseVerb -> -# 'hækka:so'_bh -# | 'auka:so'_bh - -# QIoTSpeakerDecreaseVerb -> -# 'lækka:so'_bh -# | 'minnka:so'_bh - -# # QCHANGEMakeRest -> -# # QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigMake -# # | QCHANGESubject/þf QCHANGEHvernigMake QCHANGEHvar? -# # | QCHANGEHvar? QCHANGESubject/þf QCHANGEHvernigMake -# # | QCHANGEHvar? QCHANGEHvernigMake QCHANGESubject/þf -# # | QCHANGEHvernigMake QCHANGESubject/þf QCHANGEHvar? -# # | QCHANGEHvernigMake QCHANGEHvar? QCHANGESubject/þf -# QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? - -# # TODO: Add support for "stilltu rauðan lit á ljósið í eldhúsinu" -# QCHANGESetRest -> -# # QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigSet -# # | QCHANGESubject/þf QCHANGEHvernigSet QCHANGEHvar? -# # | QCHANGEHvar? QCHANGESubject/þf QCHANGEHvernigSet -# # | QCHANGEHvar? QCHANGEHvernigSet QCHANGESubject/þf -# # | QCHANGEHvernigSet QCHANGESubject/þf QCHANGEHvar? -# # | QCHANGEHvernigSet QCHANGEHvar? QCHANGESubject/þf -# "á" QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? - -# QCHANGEChangeRest -> -# # QCHANGESubjectOne/þgf QCHANGEHvar? QCHANGEHvernigChange -# # | QCHANGESubjectOne/þgf QCHANGEHvernigChange QCHANGEHvar? -# # | QCHANGEHvar? QCHANGESubjectOne/þgf QCHANGEHvernigChange -# # | QCHANGEHvar? QCHANGEHvernigChange QCHANGESubjectOne/þgf -# # | QCHANGEHvernigChange QCHANGESubjectOne/þgf QCHANGEHvar? -# # | QCHANGEHvernigChange QCHANGEHvar? QCHANGESubjectOne/þgf - -# QCHANGELetRest -> -# QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigLet -# | QCHANGESubject/þf QCHANGEHvernigLet QCHANGEHvar? -# | QCHANGEHvar? QCHANGESubject/þf QCHANGEHvernigLet -# | QCHANGEHvar? QCHANGEHvernigLet QCHANGESubject/þf -# | QCHANGEHvernigLet QCHANGESubject/þf QCHANGEHvar? -# | QCHANGEHvernigLet QCHANGEHvar? QCHANGESubject/þf -# "vera" QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? - -# QCHANGETurnOnRest -> -# # QCHANGETurnOnLightsRest -# # | QCHANGEAHverju QCHANGEHvar? -# # | QCHANGEHvar? QCHANGEAHverju -# "á" QIoTSpeakerMusicWord/þgf QIoTSpeakerHvar? - -# # QCHANGETurnOnLightsRest -> -# # QCHANGELightSubject/þf QCHANGEHvar? -# # | QCHANGEHvar QCHANGELightSubject/þf? - -# # Would be good to add "slökktu á rauða litnum" functionality -# QCHANGETurnOffRest -> -# # QCHANGETurnOffLightsRest -# "á" QIoTSpeakerMusicWord/þgf QIoTSpeakerHvar? - -# # QCHANGETurnOffLightsRest -> -# # QCHANGELightSubject/þf QCHANGEHvar? -# # | QCHANGEHvar QCHANGELightSubject/þf? - -# # TODO: Make the subject categorization cleaner -# QCHANGEIncreaseOrDecreaseRest -> -# # QCHANGELightSubject/þf QCHANGEHvar? -# # | QCHANGEBrightnessSubject/þf QCHANGEHvar? -# QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? -# | "í" QIoTSpeakerMusicWord/þgf QIoTSpeakerHvar? - -# # QCHANGESubject/fall -> -# # QCHANGESubjectOne/fall -# # | QCHANGESubjectTwo/fall - -# QIoTMusicWord -> -# 'tónlist'/fall - -# # # TODO: Decide whether LightSubject/þgf should be accepted -# # QCHANGESubjectOne/fall -> -# # QCHANGELightSubject/fall -# # | QCHANGEColorSubject/fall -# # | QCHANGEBrightnessSubject/fall -# # | QCHANGESceneSubject/fall - -# # QCHANGESubjectTwo/fall -> -# # QCHANGEGroupNameSubject/fall # á bara að styðja "gerðu eldhúsið rautt", "gerðu eldhúsið rómó" "gerðu eldhúsið bjartara", t.d. - -# QIoTSpeakerHvar -> -# QIoTSpeakerLocationPreposition QIoTSpeakerGroupName/þgf - -# # QCHANGEHvernigMake -> -# # QCHANGEAnnadAndlag # gerðu litinn rauðan í eldhúsinu EÐA gerðu birtuna meiri í eldhúsinu -# # | QCHANGEAdHverju # gerðu litinn að rauðum í eldhúsinu -# # | QCHANGEThannigAd - -# # QCHANGEHvernigSet -> -# # QCHANGEAHvad -# # | QCHANGEThannigAd - -# # QCHANGEHvernigChange -> -# # QCHANGEIHvad -# # | QCHANGEThannigAd - -# # QCHANGEHvernigLet -> -# # QCHANGEBecome QCHANGESomethingOrSomehow -# # | QCHANGEBe QCHANGESomehow - -# # QCHANGEThannigAd -> -# # "þannig" "að"? pfn_nf QCHANGEBeOrBecomeSubjunctive QCHANGEAnnadAndlag - -# # I think these verbs only appear in these forms. -# # In which case these terminals should be deleted and a direct reference should be made in the relevant non-terminals. -# # QCHANGEBe -> -# # "vera" - -# # QCHANGEBecome -> -# # "verða" - -# # QCHANGEBeOrBecomeSubjunctive -> -# # "verði" -# # | "sé" - -# # QCHANGELightSubject/fall -> -# # QCHANGELight/fall - -# # QCHANGEColorSubject/fall -> -# # QCHANGEColorWord/fall QCHANGELight/ef? -# # | QCHANGEColorWord/fall "á" QCHANGELight/þgf - -# # QCHANGEBrightnessSubject/fall -> -# # QCHANGEBrightnessWord/fall QCHANGELight/ef? -# # | QCHANGEBrightnessWord/fall "á" QCHANGELight/þgf - -# # QCHANGESceneSubject/fall -> -# # QCHANGESceneWord/fall - -# # QCHANGEGroupNameSubject/fall -> -# # QCHANGEGroupName/fall - -# QIoTSpeakerLocationPreposition -> -# QIoTSpeakerLocationPrepositionFirstPart? QIoTSpeakerLocationPrepositionSecondPart - -# # The latter proverbs are grammatically incorrect, but common errors, both in speech and transcription. -# # The list provided is taken from StefnuAtv in Greynir.grammar. That includes "aftur:ao", which is not applicable here. -# QIoTSpeakerLocationPrepositionFirstPart -> -# StaðarAtv -# | "fram:ao" -# | "inn:ao" -# | "niður:ao" -# | "upp:ao" -# | "út:ao" - -# QIoTSpeakerLocationPrepositionSecondPart -> -# "á" | "í" - -# QIoTSpeakerGroupName/fall -> -# no/fall - -# # QCHANGELightName/fall -> -# # no/fall - - -# # QCHANGESceneName -> -# # no -# # | lo - -# # QCHANGEAnnadAndlag -> -# # QCHANGENewSetting/nf -# # | QCHANGESpyrjaHuldu/nf - -# # QCHANGEAdHverju -> -# # "að" QCHANGENewSetting/þgf - -# # QCHANGEAHvad -> -# # "á" QCHANGENewSetting/þf - -# # QCHANGEIHvad -> -# # "í" QCHANGENewSetting/þf - -# # QCHANGEAHverju -> -# # "á" QCHANGELight/þgf -# # | "á" QCHANGENewSetting/þgf - -# # QCHANGESomethingOrSomehow -> -# # QCHANGEAnnadAndlag -# # | QCHANGEAdHverju - -# # QCHANGESomehow -> -# # QCHANGEAnnadAndlag -# # | QCHANGEThannigAd - -# # QCHANGELight/fall -> -# # QCHANGELightName/fall -# # | QCHANGELightWord/fall - -# # # Should 'birta' be included -# # QCHANGELightWord/fall -> -# # 'ljós'/fall -# # | 'lýsing'/fall -# # | 'birta'/fall -# # | 'Birta'/fall - -# # QCHANGEColorWord/fall -> -# # 'litur'/fall -# # | 'litblær'/fall -# # | 'blær'/fall - -# # QCHANGEBrightnessWords/fall -> -# # 'bjartur'/fall -# # | QCHANGEBrightnessWord/fall - -# # QCHANGEBrightnessWord/fall -> -# # 'birta'/fall -# # | 'Birta'/fall -# # | 'birtustig'/fall - -# # QCHANGESceneWord/fall -> -# # 'sena'/fall -# # | 'stemning'/fall -# # | 'stemming'/fall -# # | 'stemmning'/fall - -# # # Need to ask Hulda how this works. -# # QCHANGESpyrjaHuldu/fall -> -# # # QCHANGEHuldaColor/fall -# # QCHANGEHuldaBrightness/fall -# # # | QCHANGEHuldaScene/fall - -# # # Do I need a "new light state" non-terminal? -# # QCHANGENewSetting/fall -> -# # QCHANGENewColor/fall -# # | QCHANGENewBrightness/fall -# # | QCHANGENewScene/fall - -# # # Missing "meira dimmt" -# # QCHANGEHuldaBrightness/fall -> -# # QCHANGEMoreBrighterOrHigher/fall QCHANGEBrightnessWords/fall? -# # | QCHANGELessDarkerOrLower/fall QCHANGEBrightnessWords/fall? - -# # #Unsure about whether to include /fall after QCHANGEColorName -# # QCHANGENewColor/fall -> -# # QCHANGEColorWord/fall QCHANGEColorName -# # | QCHANGEColorName QCHANGEColorWord/fall? - -# # QCHANGENewBrightness/fall -> -# # 'sá'/fall? QCHANGEBrightestOrDarkest/fall -# # | QCHANGEBrightestOrDarkest/fall QCHANGEBrightnessOrSettingWord/fall - -# # QCHANGENewScene/fall -> -# # QCHANGESceneWord/fall QCHANGESceneName -# # | QCHANGESceneName QCHANGESceneWord/fall? - -# # QCHANGEMoreBrighterOrHigher/fall -> -# # 'mikill:lo'_mst/fall -# # | 'bjartur:lo'_mst/fall -# # | 'ljós:lo'_mst/fall -# # | 'hár:lo'_mst/fall - -# # QCHANGELessDarkerOrLower/fall -> -# # 'lítill:lo'_mst/fall -# # | 'dökkur:lo'_mst/fall -# # | 'dimmur:lo'_mst/fall -# # | 'lágur:lo'_mst/fall - -# # QCHANGEBrightestOrDarkest/fall -> -# # QCHANGEBrightest/fall -# # | QCHANGEDarkest/fall - -# # QCHANGEBrightest/fall -> -# # 'bjartur:lo'_evb -# # | 'bjartur:lo'_esb -# # | 'ljós:lo'_evb -# # | 'ljós:lo'_esb - -# # QCHANGEDarkest/fall -> -# # 'dimmur:lo'_evb -# # | 'dimmur:lo'_esb -# # | 'dökkur:lo'_evb -# # | 'dökkur:lo'_esb - -# # QCHANGEBrightnessOrSettingWord/fall -> -# # QCHANGEBrightnessWord/fall -# # | QCHANGESettingWord/fall - -# # QCHANGESettingWord/fall -> -# # 'stilling'/fall - -# """ +_IoT_QTYPE = "IoT" +TOPIC_LEMMAS = [ + "tónlist", +] # def QIoTSpeakerIncreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: # result.action = "increase_volume" +# if "hue_obj" not in result: +# result["hue_obj"] = {"on": True, "bri_inc": 64} +# else: +# result["hue_obj"]["bri_inc"] = 64 +# result["hue_obj"]["on"] = True + + +def help_text(lemma: str) -> str: + """Help text to return when query.py is unable to parse a query but + one of the above lemmas is found in it""" + return "Ég skil þig ef þú segir til dæmis: {0}.".format( + random.choice( + ("Hækkaðu í tónlistinni", "Kveiktu á tónlist", "Láttu vera tónlist") + ) + ) + + +# This module wants to handle parse trees for queries +HANDLE_TREE = True + +# The grammar nonterminals this module wants to handle +QUERY_NONTERMINALS = {"QIoTSpeaker"} + +# The context-free grammar for the queries recognized by this plug-in module +# GRAMMAR = read_grammar_file("iot_hue") + +GRAMMAR = f""" + +/þgf = þgf +/ef = ef + +Query → + QIoTSpeaker '?'? + +QIoTSpeaker → + QIoTSpeakerQuery + +# QIoTSpeakerQuery -> +# QIoTSpeakerMakeVerb QIoTSpeakerMakeRest +# | QIoTSpeakerSetVerb QIoTSpeakerSetRest +# | QIoTSpeakerChangeVerb QIoTSpeakerChangeRest +# | QIoTSpeakerLetVerb QIoTSpeakerLetRest +# | QIoTSpeakerTurnOnVerb QIoTSpeakerTurnOnRest +# | QIoTSpeakerTurnOffVerb QIoTSpeakerTurnOffRest +# | QIoTSpeakerIncreaseOrDecreaseVerb QIoTSpeakerIncreaseOrDecreaseRest + +QIoTSpeakerMakeVerb -> + 'gera:so'_bh + +QIoTSpeakerSetVerb -> + 'setja:so'_bh + | 'stilla:so'_bh + +QIoTSpeakerChangeVerb -> + 'breyta:so'_bh + +QIoTSpeakerLetVerb -> + 'láta:so'_bh + +QIoTSpeakerTurnOnVerb -> + 'kveikja:so'_bh + +QIoTSpeakerTurnOffVerb -> + 'slökkva:so'_bh + +QIoTSpeakerIncreaseOrDecreaseVerb -> + QIoTSpeakerIncreaseVerb + | QIoTSpeakerDecreaseVerb + +QIoTSpeakerIncreaseVerb -> + 'hækka:so'_bh + | 'auka:so'_bh + +QIoTSpeakerDecreaseVerb -> + 'lækka:so'_bh + | 'minnka:so'_bh + +# QCHANGEMakeRest -> + # QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigMake + # | QCHANGESubject/þf QCHANGEHvernigMake QCHANGEHvar? + # | QCHANGEHvar? QCHANGESubject/þf QCHANGEHvernigMake + # | QCHANGEHvar? QCHANGEHvernigMake QCHANGESubject/þf + # | QCHANGEHvernigMake QCHANGESubject/þf QCHANGEHvar? + # | QCHANGEHvernigMake QCHANGEHvar? QCHANGESubject/þf + QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? + +# TODO: Add support for "stilltu rauðan lit á ljósið í eldhúsinu" +QCHANGESetRest -> + # QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigSet + # | QCHANGESubject/þf QCHANGEHvernigSet QCHANGEHvar? + # | QCHANGEHvar? QCHANGESubject/þf QCHANGEHvernigSet + # | QCHANGEHvar? QCHANGEHvernigSet QCHANGESubject/þf + # | QCHANGEHvernigSet QCHANGESubject/þf QCHANGEHvar? + # | QCHANGEHvernigSet QCHANGEHvar? QCHANGESubject/þf + "á" QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? + +QCHANGEChangeRest -> + # QCHANGESubjectOne/þgf QCHANGEHvar? QCHANGEHvernigChange + # | QCHANGESubjectOne/þgf QCHANGEHvernigChange QCHANGEHvar? + # | QCHANGEHvar? QCHANGESubjectOne/þgf QCHANGEHvernigChange + # | QCHANGEHvar? QCHANGEHvernigChange QCHANGESubjectOne/þgf + # | QCHANGEHvernigChange QCHANGESubjectOne/þgf QCHANGEHvar? + # | QCHANGEHvernigChange QCHANGEHvar? QCHANGESubjectOne/þgf + +QCHANGELetRest -> + QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigLet + | QCHANGESubject/þf QCHANGEHvernigLet QCHANGEHvar? + | QCHANGEHvar? QCHANGESubject/þf QCHANGEHvernigLet + | QCHANGEHvar? QCHANGEHvernigLet QCHANGESubject/þf + | QCHANGEHvernigLet QCHANGESubject/þf QCHANGEHvar? + | QCHANGEHvernigLet QCHANGEHvar? QCHANGESubject/þf + "vera" QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? + +QCHANGETurnOnRest -> + # QCHANGETurnOnLightsRest + # | QCHANGEAHverju QCHANGEHvar? + # | QCHANGEHvar? QCHANGEAHverju + "á" QIoTSpeakerMusicWord/þgf QIoTSpeakerHvar? + +# QCHANGETurnOnLightsRest -> +# QCHANGELightSubject/þf QCHANGEHvar? +# | QCHANGEHvar QCHANGELightSubject/þf? + +# Would be good to add "slökktu á rauða litnum" functionality +QCHANGETurnOffRest -> + # QCHANGETurnOffLightsRest + "á" QIoTSpeakerMusicWord/þgf QIoTSpeakerHvar? + +# QCHANGETurnOffLightsRest -> +# QCHANGELightSubject/þf QCHANGEHvar? +# | QCHANGEHvar QCHANGELightSubject/þf? + +# TODO: Make the subject categorization cleaner +QCHANGEIncreaseOrDecreaseRest -> + # QCHANGELightSubject/þf QCHANGEHvar? + # | QCHANGEBrightnessSubject/þf QCHANGEHvar? + QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? + | "í" QIoTSpeakerMusicWord/þgf QIoTSpeakerHvar? + +# QCHANGESubject/fall -> +# QCHANGESubjectOne/fall +# | QCHANGESubjectTwo/fall + +QIoTMusicWord -> + 'tónlist'/fall + +# # TODO: Decide whether LightSubject/þgf should be accepted +# QCHANGESubjectOne/fall -> +# QCHANGELightSubject/fall +# | QCHANGEColorSubject/fall +# | QCHANGEBrightnessSubject/fall +# | QCHANGESceneSubject/fall + +# QCHANGESubjectTwo/fall -> +# QCHANGEGroupNameSubject/fall # á bara að styðja "gerðu eldhúsið rautt", "gerðu eldhúsið rómó" "gerðu eldhúsið bjartara", t.d. + +QIoTSpeakerHvar -> + QIoTSpeakerLocationPreposition QIoTSpeakerGroupName/þgf + +# QCHANGEHvernigMake -> +# QCHANGEAnnadAndlag # gerðu litinn rauðan í eldhúsinu EÐA gerðu birtuna meiri í eldhúsinu +# | QCHANGEAdHverju # gerðu litinn að rauðum í eldhúsinu +# | QCHANGEThannigAd + +# QCHANGEHvernigSet -> +# QCHANGEAHvad +# | QCHANGEThannigAd + +# QCHANGEHvernigChange -> +# QCHANGEIHvad +# | QCHANGEThannigAd + +# QCHANGEHvernigLet -> +# QCHANGEBecome QCHANGESomethingOrSomehow +# | QCHANGEBe QCHANGESomehow + +# QCHANGEThannigAd -> +# "þannig" "að"? pfn_nf QCHANGEBeOrBecomeSubjunctive QCHANGEAnnadAndlag + +# I think these verbs only appear in these forms. +# In which case these terminals should be deleted and a direct reference should be made in the relevant non-terminals. +# QCHANGEBe -> +# "vera" + +# QCHANGEBecome -> +# "verða" + +# QCHANGEBeOrBecomeSubjunctive -> +# "verði" +# | "sé" + +# QCHANGELightSubject/fall -> +# QCHANGELight/fall + +# QCHANGEColorSubject/fall -> +# QCHANGEColorWord/fall QCHANGELight/ef? +# | QCHANGEColorWord/fall "á" QCHANGELight/þgf + +# QCHANGEBrightnessSubject/fall -> +# QCHANGEBrightnessWord/fall QCHANGELight/ef? +# | QCHANGEBrightnessWord/fall "á" QCHANGELight/þgf + +# QCHANGESceneSubject/fall -> +# QCHANGESceneWord/fall + +# QCHANGEGroupNameSubject/fall -> +# QCHANGEGroupName/fall + +QIoTSpeakerLocationPreposition -> + QIoTSpeakerLocationPrepositionFirstPart? QIoTSpeakerLocationPrepositionSecondPart + +# The latter proverbs are grammatically incorrect, but common errors, both in speech and transcription. +# The list provided is taken from StefnuAtv in Greynir.grammar. That includes "aftur:ao", which is not applicable here. +QIoTSpeakerLocationPrepositionFirstPart -> + StaðarAtv + | "fram:ao" + | "inn:ao" + | "niður:ao" + | "upp:ao" + | "út:ao" + +QIoTSpeakerLocationPrepositionSecondPart -> + "á" | "í" + +QIoTSpeakerGroupName/fall -> + no/fall +# QCHANGELightName/fall -> +# no/fall -# def QIoTSpeakerDecreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: -# result.action = "decrease_volume" +# QCHANGESceneName -> +# no +# | lo + +# QCHANGEAnnadAndlag -> +# QCHANGENewSetting/nf +# | QCHANGESpyrjaHuldu/nf + +# QCHANGEAdHverju -> +# "að" QCHANGENewSetting/þgf + +# QCHANGEAHvad -> +# "á" QCHANGENewSetting/þf + +# QCHANGEIHvad -> +# "í" QCHANGENewSetting/þf + +# QCHANGEAHverju -> +# "á" QCHANGELight/þgf +# | "á" QCHANGENewSetting/þgf + +# QCHANGESomethingOrSomehow -> +# QCHANGEAnnadAndlag +# | QCHANGEAdHverju + +# QCHANGESomehow -> +# QCHANGEAnnadAndlag +# | QCHANGEThannigAd + +# QCHANGELight/fall -> +# QCHANGELightName/fall +# | QCHANGELightWord/fall -# def QIoTSpeakerGroupName(node: Node, params: QueryStateDict, result: Result) -> None: -# result["group_name"] = result._indefinite +# # Should 'birta' be included +# QCHANGELightWord/fall -> +# 'ljós'/fall +# | 'lýsing'/fall +# | 'birta'/fall +# | 'Birta'/fall +# QCHANGEColorWord/fall -> +# 'litur'/fall +# | 'litblær'/fall +# | 'blær'/fall -# def sentence(state: QueryStateDict, result: Result) -> None: -# """Called when sentence processing is complete""" -# q: Query = state["query"] +# QCHANGEBrightnessWords/fall -> +# 'bjartur'/fall +# | QCHANGEBrightnessWord/fall -# q.set_qtype(result.get["qtype"]) +# QCHANGEBrightnessWord/fall -> +# 'birta'/fall +# | 'Birta'/fall +# | 'birtustig'/fall -# smartdevice_type = "smartSpeaker" +# QCHANGESceneWord/fall -> +# 'sena'/fall +# | 'stemning'/fall +# | 'stemming'/fall +# | 'stemmning'/fall -# # Fetch relevant data from the device_data table to perform an action on the lights -# sonos_code = q.client_data("sonos_code") -# device_data = q.client_data("sonos_credentials").json() +# # Need to ask Hulda how this works. +# QCHANGESpyrjaHuldu/fall -> +# # QCHANGEHuldaColor/fall +# QCHANGEHuldaBrightness/fall +# # | QCHANGEHuldaScene/fall + +# # Do I need a "new light state" non-terminal? +# QCHANGENewSetting/fall -> +# QCHANGENewColor/fall +# | QCHANGENewBrightness/fall +# | QCHANGENewScene/fall -# # TODO: Need to add check for if there are no registered devices to an account, probably when initilazing the querydata -# if device_data is not None: -# try: -# access_token = device_data.get("access_token") -# refresh_token = device_data.get("refresh_token") -# except: -# answer = "Mig vantar Sonos-tóka til að framkvæma þessa aðgerð." -# try: -# household_id = device_data.get("household_id") -# group_id = device_data.get("group_id") -# player_id = device_data.get("player_id") -# except: -# answer = "Mig vantar auðkenni Sonos-tækjanna þinna." -# else: -# answer = "Mig vantar upplýsingar um Sonos-tækin þín." - - -# # Successfully fetched data from the device_data table -# print("access_token: " + access_token) -# print("refresh_token: " + refresh_token) -# print("household_id: " + household_id) -# print("group_id: " + group_id) -# print("player_id: " + player_id) -# try: -# # kalla í javascripts stuff -# light_or_group_name = result.get("light_name", result.get("group_name", "")) -# color_name = result.get("color_name", "") -# print("GROUP NAME:", light_or_group_name) -# print("COLOR NAME:", color_name) -# print(result.hue_obj) -# q.set_answer( -# *gen_answer( -# "ég var að kveikja ljósin! " -# # + group_name -# # + " " -# # + color_name -# # + " " -# # + result.action -# # + " " -# # + str(result.hue_obj.get("hue", "enginn litur")) -# ) -# ) -# js = ( -# read_jsfile("IoT_Embla/fuse.js") -# + f"var BRIDGE_IP = '{bridge_ip}';var USERNAME = '{username}';" -# + read_jsfile("IoT_Embla/Philips_Hue/fuse_search.js") -# + read_jsfile("IoT_Embla/Philips_Hue/lights.js") -# + read_jsfile("IoT_Embla/Philips_Hue/set_lights.js") -# ) -# js += f"setLights('{light_or_group_name}', '{json.dumps(result.hue_obj)}');" -# q.set_command(js) -# except Exception as e: -# logging.warning("Exception while processing random query: {0}".format(e)) -# q.set_error("E_EXCEPTION: {0}".format(e)) -# raise - -# # f"var BRIDGE_IP = '192.168.1.68';var USERNAME = 'p3obluiXT13IbHMpp4X63ZvZnpNRdbqqMt723gy2';" +# # Missing "meira dimmt" +# QCHANGEHuldaBrightness/fall -> +# QCHANGEMoreBrighterOrHigher/fall QCHANGEBrightnessWords/fall? +# | QCHANGELessDarkerOrLower/fall QCHANGEBrightnessWords/fall? + +# #Unsure about whether to include /fall after QCHANGEColorName +# QCHANGENewColor/fall -> +# QCHANGEColorWord/fall QCHANGEColorName +# | QCHANGEColorName QCHANGEColorWord/fall? + +# QCHANGENewBrightness/fall -> +# 'sá'/fall? QCHANGEBrightestOrDarkest/fall +# | QCHANGEBrightestOrDarkest/fall QCHANGEBrightnessOrSettingWord/fall + +# QCHANGENewScene/fall -> +# QCHANGESceneWord/fall QCHANGESceneName +# | QCHANGESceneName QCHANGESceneWord/fall? + +# QCHANGEMoreBrighterOrHigher/fall -> +# 'mikill:lo'_mst/fall +# | 'bjartur:lo'_mst/fall +# | 'ljós:lo'_mst/fall +# | 'hár:lo'_mst/fall + +# QCHANGELessDarkerOrLower/fall -> +# 'lítill:lo'_mst/fall +# | 'dökkur:lo'_mst/fall +# | 'dimmur:lo'_mst/fall +# | 'lágur:lo'_mst/fall + +# QCHANGEBrightestOrDarkest/fall -> +# QCHANGEBrightest/fall +# | QCHANGEDarkest/fall + +# QCHANGEBrightest/fall -> +# 'bjartur:lo'_evb +# | 'bjartur:lo'_esb +# | 'ljós:lo'_evb +# | 'ljós:lo'_esb + +# QCHANGEDarkest/fall -> +# 'dimmur:lo'_evb +# | 'dimmur:lo'_esb +# | 'dökkur:lo'_evb +# | 'dökkur:lo'_esb + +# QCHANGEBrightnessOrSettingWord/fall -> +# QCHANGEBrightnessWord/fall +# | QCHANGESettingWord/fall + +# QCHANGESettingWord/fall -> +# 'stilling'/fall + +""" + + +def QIoTSpeakerIncreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: + result.action = "increase_volume" + + +def QIoTSpeakerDecreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: + result.action = "decrease_volume" + + +def QIoTSpeakerGroupName(node: Node, params: QueryStateDict, result: Result) -> None: + result["group_name"] = result._indefinite + + +def sentence(state: QueryStateDict, result: Result) -> None: + """Called when sentence processing is complete""" + q: Query = state["query"] + + q.set_qtype(result.get["qtype"]) + + smartdevice_type = "smartSpeaker" + + # Fetch relevant data from the device_data table to perform an action on the lights + sonos_code = q.client_data("sonos_code") + device_data = q.client_data("sonos_credentials").json() + + # TODO: Need to add check for if there are no registered devices to an account, probably when initilazing the querydata + if device_data is not None: + try: + access_token = device_data.get("access_token") + refresh_token = device_data.get("refresh_token") + except: + answer = "Mig vantar Sonos-tóka til að framkvæma þessa aðgerð." + try: + household_id = device_data.get("household_id") + group_id = device_data.get("group_id") + player_id = device_data.get("player_id") + except: + answer = "Mig vantar auðkenni Sonos-tækjanna þinna." + else: + answer = "Mig vantar upplýsingar um Sonos-tækin þín." + + + # Successfully fetched data from the device_data table + print("access_token: " + access_token) + print("refresh_token: " + refresh_token) + print("household_id: " + household_id) + print("group_id: " + group_id) + print("player_id: " + player_id) + try: + # kalla í javascripts stuff + light_or_group_name = result.get("light_name", result.get("group_name", "")) + color_name = result.get("color_name", "") + print("GROUP NAME:", light_or_group_name) + print("COLOR NAME:", color_name) + print(result.hue_obj) + q.set_answer( + *gen_answer( + "ég var að kveikja ljósin! " + # + group_name + # + " " + # + color_name + # + " " + # + result.action + # + " " + # + str(result.hue_obj.get("hue", "enginn litur")) + ) + ) + js = ( + read_jsfile("IoT_Embla/fuse.js") + + f"var BRIDGE_IP = '{bridge_ip}';var USERNAME = '{username}';" + + read_jsfile("IoT_Embla/Philips_Hue/fuse_search.js") + + read_jsfile("IoT_Embla/Philips_Hue/lights.js") + + read_jsfile("IoT_Embla/Philips_Hue/set_lights.js") + ) + js += f"setLights('{light_or_group_name}', '{json.dumps(result.hue_obj)}');" + q.set_command(js) + except Exception as e: + logging.warning("Exception while processing random query: {0}".format(e)) + q.set_error("E_EXCEPTION: {0}".format(e)) + raise + + # f"var BRIDGE_IP = '192.168.1.68';var USERNAME = 'p3obluiXT13IbHMpp4X63ZvZnpNRdbqqMt723gy2';" From fa1609e917aa56b3483b72b767c6ebea6534afd4 Mon Sep 17 00:00:00 2001 From: Logi E Date: Tue, 28 Jun 2022 10:22:51 +0000 Subject: [PATCH 109/371] fixed clock before fruit bug, adding date&time resources --- queries/dialogue.py | 213 +++++++++++------------ queries/fruitseller/fruitseller.toml | 2 +- queries/fruitseller_module.py | 249 ++++++++++++++++----------- 3 files changed, 258 insertions(+), 206 deletions(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index bfdbdf03..8ec1173b 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -1,4 +1,14 @@ -from typing import Any, Callable, Dict, Mapping, Tuple, Union, List, Optional, cast +from typing import ( + Any, + Callable, + Dict, + Mapping, + Tuple, + Union, + List, + Optional, + cast, +) from typing_extensions import TypedDict import os.path @@ -28,10 +38,14 @@ ListResourceType = List[ResourceDataType] # Types for use in callbacks -CallbackType = Callable[["Resource", Result], None] +CallbackType = Callable[["Resource", "DialogueStateManager", Result], None] FilterFuncType = Callable[["Resource"], bool] CallbackTupleType = Tuple[FilterFuncType, CallbackType] +# Types for use in generating prompts/answers +AnsweringFunctionType = Callable[["Resource", "DialogueStateManager"], Optional[str]] +AnsweringFunctionMap = Mapping[str, AnsweringFunctionType] + class ResourceState(IntEnum): """Enum representing the different states a dialogue resource can be in.""" @@ -104,6 +118,10 @@ def is_paused(self) -> bool: def is_skipped(self) -> bool: return self.state is ResourceState.SKIPPED + @property + def is_cancelled(self) -> bool: + return self.state is ResourceState.CANCELLED + def next_action(self) -> Any: raise NotImplementedError() @@ -120,24 +138,6 @@ class ListResource(Resource): def list_available_options(self) -> str: raise NotImplementedError() - # def generate_answer( - # self, dsm: "DialogueStateManager", result: Result - # ) -> Optional[str]: - # ans: Optional[str] = self._get_child_answer(dsm, result) - # if ans: - # return ans - # if self.state is ResourceState.UNFULFILLED: - # ans = self.prompts["initial"] - # if self.state is ResourceState.PARTIALLY_FULFILLED: - # ans = ( - # f"{self.prompts['repeat'].format(list_items = _list_items(self.data))}" - # ) - # if self.state is ResourceState.FULFILLED: - # ans = ( - # f"{self.prompts['confirm'].format(list_items = _list_items(self.data))}" - # ) - # return ans - # TODO: # ExactlyOneResource (choose one resource from options) @@ -157,19 +157,14 @@ def set_no(self): self.data = False self.state = ResourceState.CONFIRMED - # def generate_answer( - # self, dsm: "DialogueStateManager", result: Result - # ) -> Optional[str]: - # ans: Optional[str] = self._get_child_answer(dsm, result) - # if ans: - # return ans - # if self.data: - # ans = self.prompts["yes_answer"] - # else: - # ans = self.prompts["no_answer"] - # if ans is None: - # raise ValueError("No answer generated") - # return ans + +@dataclass +class DateResource(Resource): + data: datetime.date = field(default_factory=datetime.date.today) + +@dataclass +class TimeResource(Resource): + data: datetime.time = field(default_factory=datetime.time) @dataclass @@ -198,37 +193,6 @@ def set_date(self, new_date: Optional[datetime.date] = None) -> None: def set_time(self, new_time: Optional[datetime.time] = None) -> None: self.data[1] = new_time - def get_answer(self, dsm: "DialogueStateManager") -> Optional[str]: - ans: Optional[str] = super().get_answer(dsm) - if ans: - return ans - - if self.state is ResourceState.CONFIRMED: - return None - - if self.state is ResourceState.UNFULFILLED: - ans = self.prompts["initial"] - - if self.state is ResourceState.PARTIALLY_FULFILLED: - if self.has_date(): - ans = self.prompts["date_fulfilled"].format( - date=self.data[0].strftime("%Y/%m/%d") - ) - if self.has_time() and self.prompts["time_fulfilled"]: - ans = self.prompts["time_fulfilled"].format( - time=self.data[1].strftime("%H:%M") - ) - - if self.state is ResourceState.FULFILLED: - if self.has_date() and self.has_time(): - ans = self.prompts["confirm"].format( - date_time=datetime.datetime.combine( - cast(datetime.date, self.data[0]), - cast(datetime.time, self.data[1]), - ).strftime("%Y/%m/%d %H:%M") - ) - return ans - @dataclass class NumberResource(Resource): @@ -241,6 +205,11 @@ class OrResource(Resource): exclusive: bool = False # Only one of the resources should be fulfilled +@dataclass +class AndResource(Resource): # For answering multiple resources at the same time + data: Dict[str, Any] = field(default_factory=dict) + + @dataclass class FinalResource(Resource): data: Any = None @@ -255,7 +224,7 @@ class DialogueStructureType(TypedDict): """ """ dialogue_name: str - resources: List[Resource] + resources: Dict[str, Resource] _RESOURCE_TYPES: Mapping[str, Any] = { @@ -274,13 +243,16 @@ def _load_dialogue_structure(filename: str) -> DialogueStructureType: fpath = os.path.join(basepath, filename, filename + ".toml") with open(fpath, mode="r") as file: f = file.read() - obj: Dict[str, Any] = tomllib.loads(f) # type:ignore + obj: Dict[str, Any] = tomllib.loads(f) # type: ignore assert DIALOGUE_NAME_KEY in obj assert DIALOGUE_RESOURCES_KEY in obj - for i, resource in enumerate(obj[DIALOGUE_RESOURCES_KEY]): + resource_dict: Dict[str, Resource] = {} + for resource in obj[DIALOGUE_RESOURCES_KEY]: assert "name" in resource assert "type" in resource - obj[DIALOGUE_RESOURCES_KEY][i] = _RESOURCE_TYPES[resource["type"]](**resource) + # Create instances of Resource classes (and its subclasses) + resource_dict[resource["name"]] = _RESOURCE_TYPES[resource["type"]](**resource) + obj[DIALOGUE_RESOURCES_KEY] = resource_dict return cast(DialogueStructureType, obj) @@ -296,11 +268,12 @@ def __init__( self._start_qtype: str = start_dialogue_qtype self._q: Query = query self._result: Result = result - print("CALLBACKS:", self._result.get("callbacks")) self._resources: Dict[str, Resource] = {} self._saved_state: DialogueStructureType = self._get_saved_dialogue_state() self._data: Dict[str, Any] = {} - # TODO: CALL STACK! + self._answering_functions: AnsweringFunctionMap = {} + self._answer: Optional[str] = None + self._error: bool = False # TODO: Delegate answering from a resource to another resource or to another dialogue # TODO: í ávaxtasamtali "ég vil panta flug" "viltu að ég geymi ávaxtapöntunina eða eyði henni?" ... @@ -311,18 +284,19 @@ def not_in_dialogue(self) -> bool: and self._saved_state.get(DIALOGUE_NAME_KEY) != self._dialogue_name ) - def setup_dialogue(self) -> None: + def setup_dialogue( + self, + answering_functions: AnsweringFunctionMap, + ) -> None: obj = _load_dialogue_structure(self._dialogue_name) - for i, resource in enumerate(obj[DIALOGUE_RESOURCES_KEY]): - if self._saved_state and i < len(self._saved_state[DIALOGUE_RESOURCES_KEY]): - resource.update(self._saved_state[DIALOGUE_RESOURCES_KEY][i]) - resource.state = ResourceState( - self._saved_state[DIALOGUE_RESOURCES_KEY][i].state - ) - self._resources[resource.name] = resource - - self.resourceState: Optional[Resource] = None - self.ans: Optional[str] = None + for rname, resource in obj[DIALOGUE_RESOURCES_KEY].items(): + if rname in self._saved_state[DIALOGUE_RESOURCES_KEY]: + # Update empty resource with serialized data + resource.update(self._saved_state[DIALOGUE_RESOURCES_KEY][rname]) + # Change from int to enum type + resource.state = ResourceState(resource.state) + self._resources[rname] = resource + self._answering_functions = answering_functions def start_dialogue(self): """Save client's state as having started this dialogue""" @@ -330,7 +304,7 @@ def start_dialogue(self): self._set_dialogue_state( { DIALOGUE_NAME_KEY: self._dialogue_name, - DIALOGUE_RESOURCES_KEY: [], + DIALOGUE_RESOURCES_KEY: {}, } ) @@ -340,7 +314,7 @@ def update_dialogue_state(self): self._set_dialogue_state( { DIALOGUE_NAME_KEY: self._dialogue_name, - DIALOGUE_RESOURCES_KEY: list(self._resources.values()), + DIALOGUE_RESOURCES_KEY: self._resources, } ) @@ -350,52 +324,70 @@ def get_resource(self, name: str) -> Resource: def get_result(self) -> Result: return self._result - def get_answer(self) -> str: + def get_answer(self) -> Optional[str]: # Executing callbacks - self._execute_callbacks() + cbs: Optional[List[CallbackTupleType]] = self._result.get("callbacks") + curr_resource = self._resources[FINAL_RESOURCE_NAME] + if cbs: + self._execute_callbacks_postorder(curr_resource, cbs) - answer_key: Optional[Tuple[str, str]] = self._result.get("answer_key") - print("BLAAAA, answer key", answer_key) - if answer_key: - # Quick way of setting response (instead of using a callback) - self._resources[answer_key[0]].set_answer(answer_key[1]) - print("USED ANSWER KEY", answer_key) + if self._error: + # An error was raised somewhere during the callbacks + return None + + # Check if dialogue was cancelled + if curr_resource.is_cancelled: + self._answer = self._answering_functions[FINAL_RESOURCE_NAME]( + curr_resource, self + ) + if not self._answer: + raise ValueError("No answer for cancelled dialogue") + return self._answer + + # Iterate through resources (inorder traversal) + # until one generates an answer + self._answer = self._get_answer_postorder(curr_resource) - ans = self._resources[FINAL_RESOURCE_NAME].get_answer(self) - print("GOT ANSWER:", ans) if self._resources[FINAL_RESOURCE_NAME].is_confirmed: # Final callback (performing some operation with the dialogue's data) # should be called before ending dialogue self.end_dialogue() else: self.update_dialogue_state() - if ans is None: - ans = self._resources[FINAL_RESOURCE_NAME].prompts["final"] - return ans - - def _execute_callbacks(self) -> None: - cbs: Optional[List[CallbackTupleType]] = self._result.get("callbacks") - print("CBS:", cbs) - if cbs: - for filter_func, cb in cbs: - for resource in self._resources.values(): - if filter_func(resource): - cb(resource, self._result) + assert self._answer is not None, "No answer generated :(" + return self._answer + + def _get_answer_postorder(self, curr_resource: Resource) -> Optional[str]: + for rname in curr_resource.requires: + ans = self._get_answer_postorder(self._resources[rname]) + if ans: + return ans + return self._answering_functions[curr_resource.name](curr_resource, self) + + def _execute_callbacks_postorder( + self, curr_resource: Resource, cbs: List[CallbackTupleType] + ) -> None: + for rname in curr_resource.requires: + self._execute_callbacks_postorder(self._resources[rname], cbs) + + for filter_func, cb in cbs: + if filter_func(curr_resource): + cb(curr_resource, self, self._result) def _get_saved_dialogue_state(self) -> DialogueStructureType: """Load the dialogue state for a client""" cd = self._q.client_data(DIALOGUE_KEY) # Return empty DialogueStructureType in case no dialogue state exists - ds: DialogueStructureType = { + dialogue_struct: DialogueStructureType = { DIALOGUE_NAME_KEY: "", - DIALOGUE_RESOURCES_KEY: [], + DIALOGUE_RESOURCES_KEY: {}, } if cd: ds_str = cd.get(DIALOGUE_DATA_KEY) if isinstance(ds_str, str) and ds_str != EMPTY_DIALOGUE_DATA: # TODO: Add try-except block - ds = json.loads(ds_str, cls=DialogueJSONDecoder) - return ds + dialogue_struct = json.loads(ds_str, cls=DialogueJSONDecoder) + return dialogue_struct def _set_dialogue_state(self, ds: DialogueStructureType) -> None: """Save the state of a dialogue for a client""" @@ -410,6 +402,9 @@ def end_dialogue(self) -> None: # TODO: Remove line from database? self._q.set_client_data(DIALOGUE_KEY, {DIALOGUE_DATA_KEY: EMPTY_DIALOGUE_DATA}) + def set_error(self) -> None: + self._error = True + class DialogueJSONEncoder(json.JSONEncoder): def default(self, o: Any) -> Any: diff --git a/queries/fruitseller/fruitseller.toml b/queries/fruitseller/fruitseller.toml index e9a95dc7..4eb95b3f 100644 --- a/queries/fruitseller/fruitseller.toml +++ b/queries/fruitseller/fruitseller.toml @@ -3,7 +3,7 @@ dialogue_name = "fruitseller" [[resources]] name = "Fruits" type = "ListResource" -# TODO: Keep singular and plural forms of fruits (or options more generally) for formatting answer +# TODO: Keep singular and plural forms of fruits (or options more generally) for formatting answer? prompts.initial = "Hvaða ávexti má bjóða þér?" prompts.options = "Ávextirnir sem eru í boði eru appelsínur, bananar, epli og perur." prompts.empty = "Karfan er núna tóm. Hvaða ávexti má bjóða þér?" diff --git a/queries/fruitseller_module.py b/queries/fruitseller_module.py index 3d073fcd..e570572a 100644 --- a/queries/fruitseller_module.py +++ b/queries/fruitseller_module.py @@ -8,7 +8,10 @@ from reynir import NounPhrase from queries import gen_answer, parse_num, natlang_seq, sing_or_plur from queries.dialogue import ( + AnsweringFunctionMap, DatetimeResource, + FinalResource, + ListResource, Resource, ResourceState, DialogueStateManager, @@ -31,9 +34,13 @@ QFruitStartQuery | QFruitQuery | QFruitDateQuery + | QFruitInfoQuery + +QFruitInfoQuery → + "hver"? "er"? "staðan" "á"? "ávaxtapöntuninni"? QFruitStartQuery → - "ávöxtur" + "ávöxtur" | "postur" | "póstur" | "ég" "vill" "kaupa"? "ávexti" | "ég" "vil" "kaupa"? "ávexti" | "mig" "langar" "að" "kaupa" "ávexti" "hjá"? "þér"? @@ -107,9 +114,9 @@ QFruit → 'banani' | 'epli' | 'pera' | 'appelsína' -QYes → "já" "já"* | "já" "takk" | "játakk" | "já" "þakka" "þér" "fyrir" | "já" "takk" "kærlega" "fyrir"? | "jább" "takk"? +QYes → "já" "já"* | "endilega" | "já" "takk" | "játakk" | "já" "þakka" "þér" "fyrir" | "já" "takk" "kærlega" "fyrir"? | "jább" "takk"? -QNo → "nei" "takk"? | "nei" "nei"* | "neitakk" +QNo → "nei" "takk"? | "nei" "nei"* | "neitakk" | "ómögulega" QCancelOrder → "ég" "hætti" "við" | "ég" "vil" "hætta" "við" "pöntunina" @@ -135,45 +142,43 @@ _START_DIALOGUE_QTYPE = "QFruitStartQuery" _DIALOGUE_NAME = "fruitseller" -def _generate_fruit_answer(resource: Resource, dsm: DialogueStateManager) -> Optional[str]: - ans: Optional[str] = None - if dsm.get_result()["fruitsEmpty"]: - ans = resource.prompts["empty"] - elif dsm.get_result()["fruitOptions"]: - ans = resource.prompts["options"] - if resource.state is ResourceState.CONFIRMED: - return None - if resource.state is ResourceState.UNFULFILLED: - ans = resource.prompts["initial"] - elif resource.state is ResourceState.PARTIALLY_FULFILLED: - ans = ( - f"{resource.prompts['repeat'].format(list_items = _list_items(resource.data))}" - ) - elif resource.state is ResourceState.FULFILLED: - ans = ( - f"{resource.prompts['confirm'].format(list_items = _list_items(resource.data))}" - ) - return ans -def _generate_date_answer(resource: DatetimeResource, dsm: DialogueStateManager) -> Optional[str]: +def _generate_fruit_answer( + resource: ListResource, dsm: DialogueStateManager +) -> Optional[str]: + result = dsm.get_result() + if result.get("fruitsEmpty"): + return resource.prompts["empty"] + if result.get("fruitOptions"): + return resource.prompts["options"] + if resource.is_unfulfilled: + return resource.prompts["initial"] + if resource.is_partially_fulfilled: + return f"{resource.prompts['repeat'].format(list_items = _list_items(resource.data))}" + if resource.is_fulfilled: + return f"{resource.prompts['confirm'].format(list_items = _list_items(resource.data))}" + return None + + +def _generate_date_answer( + resource: DatetimeResource, dsm: DialogueStateManager +) -> Optional[str]: ans: Optional[str] = None - if resource.state is ResourceState.CONFIRMED: - return None - - if resource.state is ResourceState.UNFULFILLED: - ans = resource.prompts["initial"] + if resource.is_unfulfilled: + return resource.prompts["initial"] - elif resource.state is ResourceState.PARTIALLY_FULFILLED: + if resource.is_partially_fulfilled: if resource.has_date(): ans = resource.prompts["date_fulfilled"].format( - date = resource.data[0].strftime("%Y/%m/%d") + date=resource.data[0].strftime("%Y/%m/%d") ) if resource.has_time() and resource.prompts["time_fulfilled"]: ans = resource.prompts["time_fulfilled"].format( - time = resource.data[1].strftime("%H:%M") + time=resource.data[1].strftime("%H:%M") ) + return ans - elif resource.state is ResourceState.FULFILLED: + if resource.is_fulfilled: if resource.has_date() and resource.has_time(): ans = resource.prompts["confirm"].format( date_time=datetime.datetime.combine( @@ -183,21 +188,26 @@ def _generate_date_answer(resource: DatetimeResource, dsm: DialogueStateManager) ) return ans -def _generate_final_answer(resource: Resource, dsm: DialogueStateManager) -> Optional[str]: + +def _generate_final_answer( + resource: FinalResource, dsm: DialogueStateManager +) -> Optional[str]: ans: Optional[str] = None - if resource.state is ResourceState.CONFIRMED: - date_resource = dsm.get_resource("Date") - ans = resource.prompts["final"].format( - fruits = _list_items(dsm.get_resource("Fruits").data), - date_time = datetime.datetime.combine( - cast(datetime.date, date_resource.data[0]), - cast(datetime.time, date_resource.data[1]), - ).strftime("%Y/%m/%d %H:%M") - ) - elif resource.state is ResourceState.CANCELLED: - ans = resource.prompts["cancelled"] + if resource.is_cancelled: + return resource.prompts["cancelled"] + + resource.state = ResourceState.CONFIRMED + date_resource = dsm.get_resource("Date") + ans = resource.prompts["final"].format( + fruits=_list_items(dsm.get_resource("Fruits").data), + date_time=datetime.datetime.combine( + cast(datetime.date, date_resource.data[0]), + cast(datetime.time, date_resource.data[1]), + ).strftime("%Y/%m/%d %H:%M"), + ) return ans + def _list_items(items: Any) -> str: item_list: List[str] = [] for num, name in items: @@ -213,7 +223,9 @@ def QFruitStartQuery(node: Node, params: QueryStateDict, result: Result): def QAddFruitQuery(node: Node, params: QueryStateDict, result: Result): - def _add_fruit(resource: Resource, result: Result) -> None: + def _add_fruit( + resource: Resource, dsm: DialogueStateManager, result: Result + ) -> None: if resource.data is None: resource.data = [] for number, name in result.queryfruits: @@ -231,7 +243,9 @@ def _add_fruit(resource: Resource, result: Result) -> None: def QRemoveFruitQuery(node: Node, params: QueryStateDict, result: Result): - def _remove_fruit(resource: Resource, result: Result) -> None: + def _remove_fruit( + resource: Resource, dsm: DialogueStateManager, result: Result + ) -> None: if resource.data is not None: for _, fruitname in result.queryfruits: for number, name in resource.data: @@ -242,6 +256,7 @@ def _remove_fruit(resource: Resource, result: Result) -> None: resource.state = ResourceState.UNFULFILLED resource.set_answer("empty") result.fruitsEmpty = True + print("CALLBACK FRUTISEMPYT", "fruitsEmpty" in result) else: resource.state = ResourceState.PARTIALLY_FULFILLED resource.set_answer("repeat", list_items=_list_items(resource.data)) @@ -254,8 +269,11 @@ def _remove_fruit(resource: Resource, result: Result) -> None: def QCancelOrder(node: Node, params: QueryStateDict, result: Result): - def _cancel_order(resource: Resource, result: Result) -> None: + def _cancel_order( + resource: Resource, dsm: DialogueStateManager, result: Result + ) -> None: resource.state = ResourceState.CANCELLED + result.qtype = "QCancelOrder" result.answer_key = ("Final", "cancelled") if "callbacks" not in result: @@ -271,14 +289,12 @@ def QFruitOptionsQuery(node: Node, params: QueryStateDict, result: Result): def QYes(node: Node, params: QueryStateDict, result: Result): - def _parse_yes(resource: Resource, result: Result) -> None: - if resource.name == "Fruits": - if resource.is_fulfilled: - resource.state = ResourceState.CONFIRMED - if resource.name == "Date": - if resource.is_fulfilled: - resource.state = ResourceState.CONFIRMED - result.answer_key = ("Final", "final") + def _parse_yes( + resource: Resource, dsm: DialogueStateManager, result: Result + ) -> None: + if "yes_used" not in result and resource.is_fulfilled: + resource.state = ResourceState.CONFIRMED + result.yes_used = True if "callbacks" not in result: result["callbacks"] = [] @@ -290,7 +306,9 @@ def _parse_yes(resource: Resource, result: Result) -> None: def QNo(node: Node, params: QueryStateDict, result: Result): - def _parse_no(resource: Resource, result: Result) -> None: + def _parse_no( + resource: Resource, dsm: DialogueStateManager, result: Result + ) -> None: if resource.name == "Fruits": if resource.is_partially_fulfilled: resource.state = ResourceState.FULFILLED @@ -350,16 +368,21 @@ def QFruitDateTime(node: Node, params: QueryStateDict, result: Result) -> None: result["delivery_time"] = datetime.time(h, min) result["delivery_date"] = datetime.date(y, m, d) - def _dt_callback(resource: DatetimeResource, result: Result) -> None: - resource.set_date(result["delivery_date"]) - resource.set_time(result["delivery_time"]) - resource.state = ResourceState.FULFILLED - resource.set_answer( - "confirm", - date_time=datetime.datetime.combine(resource.date, resource.time).strftime( - "%Y/%m/%d %H:%M" - ), - ) + def _dt_callback( + resource: DatetimeResource, dsm: DialogueStateManager, result: Result + ) -> None: + if dsm.get_resource("Fruits").is_confirmed: + resource.set_date(result["delivery_date"]) + resource.set_time(result["delivery_time"]) + resource.state = ResourceState.FULFILLED + resource.set_answer( + "confirm", + date_time=datetime.datetime.combine( + resource.date, resource.time + ).strftime("%Y/%m/%d %H:%M"), + ) + else: + dsm.set_error() if "callbacks" not in result: result["callbacks"] = [] @@ -385,21 +408,26 @@ def QFruitDate(node: Node, params: QueryStateDict, result: Result) -> None: y += 1 result["delivery_date"] = datetime.date(day=d, month=m, year=y) - def _dt_callback(resource: DatetimeResource, result: Result) -> None: - resource.set_date(result["delivery_date"]) - if resource.has_time(): - resource.state = ResourceState.FULFILLED - resource.set_answer( - "confirm", - date_time=datetime.datetime.combine( - resource.date, resource.time - ).strftime("%Y/%m/%d %H:%M"), - ) + def _dt_callback( + resource: DatetimeResource, dsm: DialogueStateManager, result: Result + ) -> None: + if dsm.get_resource("Fruits").is_confirmed: + resource.set_date(result["delivery_date"]) + if resource.has_time(): + resource.state = ResourceState.FULFILLED + resource.set_answer( + "confirm", + date_time=datetime.datetime.combine( + resource.date, resource.time + ).strftime("%Y/%m/%d %H:%M"), + ) + else: + resource.state = ResourceState.PARTIALLY_FULFILLED + resource.set_answer( + "date_fulfilled", date=resource.date.strftime("%Y/%m/%d") + ) else: - resource.state = ResourceState.PARTIALLY_FULFILLED - resource.set_answer( - "date_fulfilled", date=resource.date.strftime("%Y/%m/%d") - ) + dsm.set_error() if "callbacks" not in result: result["callbacks"] = [] @@ -419,21 +447,26 @@ def QFruitTime(node: Node, params: QueryStateDict, result: Result): result["delivery_time"] = datetime.time(hour, minute) - def _dt_callback(resource: DatetimeResource, result: Result) -> None: - resource.set_time(result["delivery_time"]) - if resource.has_date(): - resource.state = ResourceState.FULFILLED - resource.set_answer( - "confirm", - date_time=datetime.datetime.combine( - resource.date, resource.time - ).strftime("%Y/%m/%d %H:%M"), - ) + def _dt_callback( + resource: DatetimeResource, dsm: DialogueStateManager, result: Result + ) -> None: + if dsm.get_resource("Fruits").is_confirmed: + resource.set_time(result["delivery_time"]) + if resource.has_date(): + resource.state = ResourceState.FULFILLED + resource.set_answer( + "confirm", + date_time=datetime.datetime.combine( + resource.date, resource.time + ).strftime("%Y/%m/%d %H:%M"), + ) + else: + resource.state = ResourceState.PARTIALLY_FULFILLED + resource.set_answer( + "time_fulfilled", time=resource.time.strftime("%H:%M") + ) else: - resource.state = ResourceState.PARTIALLY_FULFILLED - resource.set_answer( - "time_fulfilled", time=resource.time.strftime("%H:%M") - ) + dsm.set_error() if "callbacks" not in result: result["callbacks"] = [] @@ -441,6 +474,20 @@ def _dt_callback(resource: DatetimeResource, result: Result) -> None: result.callbacks.append((filter_func, _dt_callback)) +def QFruitInfoQuery(node: Node, params: QueryStateDict, result: Result): + result.qtype = "QFruitInfo" + + +_ANSWERING_FUNCTIONS: AnsweringFunctionMap = cast( + AnsweringFunctionMap, + { + "Fruits": _generate_fruit_answer, + "Date": _generate_date_answer, + "Final": _generate_final_answer, + }, +) + + def sentence(state: QueryStateDict, result: Result) -> None: """Called when sentence processing is complete""" q: Query = state["query"] @@ -452,13 +499,23 @@ def sentence(state: QueryStateDict, result: Result) -> None: # Successfully matched a query type try: - dsm.setup_dialogue() + dsm.setup_dialogue(_ANSWERING_FUNCTIONS) if result.qtype == _START_DIALOGUE_QTYPE: dsm.start_dialogue() + elif result.qtype == "QFruitInfo": + # Example info handling functionality + ans = "Ávaxtapöntunin þín er bara flott. " + # f = dsm.get_resource("Fruits") + # ans += str(f.data) + ans += dsm.get_answer() or "" + q.set_answer(*gen_answer(ans)) + return ans = dsm.get_answer() if not ans: - raise ValueError("No answer generated!") + print("No answer generated") + q.set_error("E_QUERY_NOT_UNDERSTOOD") + return q.set_answer(*gen_answer(ans)) return From 084534339711b4a9f09988122572a454e6b9fc4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Tue, 28 Jun 2022 11:14:24 +0000 Subject: [PATCH 110/371] Added properties and functions to Date and Time resources --- queries/dialogue.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/queries/dialogue.py b/queries/dialogue.py index 8ec1173b..fd84cba0 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -162,10 +162,25 @@ def set_no(self): class DateResource(Resource): data: datetime.date = field(default_factory=datetime.date.today) + @property + def date(self) -> Optional[datetime.date]: + return self.data + + def set_date(self, new_date: datetime.date) -> None: + self.data = new_date + + @dataclass class TimeResource(Resource): data: datetime.time = field(default_factory=datetime.time) + @property + def time(self) -> Optional[datetime.time]: + return self.data + + def set_time(self, new_time: datetime.time) -> None: + self.data = new_time + @dataclass class DatetimeResource(Resource): From 44fb99b1f074209f594628b0d3511935aa7c24c7 Mon Sep 17 00:00:00 2001 From: Logi E Date: Tue, 28 Jun 2022 12:09:19 +0000 Subject: [PATCH 111/371] split Datetime into Date and Time --- queries/dialogue.py | 42 +++--- queries/fruitseller/fruitseller.toml | 18 ++- queries/fruitseller_module.py | 187 ++++++++++++--------------- 3 files changed, 117 insertions(+), 130 deletions(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index fd84cba0..2e80e365 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -122,9 +122,6 @@ def is_skipped(self) -> bool: def is_cancelled(self) -> bool: return self.state is ResourceState.CANCELLED - def next_action(self) -> Any: - raise NotImplementedError() - def update(self, new_data: Optional["Resource"]) -> None: if new_data: self.__dict__.update(new_data.__dict__) @@ -133,10 +130,6 @@ def update(self, new_data: Optional["Resource"]) -> None: @dataclass class ListResource(Resource): data: ListResourceType = field(default_factory=list) - available_options: Optional[ListResourceType] = None - - def list_available_options(self) -> str: - raise NotImplementedError() # TODO: @@ -164,7 +157,7 @@ class DateResource(Resource): @property def date(self) -> Optional[datetime.date]: - return self.data + return self.data if self.is_fulfilled else None def set_date(self, new_date: datetime.date) -> None: self.data = new_date @@ -176,7 +169,7 @@ class TimeResource(Resource): @property def time(self) -> Optional[datetime.time]: - return self.data + return self.data if self.is_fulfilled else None def set_time(self, new_time: datetime.time) -> None: self.data = new_time @@ -216,13 +209,12 @@ class NumberResource(Resource): @dataclass class OrResource(Resource): - data: Dict[str, Any] = field(default_factory=dict) exclusive: bool = False # Only one of the resources should be fulfilled @dataclass class AndResource(Resource): # For answering multiple resources at the same time - data: Dict[str, Any] = field(default_factory=dict) + pass @dataclass @@ -230,6 +222,17 @@ class FinalResource(Resource): data: Any = None +_RESOURCE_TYPES: Mapping[str, Any] = { + "Resource": Resource, + "ListResource": ListResource, + "YesNoResource": YesNoResource, + "DateResource": DateResource, + "TimeResource": TimeResource, + "DatetimeResource": DatetimeResource, + "NumberResource": NumberResource, + "FinalResource": FinalResource, +} + ############################## # RESOURCE CLASSES END # ############################## @@ -242,16 +245,6 @@ class DialogueStructureType(TypedDict): resources: Dict[str, Resource] -_RESOURCE_TYPES: Mapping[str, Any] = { - "Resource": Resource, - "ListResource": ListResource, - "YesNoResource": YesNoResource, - "DatetimeResource": DatetimeResource, - "NumberResource": NumberResource, - "FinalResource": FinalResource, -} - - def _load_dialogue_structure(filename: str) -> DialogueStructureType: """Loads dialogue structure from TOML file.""" basepath, _ = os.path.split(os.path.realpath(__file__)) @@ -264,7 +257,8 @@ def _load_dialogue_structure(filename: str) -> DialogueStructureType: resource_dict: Dict[str, Resource] = {} for resource in obj[DIALOGUE_RESOURCES_KEY]: assert "name" in resource - assert "type" in resource + if "type" not in resource: + resource["type"] = "Resource" # Create instances of Resource classes (and its subclasses) resource_dict[resource["name"]] = _RESOURCE_TYPES[resource["type"]](**resource) obj[DIALOGUE_RESOURCES_KEY] = resource_dict @@ -377,7 +371,9 @@ def _get_answer_postorder(self, curr_resource: Resource) -> Optional[str]: ans = self._get_answer_postorder(self._resources[rname]) if ans: return ans - return self._answering_functions[curr_resource.name](curr_resource, self) + if curr_resource.name in self._answering_functions: + return self._answering_functions[curr_resource.name](curr_resource, self) + return None def _execute_callbacks_postorder( self, curr_resource: Resource, cbs: List[CallbackTupleType] diff --git a/queries/fruitseller/fruitseller.toml b/queries/fruitseller/fruitseller.toml index 4eb95b3f..f566aa43 100644 --- a/queries/fruitseller/fruitseller.toml +++ b/queries/fruitseller/fruitseller.toml @@ -12,13 +12,25 @@ prompts.repeat = "Pöntunin samanstendur af {list_items}. Verður það eitthva [[resources]] name = "Date" -type = "DatetimeResource" -requires = ["Fruits"] +type = "DateResource" + +[[resources]] +name = "Time" +type = "TimeResource" + +[[resources]] +name = "DateTime" +requires = ["Date", "Time"] prompts.initial = "Hvenær viltu fá ávextina?" prompts.time_fulfilled = "Afhendingin verður klukkan {time}. Hvaða dagsetningu viltu fá ávextina?" prompts.date_fulfilled = "Afhendingin verður {date}. Klukkan hvað viltu fá ávextina?" prompts.confirm = "Afhending pöntunar er {date_time}. Viltu staðfesta afhendinguna?" +# [[resources]] +# name = "Date" +# type = "DatetimeResource" +# requires = ["Fruits"] + # [[resources]] # name = "ConfirmOrder" # type = "YesNoResource" @@ -28,6 +40,6 @@ prompts.confirm = "Afhending pöntunar er {date_time}. Viltu staðfesta afhendin [[resources]] name = "Final" type = "FinalResource" -requires = ["Date"] +requires = ["Fruits", "DateTime"] prompts.final = "Pöntunin þín er {fruits} og verður afhent {date_time}." prompts.cancelled = "Móttekið, hætti við pöntunina." diff --git a/queries/fruitseller_module.py b/queries/fruitseller_module.py index e570572a..55bad727 100644 --- a/queries/fruitseller_module.py +++ b/queries/fruitseller_module.py @@ -9,12 +9,13 @@ from queries import gen_answer, parse_num, natlang_seq, sing_or_plur from queries.dialogue import ( AnsweringFunctionMap, - DatetimeResource, + DateResource, FinalResource, ListResource, Resource, ResourceState, DialogueStateManager, + TimeResource, ) # Indicate that this module wants to handle parse trees for queries, @@ -160,32 +161,34 @@ def _generate_fruit_answer( return None -def _generate_date_answer( - resource: DatetimeResource, dsm: DialogueStateManager +def _generate_datetime_answer( + resource: Resource, dsm: DialogueStateManager ) -> Optional[str]: ans: Optional[str] = None + date_resource: DateResource = cast(DateResource, dsm.get_resource("Date")) + time_resource: TimeResource = cast(TimeResource, dsm.get_resource("Time")) + if resource.is_unfulfilled: return resource.prompts["initial"] if resource.is_partially_fulfilled: - if resource.has_date(): + if date_resource.is_fulfilled: ans = resource.prompts["date_fulfilled"].format( - date=resource.data[0].strftime("%Y/%m/%d") + date=date_resource.data.strftime("%Y/%m/%d") ) - if resource.has_time() and resource.prompts["time_fulfilled"]: + if time_resource.is_fulfilled: ans = resource.prompts["time_fulfilled"].format( - time=resource.data[1].strftime("%H:%M") + time=time_resource.data.strftime("%H:%M") ) return ans if resource.is_fulfilled: - if resource.has_date() and resource.has_time(): - ans = resource.prompts["confirm"].format( - date_time=datetime.datetime.combine( - cast(datetime.date, resource.data[0]), - cast(datetime.time, resource.data[1]), - ).strftime("%Y/%m/%d %H:%M") - ) + ans = resource.prompts["confirm"].format( + date_time=datetime.datetime.combine( + date_resource.data, + time_resource.data, + ).strftime("%Y/%m/%d %H:%M") + ) return ans @@ -198,11 +201,12 @@ def _generate_final_answer( resource.state = ResourceState.CONFIRMED date_resource = dsm.get_resource("Date") + time_resource = dsm.get_resource("Time") ans = resource.prompts["final"].format( fruits=_list_items(dsm.get_resource("Fruits").data), date_time=datetime.datetime.combine( - cast(datetime.date, date_resource.data[0]), - cast(datetime.time, date_resource.data[1]), + date_resource.data, + time_resource.data, ).strftime("%Y/%m/%d %H:%M"), ) return ans @@ -219,7 +223,6 @@ def _list_items(items: Any) -> str: def QFruitStartQuery(node: Node, params: QueryStateDict, result: Result): result.qtype = _START_DIALOGUE_QTYPE - result.answer_key = ("Fruits", "initial") def QAddFruitQuery(node: Node, params: QueryStateDict, result: Result): @@ -232,7 +235,6 @@ def _add_fruit( resource.data.append((number, name)) resource.state = ResourceState.PARTIALLY_FULFILLED print("INSIDE ADD FRUITS CALLBACK", resource.name) - resource.set_answer("repeat", list_items=_list_items(resource.data)) print("ANSWER IS:", resource._answer) if "callbacks" not in result: @@ -295,11 +297,14 @@ def _parse_yes( if "yes_used" not in result and resource.is_fulfilled: resource.state = ResourceState.CONFIRMED result.yes_used = True + if resource.name == "DateTime": + for rname in resource.requires: + dsm.get_resource(rname).state = ResourceState.CONFIRMED if "callbacks" not in result: result["callbacks"] = [] filter_func: Callable[[Resource], bool] = ( - lambda r: r.name in ("Fruits", "Date") and not r.is_confirmed + lambda r: r.name in ("Fruits", "DateTime") and not r.is_confirmed ) result.callbacks.append((filter_func, _parse_yes)) result.qtype = "QYes" @@ -320,7 +325,7 @@ def _parse_no( if "callbacks" not in result: result["callbacks"] = [] filter_func: Callable[[Resource], bool] = ( - lambda r: r.name in ("Fruits", "Date") and not r.is_confirmed + lambda r: r.name == "Fruits" and not r.is_confirmed ) result.callbacks.append((filter_func, _parse_no)) result.qtype = "QNo" @@ -349,45 +354,20 @@ def QFruit(node: Node, params: QueryStateDict, result: Result): result.fruit = fruit -def QFruitDateTime(node: Node, params: QueryStateDict, result: Result) -> None: - result.qtype = "bull" - datetimenode = node.first_child(lambda n: True) - assert isinstance(datetimenode, TerminalNode) - now = datetime.datetime.now() - y, m, d, h, min, _ = (i if i != 0 else None for i in json.loads(datetimenode.aux)) - if y is None: - y = now.year - if m is None: - m = now.month - if d is None: - d = now.day - if h is None: - h = 12 - if min is None: - min = 0 - result["delivery_time"] = datetime.time(h, min) - result["delivery_date"] = datetime.date(y, m, d) - - def _dt_callback( - resource: DatetimeResource, dsm: DialogueStateManager, result: Result - ) -> None: - if dsm.get_resource("Fruits").is_confirmed: - resource.set_date(result["delivery_date"]) - resource.set_time(result["delivery_time"]) - resource.state = ResourceState.FULFILLED - resource.set_answer( - "confirm", - date_time=datetime.datetime.combine( - resource.date, resource.time - ).strftime("%Y/%m/%d %H:%M"), - ) +def _date_callback( + resource: DateResource, dsm: DialogueStateManager, result: Result +) -> None: + if dsm.get_resource("Fruits").is_confirmed: + resource.set_date(result["delivery_date"]) + resource.state = ResourceState.FULFILLED + time_resource = dsm.get_resource("Time") + datetime_resource = dsm.get_resource("DateTime") + if time_resource.is_fulfilled: + datetime_resource.state = ResourceState.FULFILLED else: - dsm.set_error() - - if "callbacks" not in result: - result["callbacks"] = [] - filter_func: Callable[[Resource], bool] = lambda r: r.name == "Date" - result.callbacks.append((filter_func, _dt_callback)) + datetime_resource.state = ResourceState.PARTIALLY_FULFILLED + else: + dsm.set_error() def QFruitDate(node: Node, params: QueryStateDict, result: Result) -> None: @@ -408,35 +388,30 @@ def QFruitDate(node: Node, params: QueryStateDict, result: Result) -> None: y += 1 result["delivery_date"] = datetime.date(day=d, month=m, year=y) - def _dt_callback( - resource: DatetimeResource, dsm: DialogueStateManager, result: Result - ) -> None: - if dsm.get_resource("Fruits").is_confirmed: - resource.set_date(result["delivery_date"]) - if resource.has_time(): - resource.state = ResourceState.FULFILLED - resource.set_answer( - "confirm", - date_time=datetime.datetime.combine( - resource.date, resource.time - ).strftime("%Y/%m/%d %H:%M"), - ) - else: - resource.state = ResourceState.PARTIALLY_FULFILLED - resource.set_answer( - "date_fulfilled", date=resource.date.strftime("%Y/%m/%d") - ) - else: - dsm.set_error() - if "callbacks" not in result: result["callbacks"] = [] filter_func: Callable[[Resource], bool] = lambda r: r.name == "Date" - result.callbacks.append((filter_func, _dt_callback)) + result.callbacks.append((filter_func, _date_callback)) return raise ValueError("No date in {0}".format(str(datenode))) +def _time_callback( + resource: TimeResource, dsm: DialogueStateManager, result: Result +) -> None: + if dsm.get_resource("Fruits").is_confirmed: + resource.set_time(result["delivery_time"]) + resource.state = ResourceState.FULFILLED + date_resource = dsm.get_resource("Date") + datetime_resource = dsm.get_resource("DateTime") + if date_resource.is_fulfilled: + datetime_resource.state = ResourceState.FULFILLED + else: + datetime_resource.state = ResourceState.PARTIALLY_FULFILLED + else: + dsm.set_error() + + def QFruitTime(node: Node, params: QueryStateDict, result: Result): result.qtype = "bull" # Extract time from time terminal nodes @@ -447,31 +422,35 @@ def QFruitTime(node: Node, params: QueryStateDict, result: Result): result["delivery_time"] = datetime.time(hour, minute) - def _dt_callback( - resource: DatetimeResource, dsm: DialogueStateManager, result: Result - ) -> None: - if dsm.get_resource("Fruits").is_confirmed: - resource.set_time(result["delivery_time"]) - if resource.has_date(): - resource.state = ResourceState.FULFILLED - resource.set_answer( - "confirm", - date_time=datetime.datetime.combine( - resource.date, resource.time - ).strftime("%Y/%m/%d %H:%M"), - ) - else: - resource.state = ResourceState.PARTIALLY_FULFILLED - resource.set_answer( - "time_fulfilled", time=resource.time.strftime("%H:%M") - ) - else: - dsm.set_error() - if "callbacks" not in result: result["callbacks"] = [] - filter_func: Callable[[Resource], bool] = lambda r: r.name == "Date" - result.callbacks.append((filter_func, _dt_callback)) + filter_func: Callable[[Resource], bool] = lambda r: r.name == "Time" + result.callbacks.append((filter_func, _time_callback)) + + +def QFruitDateTime(node: Node, params: QueryStateDict, result: Result) -> None: + result.qtype = "bull" + datetimenode = node.first_child(lambda n: True) + assert isinstance(datetimenode, TerminalNode) + now = datetime.datetime.now() + y, m, d, h, min, _ = (i if i != 0 else None for i in json.loads(datetimenode.aux)) + if y is None: + y = now.year + if m is None: + m = now.month + if d is None: + d = now.day + if h is None: + h = 12 + if min is None: + min = 0 + result["delivery_time"] = datetime.time(h, min) + result["delivery_date"] = datetime.date(y, m, d) + + if "callbacks" not in result: + result["callbacks"] = [] + result.callbacks.append((lambda r: r.name == "Date", _date_callback)) + result.callbacks.append((lambda r: r.name == "Time", _time_callback)) def QFruitInfoQuery(node: Node, params: QueryStateDict, result: Result): @@ -482,7 +461,7 @@ def QFruitInfoQuery(node: Node, params: QueryStateDict, result: Result): AnsweringFunctionMap, { "Fruits": _generate_fruit_answer, - "Date": _generate_date_answer, + "DateTime": _generate_datetime_answer, "Final": _generate_final_answer, }, ) From 17913c6ade37efb40b37424060ce7aa1af3b1429 Mon Sep 17 00:00:00 2001 From: Logi E Date: Tue, 28 Jun 2022 13:09:47 +0000 Subject: [PATCH 112/371] added extra response for no matched fruit when removing --- queries/dialogue.py | 22 +--------------------- queries/fruitseller_module.py | 17 +++++++++++------ 2 files changed, 12 insertions(+), 27 deletions(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index 2e80e365..f321fe26 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -132,7 +132,7 @@ class ListResource(Resource): data: ListResourceType = field(default_factory=list) -# TODO: +# TODO: ? # ExactlyOneResource (choose one resource from options) # SetResource (a set of resources)? # ... @@ -181,26 +181,6 @@ class DatetimeResource(Resource): default_factory=lambda: [None, None] ) - @property - def date(self) -> Optional[datetime.date]: - return self.data[0] - - @property - def time(self) -> Optional[datetime.time]: - return self.data[1] - - def has_date(self) -> bool: - return len(self.data) > 0 and isinstance(self.data[0], datetime.date) - - def has_time(self) -> bool: - return len(self.data) > 1 and isinstance(self.data[1], datetime.time) - - def set_date(self, new_date: Optional[datetime.date] = None) -> None: - self.data[0] = new_date - - def set_time(self, new_time: Optional[datetime.time] = None) -> None: - self.data[1] = new_time - @dataclass class NumberResource(Resource): diff --git a/queries/fruitseller_module.py b/queries/fruitseller_module.py index 55bad727..26462e60 100644 --- a/queries/fruitseller_module.py +++ b/queries/fruitseller_module.py @@ -155,7 +155,14 @@ def _generate_fruit_answer( if resource.is_unfulfilled: return resource.prompts["initial"] if resource.is_partially_fulfilled: - return f"{resource.prompts['repeat'].format(list_items = _list_items(resource.data))}" + ans: str = "" + if "actually_removed_something" in result: + if not result["actually_removed_something"]: + ans += "Ég fann ekki ávöxtinn sem þú vildir fjarlægja. " + return ( + ans + + f"{resource.prompts['repeat'].format(list_items = _list_items(resource.data))}" + ) if resource.is_fulfilled: return f"{resource.prompts['confirm'].format(list_items = _list_items(resource.data))}" return None @@ -232,10 +239,9 @@ def _add_fruit( if resource.data is None: resource.data = [] for number, name in result.queryfruits: + # TODO: check whether we have duplicate fruits resource.data.append((number, name)) resource.state = ResourceState.PARTIALLY_FULFILLED - print("INSIDE ADD FRUITS CALLBACK", resource.name) - print("ANSWER IS:", resource._answer) if "callbacks" not in result: result["callbacks"] = [] @@ -248,20 +254,19 @@ def QRemoveFruitQuery(node: Node, params: QueryStateDict, result: Result): def _remove_fruit( resource: Resource, dsm: DialogueStateManager, result: Result ) -> None: + result.actually_removed_something = False if resource.data is not None: for _, fruitname in result.queryfruits: for number, name in resource.data: if name == fruitname: resource.data.remove([number, name]) + result.actually_removed_something = True break if len(resource.data) == 0: resource.state = ResourceState.UNFULFILLED - resource.set_answer("empty") result.fruitsEmpty = True - print("CALLBACK FRUTISEMPYT", "fruitsEmpty" in result) else: resource.state = ResourceState.PARTIALLY_FULFILLED - resource.set_answer("repeat", list_items=_list_items(resource.data)) if "callbacks" not in result: result["callbacks"] = [] From a7865f6270530e06452d1cd233864f502e78b507 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Tue, 28 Jun 2022 13:44:14 +0000 Subject: [PATCH 113/371] Added theater.toml with resource prompts and requirements --- queries/theater/theater.toml | 39 ++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 queries/theater/theater.toml diff --git a/queries/theater/theater.toml b/queries/theater/theater.toml new file mode 100644 index 00000000..d3ff284b --- /dev/null +++ b/queries/theater/theater.toml @@ -0,0 +1,39 @@ +dialogue_name = "theater" + +[[resources]] +name = "Show" +type = "ListResource" +prompts.initial = "Hvaða sýningu má bjóða þér að fara á?" +prompts.options = "Sýningarnar sem eru í boði eru: {options}" +prompts.confirm = "Þú valdir sýninguna {show}, viltu halda áfram með pöntunina?" + +[[resources]] +name = "ShowDate" +type = "ListResource" +requires = "Show" +prompts.initial = "Hvenær viltu fara á sýninguna {show}?" +prompts.options = "Sýningardagsetningar eru: {options}" +prompts.confirm = "Þú valdir dagsetninguna {date}, viltu halda áfram og velja fjölda sæta?" + +[[resources]] +name = "ShowSeats" +type = "NumberResource" +requires = "ShowDate" +prompts.initial = "Hversu mörg sæti viltu bóka á sýninguna {show} þann {date}?" +prompts.confirm = "Þú valdir {seats} sæti, viltu halda áfram og velja staðsetningu sætanna?" + +[[resources]] +name = "SeatLocation" +type = "ListResource" +requires = "ShowSeats" +prompts.initial = "{seats} sæti eru í boði á svæðum {seat_location}. Á hvaða svæði viltu fá sæti?" +prompts.options = "Svæðin með {seats} laus sæti eru: {seat_location}." +prompts.confirm = "Þú valdir svæðið {seat_location}, viltu halda áfram?" + +[[resources]] +name = "Final" +type = "FinalResource" +requires = "SeatLocation" +prompts.final = "Þú valdir {seats} sæti á svæðinum {seat_location} fyrir sýninguna {show} þann {date}." + +# TODO: Add a resource for the payment method \ No newline at end of file From 31b65d1db8295036c2077531771436cfe7bafd36 Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Tue, 28 Jun 2022 13:59:09 +0000 Subject: [PATCH 114/371] sonos db dict merge --- queries/iot_connect.py | 8 ++++++-- query.py | 29 +++++++++++++++-------------- routes/api.py | 5 ++++- util.py | 12 ++++++++++++ 4 files changed, 37 insertions(+), 17 deletions(-) diff --git a/queries/iot_connect.py b/queries/iot_connect.py index 6c8ddaaf..e071de12 100644 --- a/queries/iot_connect.py +++ b/queries/iot_connect.py @@ -176,7 +176,11 @@ def sentence(state: QueryStateDict, result: Result) -> None: return elif result.qtype == "create_speaker_token": - code = str(q.client_data("sonos_code")) + device_data = q.client_data("iot_speakers") + code = device_data.get("sonos").get("credentials").get("code") + if device_data is None or code is None: + q.set_error("E_NO_DEVICE_DATA") + return sonos_encoded_credentials = read_api_key("SonosEncodedCredentials") response = create_token(code, sonos_encoded_credentials, host) if response.status_code != 200: @@ -241,7 +245,7 @@ def create_sonos_cred_dict(access_token, refresh_token, q): def store_sonos_data_and_credentials(data_dict, cred_dict, q): sonos_dict = {} sonos_dict["sonos"] = {"credentials": cred_dict, "data": data_dict} - q.update_client_data("iot_speakers", sonos_dict) + q.set_client_data("iot_speakers", sonos_dict, update_in_place=True) def create_grouplist_for_db(groups): diff --git a/query.py b/query.py index 126b6783..5e2f3920 100755 --- a/query.py +++ b/query.py @@ -52,6 +52,7 @@ import re import random from collections import defaultdict +from copy import deepcopy from settings import Settings @@ -80,6 +81,8 @@ from processor import modules_in_dir from geo import LatLonTuple +from util import merge_two_dicts + # Query response ResponseDict = Dict[str, Any] ResponseMapping = Mapping[str, Any] @@ -885,26 +888,21 @@ def client_data(self, key: str) -> Optional[ClientDataDict]: ) return None - def set_client_data(self, key: str, data: ClientDataDict) -> None: + def set_client_data( + self, key: str, data: ClientDataDict, update_in_place=False + ) -> None: """Setter for client query data""" if not self.client_id or not key: logging.warning("Couldn't save query data, no client ID or key") return - Query.store_query_data(self.client_id, key, data) - - def update_client_data(self, key: str, new_data: ClientDataDict) -> None: - print("new_data :", new_data) - stored_data = self.client_data(key) - print("stored_data before update:", stored_data) - if stored_data is None: - self.set_client_data(key, new_data) - return - stored_data.update(new_data) - print("stored_data :", stored_data) - self.set_client_data(key, stored_data) + Query.store_query_data( + self.client_id, key, data, update_in_place=update_in_place + ) @staticmethod - def store_query_data(client_id: str, key: str, data: ClientDataDict) -> bool: + def store_query_data( + client_id: str, key: str, data: ClientDataDict, update_in_place=False + ) -> bool: """Save client query data in the database, under the given key""" if not client_id or not key: return False @@ -927,6 +925,9 @@ def store_query_data(client_id: str, key: str, data: ClientDataDict) -> bool: ) session.add(row) else: + if update_in_place: + stored_data = deepcopy(row.data) + data = merge_two_dicts(stored_data, data) # Already present: update row.data = data # type: ignore row.modified = now # type: ignore diff --git a/routes/api.py b/routes/api.py index 4ecc75a3..29f853f5 100755 --- a/routes/api.py +++ b/routes/api.py @@ -725,8 +725,11 @@ def sonos_code(version: int = 1) -> Response: args = request.args client_id = args.get("state") code = args.get("code") + code = {"sonos": {"credentials": {"code": code}}} if client_id and code: - success = QueryObject.store_query_data(client_id, "sonos_code", code) + success = QueryObject.store_query_data( + client_id, "iot_speakers", code, update_in_place=True + ) if success: return better_jsonify(valid=True, msg="Registered sonos code") diff --git a/util.py b/util.py index 7786b54f..115eff08 100755 --- a/util.py +++ b/util.py @@ -73,3 +73,15 @@ def icelandic_asciify(text: str) -> str: t = t.encode("ascii", "ignore").decode() return t + + +def merge_two_dicts(dict_a, dict_b): + for key in dict_b: + if key in dict_a: + if isinstance(dict_a[key], dict) and isinstance(dict_b[key], dict): + merge_two_dicts(dict_a[key], dict_b[key]) + else: + dict_a[key] = dict_b[key] + else: + dict_a[key] = dict_b[key] + return dict_a From 43bb661606c1bc4fd02f0ff07f94470b7b111051 Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Tue, 28 Jun 2022 14:18:50 +0000 Subject: [PATCH 115/371] new docstring for register query api --- routes/api.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/routes/api.py b/routes/api.py index 29f853f5..e2f9806c 100755 --- a/routes/api.py +++ b/routes/api.py @@ -636,21 +636,21 @@ def register_query_data_api(version: int = 1) -> Response: """ Stores or updates query data for the given client ID - Hinrik's comment: - Data format example from js code - { - 'device_id': device_id, - 'key': 'smartlights', - 'data': { - 'smartlights': { - 'selected_light': 'philips_hue', - 'philips_hue': { - 'username': username, - 'ipAddress': internalipaddress - } - } - } - } + Jóhann's comment: + Data format example for IoT device from js code: + + 'client_id': clientID, + 'key': "iot_lights", + 'data': { + 'philips_hue': { + 'credentials': { + 'username': username, + 'ip_address': ip address, + }, + 'data': { + 'groups': [{group1}, {group2}] + }, + }; """ From 69fa0c44fd8030afe5abfd13f0b6115b8bb4aa11 Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Tue, 28 Jun 2022 14:29:22 +0000 Subject: [PATCH 116/371] added sonos class --- queries/iot_speakers.py | 157 ++++++++++++++++++++++------------------ queries/sonos.py | 34 ++++++++- 2 files changed, 115 insertions(+), 76 deletions(-) diff --git a/queries/iot_speakers.py b/queries/iot_speakers.py index 79b4fc9e..56f00e8c 100644 --- a/queries/iot_speakers.py +++ b/queries/iot_speakers.py @@ -43,6 +43,7 @@ from query import Query, QueryStateDict, AnswerTuple from queries import gen_answer, read_jsfile, read_grammar_file +from queries.sonos import SonosClient from tree import Result, Node @@ -91,14 +92,15 @@ def help_text(lemma: str) -> str: QIoTSpeaker → QIoTSpeakerQuery -# QIoTSpeakerQuery -> -# QIoTSpeakerMakeVerb QIoTSpeakerMakeRest -# | QIoTSpeakerSetVerb QIoTSpeakerSetRest -# | QIoTSpeakerChangeVerb QIoTSpeakerChangeRest -# | QIoTSpeakerLetVerb QIoTSpeakerLetRest -# | QIoTSpeakerTurnOnVerb QIoTSpeakerTurnOnRest -# | QIoTSpeakerTurnOffVerb QIoTSpeakerTurnOffRest -# | QIoTSpeakerIncreaseOrDecreaseVerb QIoTSpeakerIncreaseOrDecreaseRest +QIoTSpeakerQuery -> + QIoTSpeakerMakeVerb QIoTSpeakerMakeRest + | QIoTSpeakerSetVerb QIoTSpeakerSetRest + # | QIoTSpeakerChangeVerb QIoTSpeakerChangeRest + | QIoTSpeakerLetVerb QIoTSpeakerLetRest + | QIoTSpeakerTurnOnVerb QIoTSpeakerTurnOnRest + | QIoTSpeakerTurnOffVerb QIoTSpeakerTurnOffRest + | QIoTSpeakerPlayVerb QIoTSpeakerPlayRest + | QIoTSpeakerIncreaseOrDecreaseVerb QIoTSpeakerIncreaseOrDecreaseRest QIoTSpeakerMakeVerb -> 'gera:so'_bh @@ -119,6 +121,10 @@ def help_text(lemma: str) -> str: QIoTSpeakerTurnOffVerb -> 'slökkva:so'_bh +QIoTSpeakerPlayVerb -> + 'spila:so'_bh + | "spilaðu" + QIoTSpeakerIncreaseOrDecreaseVerb -> QIoTSpeakerIncreaseVerb | QIoTSpeakerDecreaseVerb @@ -131,7 +137,7 @@ def help_text(lemma: str) -> str: 'lækka:so'_bh | 'minnka:so'_bh -# QCHANGEMakeRest -> +QIoTSpeakerMakeRest -> # QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigMake # | QCHANGESubject/þf QCHANGEHvernigMake QCHANGEHvar? # | QCHANGEHvar? QCHANGESubject/þf QCHANGEHvernigMake @@ -141,7 +147,7 @@ def help_text(lemma: str) -> str: QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? # TODO: Add support for "stilltu rauðan lit á ljósið í eldhúsinu" -QCHANGESetRest -> +QIoTSpeakerSetRest -> # QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigSet # | QCHANGESubject/þf QCHANGEHvernigSet QCHANGEHvar? # | QCHANGEHvar? QCHANGESubject/þf QCHANGEHvernigSet @@ -150,7 +156,7 @@ def help_text(lemma: str) -> str: # | QCHANGEHvernigSet QCHANGEHvar? QCHANGESubject/þf "á" QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? -QCHANGEChangeRest -> +# QIoTSpeakerChangeRest -> # QCHANGESubjectOne/þgf QCHANGEHvar? QCHANGEHvernigChange # | QCHANGESubjectOne/þgf QCHANGEHvernigChange QCHANGEHvar? # | QCHANGEHvar? QCHANGESubjectOne/þgf QCHANGEHvernigChange @@ -158,16 +164,17 @@ def help_text(lemma: str) -> str: # | QCHANGEHvernigChange QCHANGESubjectOne/þgf QCHANGEHvar? # | QCHANGEHvernigChange QCHANGEHvar? QCHANGESubjectOne/þgf -QCHANGELetRest -> - QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigLet - | QCHANGESubject/þf QCHANGEHvernigLet QCHANGEHvar? - | QCHANGEHvar? QCHANGESubject/þf QCHANGEHvernigLet - | QCHANGEHvar? QCHANGEHvernigLet QCHANGESubject/þf - | QCHANGEHvernigLet QCHANGESubject/þf QCHANGEHvar? - | QCHANGEHvernigLet QCHANGEHvar? QCHANGESubject/þf +QIoTSpeakerLetRest -> + # QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigLet + # | QCHANGESubject/þf QCHANGEHvernigLet QCHANGEHvar? + # | QCHANGEHvar? QCHANGESubject/þf QCHANGEHvernigLet + # | QCHANGEHvar? QCHANGEHvernigLet QCHANGESubject/þf + # | QCHANGEHvernigLet QCHANGESubject/þf QCHANGEHvar? + # | QCHANGEHvernigLet QCHANGEHvar? QCHANGESubject/þf "vera" QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? + | "á" QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? -QCHANGETurnOnRest -> +QIoTSpeakerTurnOnRest -> # QCHANGETurnOnLightsRest # | QCHANGEAHverju QCHANGEHvar? # | QCHANGEHvar? QCHANGEAHverju @@ -178,7 +185,7 @@ def help_text(lemma: str) -> str: # | QCHANGEHvar QCHANGELightSubject/þf? # Would be good to add "slökktu á rauða litnum" functionality -QCHANGETurnOffRest -> +QIoTSpeakerTurnOffRest -> # QCHANGETurnOffLightsRest "á" QIoTSpeakerMusicWord/þgf QIoTSpeakerHvar? @@ -186,8 +193,12 @@ def help_text(lemma: str) -> str: # QCHANGELightSubject/þf QCHANGEHvar? # | QCHANGEHvar QCHANGELightSubject/þf? +QIoTSpeakerPlayRest -> + QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? + | "tónlist" + # TODO: Make the subject categorization cleaner -QCHANGEIncreaseOrDecreaseRest -> +QIoTSpeakerIncreaseOrDecreaseRest -> # QCHANGELightSubject/þf QCHANGEHvar? # | QCHANGEBrightnessSubject/þf QCHANGEHvar? QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? @@ -197,8 +208,8 @@ def help_text(lemma: str) -> str: # QCHANGESubjectOne/fall # | QCHANGESubjectTwo/fall -QIoTMusicWord -> - 'tónlist'/fall +QIoTSpeakerMusicWord/fall -> + 'tónlist:no'/fall # # TODO: Decide whether LightSubject/þgf should be accepted # QCHANGESubjectOne/fall -> @@ -425,34 +436,41 @@ def QIoTSpeakerGroupName(node: Node, params: QueryStateDict, result: Result) -> result["group_name"] = result._indefinite +def QIoTMusicWord(node: Node, params: QueryStateDict, result: Result) -> None: + result.target = "music" + print("music") + + def sentence(state: QueryStateDict, result: Result) -> None: + # try: + print("sentence") """Called when sentence processing is complete""" q: Query = state["query"] - q.set_qtype(result.get["qtype"]) + q.set_qtype(result.get("qtype")) + + # TODO: Find way to only catch playing commands + if result.get("action") == None: + result.action = "play_music" - smartdevice_type = "smartSpeaker" + smartdevice_type = "smart_speaker" # Fetch relevant data from the device_data table to perform an action on the lights - sonos_code = q.client_data("sonos_code") - device_data = q.client_data("sonos_credentials").json() + # sonos_code = q.client_data("sonos_code") + device_data = q.client_data("iot_speakers") + print(device_data) # TODO: Need to add check for if there are no registered devices to an account, probably when initilazing the querydata if device_data is not None: + print("if clause") try: - access_token = device_data.get("access_token") - refresh_token = device_data.get("refresh_token") - except: - answer = "Mig vantar Sonos-tóka til að framkvæma þessa aðgerð." - try: - household_id = device_data.get("household_id") - group_id = device_data.get("group_id") - player_id = device_data.get("player_id") - except: - answer = "Mig vantar auðkenni Sonos-tækjanna þinna." - else: - answer = "Mig vantar upplýsingar um Sonos-tækin þín." - + access_token = device_data["sonos"]["credentials"]["access_token"] + refresh_token = device_data["sonos"]["credentials"]["refresh_token"] + household_id = device_data["sonos"]["data"]["households"][0]["id"] + group_id = device_data["sonos"]["data"]["groups"][0]["Family Room"] + player_id = device_data["sonos"]["data"]["players"][0]["Family Room"] + except KeyError: + print("No device data found for this account") # Successfully fetched data from the device_data table print("access_token: " + access_token) @@ -460,37 +478,32 @@ def sentence(state: QueryStateDict, result: Result) -> None: print("household_id: " + household_id) print("group_id: " + group_id) print("player_id: " + player_id) - try: - # kalla í javascripts stuff - light_or_group_name = result.get("light_name", result.get("group_name", "")) - color_name = result.get("color_name", "") - print("GROUP NAME:", light_or_group_name) - print("COLOR NAME:", color_name) - print(result.hue_obj) - q.set_answer( - *gen_answer( - "ég var að kveikja ljósin! " - # + group_name - # + " " - # + color_name - # + " " - # + result.action - # + " " - # + str(result.hue_obj.get("hue", "enginn litur")) - ) - ) - js = ( - read_jsfile("IoT_Embla/fuse.js") - + f"var BRIDGE_IP = '{bridge_ip}';var USERNAME = '{username}';" - + read_jsfile("IoT_Embla/Philips_Hue/fuse_search.js") - + read_jsfile("IoT_Embla/Philips_Hue/lights.js") - + read_jsfile("IoT_Embla/Philips_Hue/set_lights.js") - ) - js += f"setLights('{light_or_group_name}', '{json.dumps(result.hue_obj)}');" - q.set_command(js) - except Exception as e: - logging.warning("Exception while processing random query: {0}".format(e)) - q.set_error("E_EXCEPTION: {0}".format(e)) - raise + + # Create a SonosClient object + sonos_client = SonosClient( + access_token=access_token, + refresh_token=refresh_token, + household_id=household_id, + group_id=group_id, + player_id=player_id, + ) + + # Perform the action on the Sonos device + if result.action == "play_music": + sonos_client.toggle_play_spause() + answer = "Ég kveikti á tónlist." + # elif result.action == "increase_volume": + # sonos_client.increase_volume() + # elif result.action == "decrease_volume": + # sonos_client.decrease_volume() + # elif result.action == "set_volume": + # sonos_client.set_volume(result.get["volume"]) + + answer_list = gen_answer(answer) + answer_list[1].replace("Sonos", "Sónos") + q.set_answer(*answer_list) # f"var BRIDGE_IP = '192.168.1.68';var USERNAME = 'p3obluiXT13IbHMpp4X63ZvZnpNRdbqqMt723gy2';" + # except Exception as e: + # print(e) + # print("Error in sentence") diff --git a/queries/sonos.py b/queries/sonos.py index 7be9869c..d9501e5a 100644 --- a/queries/sonos.py +++ b/queries/sonos.py @@ -24,7 +24,35 @@ import requests - +# TODO: Refresh token functionality +class SonosClient: + def __init__(self, access_token, refresh_token, household_id, group_id, player_id): + self.access_token = access_token + self.refresh_token = refresh_token + if (v := household_id) is not None: + self.household_id = v + else: + self.household_id = get_households(self.access_token).json()["households"][ + 0 + ]["id"] + if (v := group_id) is not None: + self.group_id = v + else: + self.group_id = get_groups(self.access_token, self.household_id).json()[ + "groups" + ][0]["id"] + if (v := player_id) is not None: + self.player_id = v + else: + self.player_id = get_players( + self.access_token, self.household_id, self.group_id + ).json()["players"][0]["id"] + + def toggle_play_pause(self): + toggle_play_pause(self.group_id, self.access_token) + + +# TODO: Check whether this should return the ids themselves instead of the json response def get_households(token): """ Returns the list of households of the user @@ -74,9 +102,7 @@ def toggle_play_pause(group_id, token): """ Toggles the play/pause of a group """ - url = ( - f"https://api.ws.sonos.com/control/api/v1/groups/{group_id}/playback/playPause" - ) + url = f"https://api.ws.sonos.com/control/api/v1/groups/{group_id}/playback/togglePlayPause" payload = {} headers = {"Content-Type": "application/json", "Authorization": f"Bearer {token}"} From cb72e4dc4152b23f43426a4b8e06f9ddb4e703a0 Mon Sep 17 00:00:00 2001 From: Logi E Date: Tue, 28 Jun 2022 14:31:55 +0000 Subject: [PATCH 117/371] starting work on theater dialogue module --- queries/dialogue.py | 4 +- queries/fruitseller_module.py | 1 + queries/theater/theater.toml | 8 +- queries/theater_module.py | 148 ++++++++++++++++++++++++++++++++++ 4 files changed, 155 insertions(+), 6 deletions(-) create mode 100644 queries/theater_module.py diff --git a/queries/dialogue.py b/queries/dialogue.py index f321fe26..e21ce569 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -18,7 +18,7 @@ from dataclasses import dataclass, field try: - import tomllib + import tomllib # type: ignore (module not available in Python <3.11) except ModuleNotFoundError: import tomli as tomllib @@ -228,6 +228,7 @@ class DialogueStructureType(TypedDict): def _load_dialogue_structure(filename: str) -> DialogueStructureType: """Loads dialogue structure from TOML file.""" basepath, _ = os.path.split(os.path.realpath(__file__)) + # TODO: Fix this, causes issues when folders have the same name as a module fpath = os.path.join(basepath, filename, filename + ".toml") with open(fpath, mode="r") as file: f = file.read() @@ -343,7 +344,6 @@ def get_answer(self) -> Optional[str]: self.end_dialogue() else: self.update_dialogue_state() - assert self._answer is not None, "No answer generated :(" return self._answer def _get_answer_postorder(self, curr_resource: Resource) -> Optional[str]: diff --git a/queries/fruitseller_module.py b/queries/fruitseller_module.py index 26462e60..8fa84e17 100644 --- a/queries/fruitseller_module.py +++ b/queries/fruitseller_module.py @@ -501,6 +501,7 @@ def sentence(state: QueryStateDict, result: Result) -> None: q.set_error("E_QUERY_NOT_UNDERSTOOD") return + q.set_qtype(result.qtype) q.set_answer(*gen_answer(ans)) return except Exception as e: diff --git a/queries/theater/theater.toml b/queries/theater/theater.toml index d3ff284b..4243faf4 100644 --- a/queries/theater/theater.toml +++ b/queries/theater/theater.toml @@ -10,7 +10,7 @@ prompts.confirm = "Þú valdir sýninguna {show}, viltu halda áfram með pöntu [[resources]] name = "ShowDate" type = "ListResource" -requires = "Show" +requires = ["Show"] prompts.initial = "Hvenær viltu fara á sýninguna {show}?" prompts.options = "Sýningardagsetningar eru: {options}" prompts.confirm = "Þú valdir dagsetninguna {date}, viltu halda áfram og velja fjölda sæta?" @@ -18,14 +18,14 @@ prompts.confirm = "Þú valdir dagsetninguna {date}, viltu halda áfram og velja [[resources]] name = "ShowSeats" type = "NumberResource" -requires = "ShowDate" +requires = ["ShowDate"] prompts.initial = "Hversu mörg sæti viltu bóka á sýninguna {show} þann {date}?" prompts.confirm = "Þú valdir {seats} sæti, viltu halda áfram og velja staðsetningu sætanna?" [[resources]] name = "SeatLocation" type = "ListResource" -requires = "ShowSeats" +requires = ["ShowSeats"] prompts.initial = "{seats} sæti eru í boði á svæðum {seat_location}. Á hvaða svæði viltu fá sæti?" prompts.options = "Svæðin með {seats} laus sæti eru: {seat_location}." prompts.confirm = "Þú valdir svæðið {seat_location}, viltu halda áfram?" @@ -33,7 +33,7 @@ prompts.confirm = "Þú valdir svæðið {seat_location}, viltu halda áfram?" [[resources]] name = "Final" type = "FinalResource" -requires = "SeatLocation" +requires = ["SeatLocation"] prompts.final = "Þú valdir {seats} sæti á svæðinum {seat_location} fyrir sýninguna {show} þann {date}." # TODO: Add a resource for the payment method \ No newline at end of file diff --git a/queries/theater_module.py b/queries/theater_module.py new file mode 100644 index 00000000..7743006d --- /dev/null +++ b/queries/theater_module.py @@ -0,0 +1,148 @@ +""" + + Greynir: Natural language processing for Icelandic + + Randomness query response module + + Copyright (C) 2022 Miðeind ehf. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see http://www.gnu.org/licenses/. + + This query module handles dialogue related to theater tickets. +""" + +from typing import Any, Optional +import logging +import random + +from query import Query, QueryStateDict +from tree import Result, Node +from queries import gen_answer, query_json_api +from queries.dialogue import DialogueStateManager, ListResource + +_THEATER_DIALOGUE_NAME = "theater" +_THEATER_QTYPE = "theater" +_START_DIALOGUE_QTYPE = "theater_start" + +TOPIC_LEMMAS = ["leikhús", "sýning"] + + +def help_text(lemma: str) -> str: + """Help text to return when query.py is unable to parse a query but + one of the above lemmas is found in it""" + return "Ég skil þig ef þú segir til dæmis: {0}.".format( + random.choice(("Hvaða sýningar eru í boði",)) + ) + + +# This module wants to handle parse trees for queries +HANDLE_TREE = True + +# The grammar nonterminals this module wants to handle +QUERY_NONTERMINALS = {"QTheater"} + +# The context-free grammar for the queries recognized by this plug-in module +GRAMMAR = """ + +Query → + QTheater + +QTheater → QTheaterQuery '?'? + +QTheaterQuery → + QTheaterHotWord | QTheaterDialogue + +QTheaterHotWord → + "leikhús" + | "þjóðleikhúsið" + | "þjóðleikhús" + | 'Þjóðleikhúsið' + | 'Þjóðleikhús' + | QTheaterEgVil? "kaupa" "leikhúsmiða" + | QTheaterEgVil? "fara" "í" "leikhús" + | QTheaterEgVil? "fara" "á" "leikhússýningu" + +QTheaterDialogue → "sýningar" + # TODO: Hvað er í boði, ég vil sýningu X, dagsetningu X, X mörg sæti, staðsetningu X + +QTheaterEgVil → + "ég"? "vil" + | "mig" "langar" "að" + +""" + + +def _generate_show_answer( + resource: ListResource, dsm: DialogueStateManager +) -> Optional[str]: + # if resource.is_unfulfilled: + # return resource.prompts["initial"] + return resource.prompts["options"].format(options=", ".join(dsm.get_result().shows)) + return None + + +def QTheaterDialogue(node: Node, params: QueryStateDict, result: Result) -> None: + result.qtype = _THEATER_QTYPE + + +def QTheaterHotWord(node: Node, params: QueryStateDict, result: Result) -> None: + result.qtype = _START_DIALOGUE_QTYPE + + +SHOW_URL = "https://leikhusid.is/wp-json/shows/v1/categories/938" + + +def _fetch_shows() -> Any: + resp = query_json_api(SHOW_URL) + if resp: + assert isinstance(resp, list) + return [s["title"] for s in resp] + + +_ANSWERING_FUNCTIONS = { + "Show": _generate_show_answer, +} + + +def sentence(state: QueryStateDict, result: Result) -> None: + """Called when sentence processing is complete""" + q: Query = state["query"] + dsm: DialogueStateManager = DialogueStateManager( + _THEATER_DIALOGUE_NAME, _START_DIALOGUE_QTYPE, q, result + ) + if dsm.not_in_dialogue(): + q.set_error("E_QUERY_NOT_UNDERSTOOD") + return + + try: + print("A") + result.shows = _fetch_shows() + dsm.setup_dialogue(_ANSWERING_FUNCTIONS) + if result.qtype == _START_DIALOGUE_QTYPE: + print("B") + dsm.start_dialogue() + print("C") + print(dsm._resources) + ans = dsm.get_answer() + print("D") + if not ans: + print("No answer generated") + q.set_error("E_QUERY_NOT_UNDERSTOOD") + return + + q.set_qtype(result.qtype) + q.set_answer(*gen_answer(ans)) + except Exception as e: + logging.warning("Exception while processing random query: {0}".format(e)) + q.set_error("E_EXCEPTION: {0}".format(e)) + raise From 44bf48442acb9d840385c6c6670d0ee915c22945 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Tue, 28 Jun 2022 15:09:36 +0000 Subject: [PATCH 118/371] Added generate answer functions for all theater resources --- queries/theater/theater.toml | 1 + queries/theater_module.py | 56 +++++++++++++++++++++++++++++++++++- 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/queries/theater/theater.toml b/queries/theater/theater.toml index 4243faf4..23130451 100644 --- a/queries/theater/theater.toml +++ b/queries/theater/theater.toml @@ -35,5 +35,6 @@ name = "Final" type = "FinalResource" requires = ["SeatLocation"] prompts.final = "Þú valdir {seats} sæti á svæðinum {seat_location} fyrir sýninguna {show} þann {date}." +prompts.cancelled = "Móttekið, hætti við leikhús pöntunina." # TODO: Add a resource for the payment method \ No newline at end of file diff --git a/queries/theater_module.py b/queries/theater_module.py index 7743006d..3895002a 100644 --- a/queries/theater_module.py +++ b/queries/theater_module.py @@ -28,7 +28,7 @@ from query import Query, QueryStateDict from tree import Result, Node from queries import gen_answer, query_json_api -from queries.dialogue import DialogueStateManager, ListResource +from queries.dialogue import DialogueStateManager, ListResource, ResourceState _THEATER_DIALOGUE_NAME = "theater" _THEATER_QTYPE = "theater" @@ -90,6 +90,56 @@ def _generate_show_answer( return resource.prompts["options"].format(options=", ".join(dsm.get_result().shows)) return None +def _generate_date_answer( + resource: ListResource, dsm: DialogueStateManager +) -> Optional[str]: + result = dsm.get_result() + if result.get("dateOptions"): + return resource.prompts["options"] + if resource.is_unfulfilled: + return resource.prompts["initial"] + if resource.is_fulfilled: + return resource.prompts["confirm"].format(date = result.get("date")) + +def _generate_seat_answer( + resource: ListResource, dsm: DialogueStateManager +) -> Optional[str]: + result = dsm.get_result() + if resource.is_unfulfilled: + return resource.prompts["initial"] + if resource.is_fulfilled: + return resource.prompts["confirm"].format(seats = result.get("seats")) + +def _generate_location_answer( + resource: ListResource, dsm: DialogueStateManager +) -> Optional[str]: + result = dsm.get_result() + if result.get("locationOptions"): + return resource.prompts["options"] + if resource.is_unfulfilled: + return resource.prompts["initial"] + if resource.is_fulfilled: + return resource.prompts["confirm"].format(location = result.get("location")) + +def _generate_final_answer( + resource: ListResource, dsm: DialogueStateManager +) -> Optional[str]: + if resource.is_cancelled: + return resource.prompts["cancelled"] + + resource.state = ResourceState.CONFIRMED + seat_resource = dsm.get_resource("ShowSeats") + location_resource = dsm.get_resource("ShowLocation") + date_resource = dsm.get_resource("ShowDate") + show_resource = dsm.get_resource("Show") + ans = resource.prompts["final"].format( + seats = seat_resource.data, + location = location_resource.data[0], + show = show_resource.data[0], + date = date_resource.data[0], + ) + return ans + def QTheaterDialogue(node: Node, params: QueryStateDict, result: Result) -> None: result.qtype = _THEATER_QTYPE @@ -111,6 +161,10 @@ def _fetch_shows() -> Any: _ANSWERING_FUNCTIONS = { "Show": _generate_show_answer, + "ShowDate": _generate_date_answer, + "ShowSeats": _generate_seat_answer, + "SeatLocation": _generate_location_answer, + "Final": _generate_final_answer, } From d4d676950b2514294fa0bc1a3c9f0a7626bd5483 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Tue, 28 Jun 2022 16:15:58 +0000 Subject: [PATCH 119/371] Added grammar for theater convo --- queries/theater/theater.toml | 2 +- queries/theater_module.py | 85 ++++++++++++++++++++++++++++++++++-- 2 files changed, 82 insertions(+), 5 deletions(-) diff --git a/queries/theater/theater.toml b/queries/theater/theater.toml index 23130451..d09a18d3 100644 --- a/queries/theater/theater.toml +++ b/queries/theater/theater.toml @@ -19,7 +19,7 @@ prompts.confirm = "Þú valdir dagsetninguna {date}, viltu halda áfram og velja name = "ShowSeats" type = "NumberResource" requires = ["ShowDate"] -prompts.initial = "Hversu mörg sæti viltu bóka á sýninguna {show} þann {date}?" +prompts.initial = "Hversu mörg sæti viltu bóka?" prompts.confirm = "Þú valdir {seats} sæti, viltu halda áfram og velja staðsetningu sætanna?" [[resources]] diff --git a/queries/theater_module.py b/queries/theater_module.py index 3895002a..c15ea547 100644 --- a/queries/theater_module.py +++ b/queries/theater_module.py @@ -21,14 +21,14 @@ This query module handles dialogue related to theater tickets. """ -from typing import Any, Optional +from typing import Any, Callable, Optional import logging import random from query import Query, QueryStateDict from tree import Result, Node -from queries import gen_answer, query_json_api -from queries.dialogue import DialogueStateManager, ListResource, ResourceState +from queries import gen_answer, parse_num, query_json_api +from queries.dialogue import DialogueStateManager, ListResource, Resource, ResourceState _THEATER_DIALOGUE_NAME = "theater" _THEATER_QTYPE = "theater" @@ -72,13 +72,67 @@ def help_text(lemma: str) -> str: | QTheaterEgVil? "fara" "í" "leikhús" | QTheaterEgVil? "fara" "á" "leikhússýningu" -QTheaterDialogue → "sýningar" +QTheaterDialogue → + QTheaterShowQuery + | QTheaterShowDateQuery + | QTheaterShowSeatsQuery + | QTheaterShowLocationQuery + | QTheaterShowOptions + | QYes + | QNo + | QCancel # TODO: Hvað er í boði, ég vil sýningu X, dagsetningu X, X mörg sæti, staðsetningu X +QTheaterShowQuery → QTheaterEgVil? "velja" 'sýning' QTheaterShowName > QTheaterShowName + +QTheaterShowName → Nl + +QTheaterShowDateQuery → + "ég"? "vil"? "fara"? "á" 'sýning'? QTheaterShowDate + +QTheaterShowDate → + QTheaterDateTime | QTheaterDate + +QTheaterDateTime → + tímapunkturafs + +QTheaterDate → + dagsafs + | dagsföst + +QTheaterShowSeatsQuery → + "ég"? "vil"? "fá"? QNum "sæti"? + +QTheaterShowLocationQuery → + "ég"? "vil"? "sæti"? QNum til? QNum "í" "röð" QNum + | "bekkur" QNum "sæti" QNum "til"? QNum + +QTheaterShowOptions → "sýningar" + | "hvaða" "sýningar" "eru" "í" "boði" + | "hvað" "er" "í" "boði" + | "hverjir"? "eru"? "valmöguleikarnir" + | "hvert" "er" "úrvalið" + + QTheaterEgVil → "ég"? "vil" + | "ég" "vill" | "mig" "langar" "að" +QNum → + # to is a declinable number word ('tveir/tvo/tveim/tveggja') + # töl is an undeclinable number word ('sautján') + # tala is a number ('17') + to | töl | tala + +QYes → "já" "já"* | "endilega" | "já" "takk" | "játakk" | "já" "þakka" "þér" "fyrir" | "já" "takk" "kærlega" "fyrir"? | "jább" "takk"? + +QNo → "nei" "takk"? | "nei" "nei"* | "neitakk" | "ómögulega" + +QCancel → "ég" "hætti" "við" + | "ég" "vil" "hætta" "við" "pöntunina" + | "ég" "vill" "hætta" "við" "pöntunina" + """ @@ -148,6 +202,29 @@ def QTheaterDialogue(node: Node, params: QueryStateDict, result: Result) -> None def QTheaterHotWord(node: Node, params: QueryStateDict, result: Result) -> None: result.qtype = _START_DIALOGUE_QTYPE +def QTheaterShowName(node: Node, params: QueryStateDict, result: Result) -> None: + show_name = " ".join(result._nominative.split()[1:]) if result._nominative.startswith("sýning") else result._nominative + print("NL: ", show_name) + +def QNum(node: Node, params: QueryStateDict, result: Result): + fruitnumber = int(parse_num(node, result._nominative)) + if fruitnumber is not None: + result.fruitnumber = fruitnumber + else: + result.fruitnumber = 1 + +def QCancel(node: Node, params: QueryStateDict, result: Result): + def _cancel_order( + resource: Resource, dsm: DialogueStateManager, result: Result + ) -> None: + resource.state = ResourceState.CANCELLED + + result.qtype = "QCancel" + if "callbacks" not in result: + result["callbacks"] = [] + filter_func: Callable[[Resource], bool] = lambda r: r.name == "Final" + result.callbacks.append((filter_func, _cancel_order)) + SHOW_URL = "https://leikhusid.is/wp-json/shows/v1/categories/938" From 40171165a8804ca9bc2662ad9c34383500d84728 Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Tue, 28 Jun 2022 16:44:01 +0000 Subject: [PATCH 120/371] added refresh token and spotify api endpoint --- queries/sonos.py | 14 ++++++++++++++ routes/api.py | 14 ++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/queries/sonos.py b/queries/sonos.py index d9501e5a..5261b88a 100644 --- a/queries/sonos.py +++ b/queries/sonos.py @@ -98,6 +98,20 @@ def create_token(code, sonos_encoded_credentials, host): return response +def refresh_token(sonos_encoded_credentials, refresh_token): + """ + Refreshes token + """ + url = f"https://api.sonos.com/login/v3/oauth/access?grant_type=refresh_token&refresh_token={refresh_token}" + + payload = {} + headers = {"Authorization": f"Basic {sonos_encoded_credentials}"} + + response = requests.request("POST", url, headers=headers, data=payload) + + return response + + def toggle_play_pause(group_id, token): """ Toggles the play/pause of a group diff --git a/routes/api.py b/routes/api.py index 4ecc75a3..244c223e 100755 --- a/routes/api.py +++ b/routes/api.py @@ -731,3 +731,17 @@ def sonos_code(version: int = 1) -> Response: return better_jsonify(valid=True, msg="Registered sonos code") return better_jsonify(valid=False, errmsg="Error registering sonos code.") + +@routes.route("/connect_spotify.api", methods=["GET"]) +@routes.route("/connect_spotify.api/v", methods=["GET", "POST"]) +def spotify_code(version: int = 1) -> Response: + print("spotify code") + args = request.args + client_id = args.get("state") + code = args.get("code") + if client_id and code: + success = QueryObject.store_query_data(client_id, "sonos_code", code) + if success: + return better_jsonify(valid=True, msg="Registered sonos code") + + return better_jsonify(valid=False, errmsg="Error registering sonos code.") \ No newline at end of file From a3eafde441b831d0ddbd9deb2bedff4d93182798 Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Tue, 28 Jun 2022 16:45:01 +0000 Subject: [PATCH 121/371] refresh-token1 --- queries/iot_connect.py | 11 +++++++++-- queries/iot_speakers.py | 5 +++++ query.py | 3 +++ routes/api.py | 16 ++++++++++++++++ 4 files changed, 33 insertions(+), 2 deletions(-) diff --git a/queries/iot_connect.py b/queries/iot_connect.py index e071de12..7ee772c6 100644 --- a/queries/iot_connect.py +++ b/queries/iot_connect.py @@ -25,6 +25,7 @@ _BREAK_LENGTH = 5 # Seconds _BREAK_SSML = ''.format(_BREAK_LENGTH) +from sqlite3 import Timestamp from typing import Dict, Mapping, Optional, cast from typing_extensions import TypedDict @@ -33,6 +34,7 @@ import json import flask import requests +from datetime import datetime import time from query import Query, QueryStateDict, AnswerTuple @@ -193,8 +195,11 @@ def sentence(state: QueryStateDict, result: Result) -> None: response_json.get("access_token"), response_json.get("refresh_token"), ) + timestamp = str(datetime.now()) data_dict = create_sonos_data_dict(access_token, q) - cred_dict = create_sonos_cred_dict(access_token, refresh_token, q) + cred_dict = create_sonos_cred_dict(access_token, refresh_token, timestamp, q) + print("data dict for update", data_dict) + print("cred dict for update", data_dict) store_sonos_data_and_credentials(data_dict, cred_dict, q) answer = "Ég bjó til tóka frá Sónos" voice_answer = answer @@ -231,12 +236,13 @@ def create_sonos_data_dict(access_token, q): return data_dict -def create_sonos_cred_dict(access_token, refresh_token, q): +def create_sonos_cred_dict(access_token, refresh_token, timestamp, q): cred_dict = {} cred_dict.update( { "access_token": access_token, "refresh_token": refresh_token, + "timestamp": timestamp, } ) return cred_dict @@ -245,6 +251,7 @@ def create_sonos_cred_dict(access_token, refresh_token, q): def store_sonos_data_and_credentials(data_dict, cred_dict, q): sonos_dict = {} sonos_dict["sonos"] = {"credentials": cred_dict, "data": data_dict} + print("final dict for db :", sonos_dict) q.set_client_data("iot_speakers", sonos_dict, update_in_place=True) diff --git a/queries/iot_speakers.py b/queries/iot_speakers.py index 56f00e8c..ac6d9809 100644 --- a/queries/iot_speakers.py +++ b/queries/iot_speakers.py @@ -40,6 +40,7 @@ import random import json import flask +from datetime import datetime, timedelta from query import Query, QueryStateDict, AnswerTuple from queries import gen_answer, read_jsfile, read_grammar_file @@ -462,6 +463,10 @@ def sentence(state: QueryStateDict, result: Result) -> None: # TODO: Need to add check for if there are no registered devices to an account, probably when initilazing the querydata if device_data is not None: + timestamp = device_data["sonos"]["credentials"]["timestamp"] + timestamp.datetime.strftime("%Y-%m-%d %H:%M:%S") + if (datetime.now() - datetime_date) > timedelta(hours=4): + print("It has been more than 4 seconds since the last update") print("if clause") try: access_token = device_data["sonos"]["credentials"]["access_token"] diff --git a/query.py b/query.py index 5e2f3920..91fbbf4a 100755 --- a/query.py +++ b/query.py @@ -926,8 +926,11 @@ def store_query_data( session.add(row) else: if update_in_place: + print("update in place") stored_data = deepcopy(row.data) + print("stored data: ", stored_data) data = merge_two_dicts(stored_data, data) + print("merged data :", data) # Already present: update row.data = data # type: ignore row.modified = now # type: ignore diff --git a/routes/api.py b/routes/api.py index e2f9806c..1dac1d9a 100755 --- a/routes/api.py +++ b/routes/api.py @@ -734,3 +734,19 @@ def sonos_code(version: int = 1) -> Response: return better_jsonify(valid=True, msg="Registered sonos code") return better_jsonify(valid=False, errmsg="Error registering sonos code.") + + +def sonos_code(version: int = 1) -> Response: + print("sonos code") + args = request.args + client_id = args.get("state") + code = args.get("code") + code = {"spotify": {"credentials": {"code": code}}} + if client_id and code: + success = QueryObject.store_query_data( + client_id, "iot_speakers", code, update_in_place=True + ) + if success: + return better_jsonify(valid=True, msg="Registered sonos code") + + return better_jsonify(valid=False, errmsg="Error registering sonos code.") From 9f8912f3a8b7d3a03b79e1cf6a83c352cb6d3abc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Tue, 28 Jun 2022 17:00:33 +0000 Subject: [PATCH 122/371] Added functionality to QTheaterShowName and QTheaterShowQuery --- queries/theater_module.py | 72 ++++++++++++++++++++++++++++++++------- 1 file changed, 60 insertions(+), 12 deletions(-) diff --git a/queries/theater_module.py b/queries/theater_module.py index c15ea547..15be018b 100644 --- a/queries/theater_module.py +++ b/queries/theater_module.py @@ -139,11 +139,18 @@ def help_text(lemma: str) -> str: def _generate_show_answer( resource: ListResource, dsm: DialogueStateManager ) -> Optional[str]: - # if resource.is_unfulfilled: - # return resource.prompts["initial"] - return resource.prompts["options"].format(options=", ".join(dsm.get_result().shows)) + result = dsm.get_result() + if result.get("showOptions"): + return resource.prompts["options"].format( + options=", ".join(dsm.get_result().shows) + ) + if resource.is_unfulfilled: + return resource.prompts["initial"] + if resource.is_fulfilled: + return resource.prompts["confirm"].format(show=resource.data[0]) return None + def _generate_date_answer( resource: ListResource, dsm: DialogueStateManager ) -> Optional[str]: @@ -153,7 +160,8 @@ def _generate_date_answer( if resource.is_unfulfilled: return resource.prompts["initial"] if resource.is_fulfilled: - return resource.prompts["confirm"].format(date = result.get("date")) + return resource.prompts["confirm"].format(date=result.get("date")) + def _generate_seat_answer( resource: ListResource, dsm: DialogueStateManager @@ -162,7 +170,8 @@ def _generate_seat_answer( if resource.is_unfulfilled: return resource.prompts["initial"] if resource.is_fulfilled: - return resource.prompts["confirm"].format(seats = result.get("seats")) + return resource.prompts["confirm"].format(seats=result.get("seats")) + def _generate_location_answer( resource: ListResource, dsm: DialogueStateManager @@ -173,7 +182,8 @@ def _generate_location_answer( if resource.is_unfulfilled: return resource.prompts["initial"] if resource.is_fulfilled: - return resource.prompts["confirm"].format(location = result.get("location")) + return resource.prompts["confirm"].format(location=result.get("location")) + def _generate_final_answer( resource: ListResource, dsm: DialogueStateManager @@ -187,10 +197,10 @@ def _generate_final_answer( date_resource = dsm.get_resource("ShowDate") show_resource = dsm.get_resource("Show") ans = resource.prompts["final"].format( - seats = seat_resource.data, - location = location_resource.data[0], - show = show_resource.data[0], - date = date_resource.data[0], + seats=seat_resource.data, + location=location_resource.data[0], + show=show_resource.data[0], + date=date_resource.data[0], ) return ans @@ -202,9 +212,46 @@ def QTheaterDialogue(node: Node, params: QueryStateDict, result: Result) -> None def QTheaterHotWord(node: Node, params: QueryStateDict, result: Result) -> None: result.qtype = _START_DIALOGUE_QTYPE + +def QTheaterShowQuery(node: Node, params: QueryStateDict, result: Result) -> None: + def _add_show( + resource: Resource, dsm: DialogueStateManager, result: Result + ) -> None: + resource.data = [dsm.get_result().show_name] + resource.state = ResourceState.FULFILLED + print("Show resource data: ", resource.data) + + if "callbacks" not in result: + result["callbacks"] = [] + filter_func: Callable[[Resource], bool] = lambda r: r.name == "Show" + result.callbacks.append((filter_func, _add_show)) + + +def QTheaterShowDateQuery(node: Node, params: QueryStateDict, result: Result) -> None: + pass + + +def QTheaterShowSeatsQuery(node: Node, params: QueryStateDict, result: Result) -> None: + pass + + +def QTheaterShowLocationQuery( + node: Node, params: QueryStateDict, result: Result +) -> None: + pass + + +def QTheaterShowOptions(node: Node, params: QueryStateDict, result: Result) -> None: + result.showOptions = True + + def QTheaterShowName(node: Node, params: QueryStateDict, result: Result) -> None: - show_name = " ".join(result._nominative.split()[1:]) if result._nominative.startswith("sýning") else result._nominative - print("NL: ", show_name) + result.show_name = ( + " ".join(result._text.split()[1:]) + if result._text.startswith("sýning") + else result._text + ) + def QNum(node: Node, params: QueryStateDict, result: Result): fruitnumber = int(parse_num(node, result._nominative)) @@ -213,6 +260,7 @@ def QNum(node: Node, params: QueryStateDict, result: Result): else: result.fruitnumber = 1 + def QCancel(node: Node, params: QueryStateDict, result: Result): def _cancel_order( resource: Resource, dsm: DialogueStateManager, result: Result From 15efada9f7985a0c23e999bc2bca31cbb031ddc2 Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Wed, 29 Jun 2022 10:07:50 +0000 Subject: [PATCH 123/371] refresh token for sonos working --- queries/iot_speakers.py | 12 +++++++----- queries/sonos.py | 19 +++++++++++++++++++ 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/queries/iot_speakers.py b/queries/iot_speakers.py index ac6d9809..eff52bb3 100644 --- a/queries/iot_speakers.py +++ b/queries/iot_speakers.py @@ -33,6 +33,7 @@ # TODO: No specified location # TODO: Fix scene issues +from os import access from typing import Dict, Mapping, Optional, cast from typing_extensions import TypedDict @@ -44,7 +45,8 @@ from query import Query, QueryStateDict, AnswerTuple from queries import gen_answer, read_jsfile, read_grammar_file -from queries.sonos import SonosClient +from queries.sonos import SonosClient, update_sonos_token +from util import read_api_key from tree import Result, Node @@ -463,10 +465,10 @@ def sentence(state: QueryStateDict, result: Result) -> None: # TODO: Need to add check for if there are no registered devices to an account, probably when initilazing the querydata if device_data is not None: - timestamp = device_data["sonos"]["credentials"]["timestamp"] - timestamp.datetime.strftime("%Y-%m-%d %H:%M:%S") - if (datetime.now() - datetime_date) > timedelta(hours=4): - print("It has been more than 4 seconds since the last update") + timestamp = device_data["sonos"]["credentials"]["timestamp"] + timestamp = datetime.strptime(timestamp, "%Y-%m-%d %H:%M:%S.%f") + if (datetime.now() - timestamp) > timedelta(hours=24): + update_sonos_token(q, device_data) print("if clause") try: access_token = device_data["sonos"]["credentials"]["access_token"] diff --git a/queries/sonos.py b/queries/sonos.py index 5261b88a..4a70ec5e 100644 --- a/queries/sonos.py +++ b/queries/sonos.py @@ -23,6 +23,8 @@ """ import requests +from datetime import datetime, timedelta +from util import read_api_key # TODO: Refresh token functionality class SonosClient: @@ -151,3 +153,20 @@ def audio_clip(audioclip_url, player_id, token): } response = requests.request("POST", url, headers=headers, data=payload) + + +def update_sonos_token(q, device_data): + print("update sonos token") + sonos_encoded_credentials = read_api_key("SonosEncodedCredentials") + refresh_token_str = device_data["sonos"]["credentials"]["refresh_token"] + access_token = refresh_token(sonos_encoded_credentials, refresh_token_str).json() + access_token = access_token["access_token"] + sonos_dict = { + "sonos": { + "credentials": { + "access_token": access_token, + "timestamp": str(datetime.now()), + } + } + } + q.set_client_data("iot_speakers", sonos_dict, update_in_place=True) From 3e436be43a0d3bf7a67a8728586c6d36bbebdd20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Wed, 29 Jun 2022 10:26:00 +0000 Subject: [PATCH 124/371] Adding more fruits that are already in the order now get added to the existing fruits --- queries/fruitseller_module.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/queries/fruitseller_module.py b/queries/fruitseller_module.py index 8fa84e17..40412fc4 100644 --- a/queries/fruitseller_module.py +++ b/queries/fruitseller_module.py @@ -238,9 +238,18 @@ def _add_fruit( ) -> None: if resource.data is None: resource.data = [] - for number, name in result.queryfruits: - # TODO: check whether we have duplicate fruits - resource.data.append((number, name)) + query_fruit_index = 0 + while query_fruit_index < len(result.queryfruits): + (number, name) = result.queryfruits[query_fruit_index] + added = False + for index, (fruit_number, fruit_name) in enumerate(resource.data): + if fruit_name == name: + resource.data[index] = (number + fruit_number, name) + added = True + break + if not added: + resource.data.append((number, name)) + query_fruit_index += 1 resource.state = ResourceState.PARTIALLY_FULFILLED if "callbacks" not in result: From 8a9bd1f94a7d6a667df25e5c1cabf6b521f0171c Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Wed, 29 Jun 2022 12:46:24 +0000 Subject: [PATCH 125/371] grammar issues --- queries/iot_hue.py | 6 +++--- queries/iot_speakers.py | 28 ++++++++++++++++------------ queries/sonos.py | 34 ++++++++++++++++------------------ routes/api.py | 14 -------------- 4 files changed, 35 insertions(+), 47 deletions(-) diff --git a/queries/iot_hue.py b/queries/iot_hue.py index 507eed34..75ba7e5b 100755 --- a/queries/iot_hue.py +++ b/queries/iot_hue.py @@ -189,9 +189,9 @@ def help_text(lemma: str) -> str: QIoTSubject/þf QIoTHvar? QIoTHvernigLet | QIoTSubject/þf QIoTHvernigLet QIoTHvar? | QIoTHvar? QIoTSubject/þf QIoTHvernigLet - | QIoTHvar? QIoTHvernigLet QIoTSubject/þf - | QIoTHvernigLet QIoTSubject/þf QIoTHvar? - | QIoTHvernigLet QIoTHvar? QIoTSubject/þf + # | QIoTHvar? QIoTHvernigLet QIoTSubject/þf + # | QIoTHvernigLet QIoTSubject/þf QIoTHvar? + # | QIoTHvernigLet QIoTHvar? QIoTSubject/þf QIoTTurnOnRest -> QIoTTurnOnLightsRest diff --git a/queries/iot_speakers.py b/queries/iot_speakers.py index 56f00e8c..f1a53d60 100644 --- a/queries/iot_speakers.py +++ b/queries/iot_speakers.py @@ -144,7 +144,7 @@ def help_text(lemma: str) -> str: # | QCHANGEHvar? QCHANGEHvernigMake QCHANGESubject/þf # | QCHANGEHvernigMake QCHANGESubject/þf QCHANGEHvar? # | QCHANGEHvernigMake QCHANGEHvar? QCHANGESubject/þf - QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? + QIoTSpeakerMusicWord QIoTSpeakerHvar? # TODO: Add support for "stilltu rauðan lit á ljósið í eldhúsinu" QIoTSpeakerSetRest -> @@ -154,7 +154,7 @@ def help_text(lemma: str) -> str: # | QCHANGEHvar? QCHANGEHvernigSet QCHANGESubject/þf # | QCHANGEHvernigSet QCHANGESubject/þf QCHANGEHvar? # | QCHANGEHvernigSet QCHANGEHvar? QCHANGESubject/þf - "á" QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? + "á" QIoTSpeakerMusicWord QIoTSpeakerHvar? # QIoTSpeakerChangeRest -> # QCHANGESubjectOne/þgf QCHANGEHvar? QCHANGEHvernigChange @@ -171,14 +171,14 @@ def help_text(lemma: str) -> str: # | QCHANGEHvar? QCHANGEHvernigLet QCHANGESubject/þf # | QCHANGEHvernigLet QCHANGESubject/þf QCHANGEHvar? # | QCHANGEHvernigLet QCHANGEHvar? QCHANGESubject/þf - "vera" QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? - | "á" QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? + 'Vera' QIoTSpeakerMusicWord QIoTSpeakerHvar? + | "á" QIoTSpeakerMusicWord QIoTSpeakerHvar? QIoTSpeakerTurnOnRest -> # QCHANGETurnOnLightsRest # | QCHANGEAHverju QCHANGEHvar? # | QCHANGEHvar? QCHANGEAHverju - "á" QIoTSpeakerMusicWord/þgf QIoTSpeakerHvar? + "á" QIoTSpeakerMusicWord QIoTSpeakerHvar? # QCHANGETurnOnLightsRest -> # QCHANGELightSubject/þf QCHANGEHvar? @@ -187,29 +187,31 @@ def help_text(lemma: str) -> str: # Would be good to add "slökktu á rauða litnum" functionality QIoTSpeakerTurnOffRest -> # QCHANGETurnOffLightsRest - "á" QIoTSpeakerMusicWord/þgf QIoTSpeakerHvar? + "á" QIoTSpeakerMusicWord QIoTSpeakerHvar? # QCHANGETurnOffLightsRest -> # QCHANGELightSubject/þf QCHANGEHvar? # | QCHANGEHvar QCHANGELightSubject/þf? QIoTSpeakerPlayRest -> - QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? + QIoTSpeakerMusicWord QIoTSpeakerHvar? | "tónlist" # TODO: Make the subject categorization cleaner QIoTSpeakerIncreaseOrDecreaseRest -> # QCHANGELightSubject/þf QCHANGEHvar? # | QCHANGEBrightnessSubject/þf QCHANGEHvar? - QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? - | "í" QIoTSpeakerMusicWord/þgf QIoTSpeakerHvar? + QIoTSpeakerMusicWord QIoTSpeakerHvar? + | "í" QIoTSpeakerMusicWord QIoTSpeakerHvar? # QCHANGESubject/fall -> # QCHANGESubjectOne/fall # | QCHANGESubjectTwo/fall -QIoTSpeakerMusicWord/fall -> - 'tónlist:no'/fall +# There is a bug when trying to +QIoTSpeakerMusicWord -> + "tónlist" + # | 'tónlist:no' # # TODO: Decide whether LightSubject/þgf should be accepted # QCHANGESubjectOne/fall -> @@ -421,6 +423,8 @@ def help_text(lemma: str) -> str: # QCHANGESettingWord/fall -> # 'stilling'/fall +$score(+35) QIoTSpeakerMusicWord + """ @@ -490,7 +494,7 @@ def sentence(state: QueryStateDict, result: Result) -> None: # Perform the action on the Sonos device if result.action == "play_music": - sonos_client.toggle_play_spause() + sonos_client.toggle_play_pause() answer = "Ég kveikti á tónlist." # elif result.action == "increase_volume": # sonos_client.increase_volume() diff --git a/queries/sonos.py b/queries/sonos.py index 5261b88a..919e471e 100644 --- a/queries/sonos.py +++ b/queries/sonos.py @@ -29,24 +29,22 @@ class SonosClient: def __init__(self, access_token, refresh_token, household_id, group_id, player_id): self.access_token = access_token self.refresh_token = refresh_token - if (v := household_id) is not None: - self.household_id = v - else: - self.household_id = get_households(self.access_token).json()["households"][ - 0 - ]["id"] - if (v := group_id) is not None: - self.group_id = v - else: - self.group_id = get_groups(self.access_token, self.household_id).json()[ - "groups" - ][0]["id"] - if (v := player_id) is not None: - self.player_id = v - else: - self.player_id = get_players( - self.access_token, self.household_id, self.group_id - ).json()["players"][0]["id"] + self.household_id = ( + household_id + or get_households(self.access_token).json()["households"][0]["id"] + ) + self.group_id = ( + group_id + or get_groups(self.access_token, self.household_id).json()["groups"][0][ + "id" + ] + ) + self.player_id = ( + player_id + or get_groups(self.access_token, self.household_id).json()["players"][0][ + "id" + ] + ) def toggle_play_pause(self): toggle_play_pause(self.group_id, self.access_token) diff --git a/routes/api.py b/routes/api.py index 244c223e..4ecc75a3 100755 --- a/routes/api.py +++ b/routes/api.py @@ -731,17 +731,3 @@ def sonos_code(version: int = 1) -> Response: return better_jsonify(valid=True, msg="Registered sonos code") return better_jsonify(valid=False, errmsg="Error registering sonos code.") - -@routes.route("/connect_spotify.api", methods=["GET"]) -@routes.route("/connect_spotify.api/v", methods=["GET", "POST"]) -def spotify_code(version: int = 1) -> Response: - print("spotify code") - args = request.args - client_id = args.get("state") - code = args.get("code") - if client_id and code: - success = QueryObject.store_query_data(client_id, "sonos_code", code) - if success: - return better_jsonify(valid=True, msg="Registered sonos code") - - return better_jsonify(valid=False, errmsg="Error registering sonos code.") \ No newline at end of file From aa59a69900a8ea816cf478fdc97b288097c4c2ab Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Wed, 29 Jun 2022 13:49:45 +0000 Subject: [PATCH 126/371] SonosClient refactor --- queries/iot_speakers.py | 57 +++++++------ queries/sonos.py | 174 +++++++++++++++++++++++++++++++--------- 2 files changed, 164 insertions(+), 67 deletions(-) diff --git a/queries/iot_speakers.py b/queries/iot_speakers.py index eff52bb3..80e8041e 100644 --- a/queries/iot_speakers.py +++ b/queries/iot_speakers.py @@ -465,39 +465,36 @@ def sentence(state: QueryStateDict, result: Result) -> None: # TODO: Need to add check for if there are no registered devices to an account, probably when initilazing the querydata if device_data is not None: - timestamp = device_data["sonos"]["credentials"]["timestamp"] - timestamp = datetime.strptime(timestamp, "%Y-%m-%d %H:%M:%S.%f") - if (datetime.now() - timestamp) > timedelta(hours=24): - update_sonos_token(q, device_data) - print("if clause") - try: - access_token = device_data["sonos"]["credentials"]["access_token"] - refresh_token = device_data["sonos"]["credentials"]["refresh_token"] - household_id = device_data["sonos"]["data"]["households"][0]["id"] - group_id = device_data["sonos"]["data"]["groups"][0]["Family Room"] - player_id = device_data["sonos"]["data"]["players"][0]["Family Room"] - except KeyError: - print("No device data found for this account") - - # Successfully fetched data from the device_data table - print("access_token: " + access_token) - print("refresh_token: " + refresh_token) - print("household_id: " + household_id) - print("group_id: " + group_id) - print("player_id: " + player_id) - - # Create a SonosClient object - sonos_client = SonosClient( - access_token=access_token, - refresh_token=refresh_token, - household_id=household_id, - group_id=group_id, - player_id=player_id, - ) + # timestamp = device_data["sonos"]["credentials"]["timestamp"] + # timestamp = datetime.strptime(timestamp, "%Y-%m-%d %H:%M:%S.%f") + # if (datetime.now() - timestamp) > timedelta(hours=24): + # update_sonos_token(q, device_data) + # print("if clause") + # try: + # access_token = device_data["sonos"]["credentials"]["access_token"] + # refresh_token = device_data["sonos"]["credentials"]["refresh_token"] + # household_id = device_data["sonos"]["data"]["households"][0]["id"] + # group_id = device_data["sonos"]["data"]["groups"][0]["Family Room"] + # player_id = device_data["sonos"]["data"]["players"][0]["Family Room"] + # except KeyError: + # print("No device data found for this account") + + # Successfully fetched data from the device_data table + # print("access_token: " + access_token) + # print("refresh_token: " + refresh_token) + # print("household_id: " + household_id) + # print("group_id: " + group_id) + # print("player_id: " + player_id) + + # Create a SonosClient object + sonos_client = SonosClient(device_data, q) + else: + print("No device data found for this account") + return # Perform the action on the Sonos device if result.action == "play_music": - sonos_client.toggle_play_spause() + sonos_client.toggle_play_pause() answer = "Ég kveikti á tónlist." # elif result.action == "increase_volume": # sonos_client.increase_volume() diff --git a/queries/sonos.py b/queries/sonos.py index 4a70ec5e..aff6e9ec 100644 --- a/queries/sonos.py +++ b/queries/sonos.py @@ -28,30 +28,144 @@ # TODO: Refresh token functionality class SonosClient: - def __init__(self, access_token, refresh_token, household_id, group_id, player_id): - self.access_token = access_token - self.refresh_token = refresh_token - if (v := household_id) is not None: - self.household_id = v - else: - self.household_id = get_households(self.access_token).json()["households"][ - 0 - ]["id"] - if (v := group_id) is not None: - self.group_id = v - else: - self.group_id = get_groups(self.access_token, self.household_id).json()[ - "groups" - ][0]["id"] - if (v := player_id) is not None: - self.player_id = v - else: - self.player_id = get_players( - self.access_token, self.household_id, self.group_id - ).json()["players"][0]["id"] + def __init__(self, device_data, q, query=None): + self._q = q + self._device_data = device_data + self._query = query + + try: + self._access_token = self._device_data["sonos"]["credentials"][ + "access_token" + ] + self._refresh_token = self._device_data["sonos"]["credentials"][ + "refresh_token" + ] + self.check_token_expiration() + self._households = ( + self._device_data["sonos"]["data"]["households"] + or self.get_households() + ) + self._household_id = self.get_household_id() + self._groups = ( + self._device_data["sonos"]["data"]["groups"][0]["Family Room"] + or self.get_groups() + ) + self._players = ( + self._device_data["sonos"]["data"]["players"][0]["Family Room"] + or self.get_players() + ) + self._group_id = self.get_group_id() + except KeyError: + print("Missing device data found for this account") + + def check_token_expiration(self): + timestamp = self._device_data["sonos"]["credentials"]["timestamp"] + timestamp = datetime.strptime(timestamp, "%Y-%m-%d %H:%M:%S.%f") + if (datetime.now() - timestamp) > timedelta(hours=24): + self.update_sonos_token() + + def update_sonos_token(self): + print("update sonos token") + self._sonos_encoded_credentials = read_api_key("SonosEncodedCredentials") + self._access_token = self.refresh_token() + self._access_token = self._access_token["access_token"] + sonos_dict = { + "sonos": { + "credentials": { + "access_token": self._access_token, + "timestamp": str(datetime.now()), + } + } + } + + self._q.set_client_data("iot_speakers", sonos_dict, update_in_place=True) + + def refresh_token(self): + """ + Refreshes token + """ + url = f"https://api.sonos.com/login/v3/oauth/access?grant_type=refresh_token&refresh_token={self._refresh_token}" + + payload = {} + headers = {"Authorization": f"Basic {self._sonos_encoded_credentials}"} + + response = requests.request("POST", url, headers=headers, data=payload) + + return response.json() def toggle_play_pause(self): - toggle_play_pause(self.group_id, self.access_token) + """ + Toggles the play/pause of a group + """ + url = f"https://api.ws.sonos.com/control/api/v1/groups/{self._group_id}/playback/togglePlayPause" + + payload = {} + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {self._access_token}", + } + + response = requests.request("POST", url, headers=headers, data=payload) + + return response + + def get_households(self): + """ + Returns the list of households of the user + """ + url = f"https://api.ws.sonos.com/control/api/v1/households" + + payload = {} + headers = {"Authorization": f"Bearer {self._access_token}"} + + response = requests.request("GET", url, headers=headers, data=payload) + + return response.json() + + def get_household_id(self): + """ + Returns the household id for the given query + """ + url = f"https://api.ws.sonos.com/control/api/v1/households" + + payload = {} + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {self._access_token}", + } + + response = requests.request("GET", url, headers=headers, data=payload) + + return response.json()["households"][0]["id"] + + def get_groups(self): + """ + Returns the list of groups of the user + """ + url = f"https://api.ws.sonos.com/control/api/v1/households/{self.household_id}/groups" + + payload = {} + headers = {"Authorization": f"Bearer {self._access_token}"} + + response = requests.request("GET", url, headers=headers, data=payload) + + return response.json() + + def get_group_id(self): + """ + Returns the group id for the given query + """ + url = f"https://api.ws.sonos.com/control/api/v1/households/{self._household_id}/groups" + + payload = {} + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {self._access_token}", + } + + response = requests.request("GET", url, headers=headers, data=payload) + + return response.json()["groups"][0]["id"] # TODO: Check whether this should return the ids themselves instead of the json response @@ -66,7 +180,7 @@ def get_households(token): response = requests.request("GET", url, headers=headers, data=payload) - return response + return response.json() def get_groups(household_id, token): @@ -114,20 +228,6 @@ def refresh_token(sonos_encoded_credentials, refresh_token): return response -def toggle_play_pause(group_id, token): - """ - Toggles the play/pause of a group - """ - url = f"https://api.ws.sonos.com/control/api/v1/groups/{group_id}/playback/togglePlayPause" - - payload = {} - headers = {"Content-Type": "application/json", "Authorization": f"Bearer {token}"} - - response = requests.request("POST", url, headers=headers, data=payload) - - return response - - def audio_clip(audioclip_url, player_id, token): """ Plays an audioclip from link to .mp3 file From 0b974da6ce94ddcceb89a242d73f89bf16947c6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Wed, 29 Jun 2022 15:06:37 +0000 Subject: [PATCH 127/371] Show selection and date selection now matches available options and generates an answer according to that --- queries/dialogue.py | 2 +- queries/theater/theater.toml | 20 ++- queries/theater_module.py | 318 +++++++++++++++++++++++++++++++++-- 3 files changed, 318 insertions(+), 22 deletions(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index e21ce569..b9218e84 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -18,7 +18,7 @@ from dataclasses import dataclass, field try: - import tomllib # type: ignore (module not available in Python <3.11) + import tomllib # type: ignore (module not available in Python <3.11) except ModuleNotFoundError: import tomli as tomllib diff --git a/queries/theater/theater.toml b/queries/theater/theater.toml index d09a18d3..bc1a0cee 100644 --- a/queries/theater/theater.toml +++ b/queries/theater/theater.toml @@ -6,19 +6,31 @@ type = "ListResource" prompts.initial = "Hvaða sýningu má bjóða þér að fara á?" prompts.options = "Sýningarnar sem eru í boði eru: {options}" prompts.confirm = "Þú valdir sýninguna {show}, viltu halda áfram með pöntunina?" +prompts.no_show_matched = "Því miður er þessi sýning ekki í boði. Vinsamlegast reyndu aftur." +prompts.no_show_matched_data_exists = "Því miður er þessi sýning ekki í boði. Síðasta valda sýning var {show}, viltu halda áfram með hana?" [[resources]] name = "ShowDate" +type = "DateResource" + +[[resources]] +name = "ShowTime" +type = "TimeResource" + +[[resources]] +name = "ShowDateTime" type = "ListResource" -requires = ["Show"] -prompts.initial = "Hvenær viltu fara á sýninguna {show}?" +requires = ["Show", "ShowDate", "ShowTime"] +prompts.initial = "Hvenær viltu fara á sýninguna {show}?\n{dates}" prompts.options = "Sýningardagsetningar eru: {options}" prompts.confirm = "Þú valdir dagsetninguna {date}, viltu halda áfram og velja fjölda sæta?" +prompts.many_matching_times = "Margar dagsetningar pössuðu við gefna tímasetningu, vinsamlegast reyndu aftur." +prompts.multiple_times_for_date = "Fyrir dagsetninguna {date} eru nokkrar tímasetningar, hverja af þeim viltu bóka?\nValmöguleikarnir eru:\n{times}" [[resources]] name = "ShowSeats" type = "NumberResource" -requires = ["ShowDate"] +requires = ["ShowDateTime"] prompts.initial = "Hversu mörg sæti viltu bóka?" prompts.confirm = "Þú valdir {seats} sæti, viltu halda áfram og velja staðsetningu sætanna?" @@ -37,4 +49,4 @@ requires = ["SeatLocation"] prompts.final = "Þú valdir {seats} sæti á svæðinum {seat_location} fyrir sýninguna {show} þann {date}." prompts.cancelled = "Móttekið, hætti við leikhús pöntunina." -# TODO: Add a resource for the payment method \ No newline at end of file +# TODO: Add a resource for the payment method diff --git a/queries/theater_module.py b/queries/theater_module.py index 15be018b..05dd1d55 100644 --- a/queries/theater_module.py +++ b/queries/theater_module.py @@ -21,14 +21,23 @@ This query module handles dialogue related to theater tickets. """ -from typing import Any, Callable, Optional +from typing import Any, Callable, Optional, cast +import json import logging import random +import datetime from query import Query, QueryStateDict -from tree import Result, Node +from tree import Result, Node, TerminalNode from queries import gen_answer, parse_num, query_json_api -from queries.dialogue import DialogueStateManager, ListResource, Resource, ResourceState +from queries.dialogue import ( + DateResource, + DialogueStateManager, + ListResource, + Resource, + ResourceState, + TimeResource, +) _THEATER_DIALOGUE_NAME = "theater" _THEATER_QTYPE = "theater" @@ -88,10 +97,10 @@ def help_text(lemma: str) -> str: QTheaterShowName → Nl QTheaterShowDateQuery → - "ég"? "vil"? "fara"? "á" 'sýning'? QTheaterShowDate + "ég"? "vil"? "fara"? "á"? 'sýning'? QTheaterShowDate QTheaterShowDate → - QTheaterDateTime | QTheaterDate + QTheaterDateTime | QTheaterDate | QTheaterTime QTheaterDateTime → tímapunkturafs @@ -100,6 +109,9 @@ def help_text(lemma: str) -> str: dagsafs | dagsföst +QTheaterTime → + "klukkan"? tími + QTheaterShowSeatsQuery → "ég"? "vil"? "fá"? QNum "sæti"? @@ -135,6 +147,31 @@ def help_text(lemma: str) -> str: """ +_SHOWS = [ + { + "title": "Emil í Kattholti", + "date": [ + datetime.datetime(2022, 8, 27, 13, 0), + datetime.datetime(2022, 8, 28, 13, 0), + datetime.datetime(2022, 8, 28, 17, 0), + ], + "location": [ + (1, 1), # (row, seat) + (1, 2), + (1, 3), + (1, 4), + (2, 7), + (2, 8), + (2, 9), + (6, 20), + (6, 21), + (6, 22), + (6, 23), + (6, 24), + ], + }, +] + def _generate_show_answer( resource: ListResource, dsm: DialogueStateManager @@ -144,6 +181,12 @@ def _generate_show_answer( return resource.prompts["options"].format( options=", ".join(dsm.get_result().shows) ) + if result.get("no_show_matched"): + return resource.prompts["no_show_matched"] + if result.get("no_show_matched_data_exists"): + return resource.prompts["no_show_matched_data_exists"].format( + show=resource.data[0] + ) if resource.is_unfulfilled: return resource.prompts["initial"] if resource.is_fulfilled: @@ -157,8 +200,43 @@ def _generate_date_answer( result = dsm.get_result() if result.get("dateOptions"): return resource.prompts["options"] + if result.get("many_matching_times"): + return resource.prompts["many_matching_times"] + if result.get("multiple_times_for_date"): + title = dsm.get_resource("Show").data[0] + show_date: Optional[datetime.date] = cast( + DateResource, dsm.get_resource("ShowDate") + ).date + show_times: list[str] = [] + if show_date is not None: + for show in _SHOWS: + if show["title"] == title: + for date in show["date"]: + assert isinstance(date, datetime.datetime) + if date.date() == show_date: + show_times.append(date.strftime(" %H:%M\n")) + return resource.prompts["multiple_times_for_date"].format( + date=show_date, times="".join(show_times) + ) if resource.is_unfulfilled: - return resource.prompts["initial"] + title = dsm.get_resource("Show").data[0] + dates: list[str] = [] + for show in _SHOWS: + if show["title"] == title: + for date in show["date"]: + dates.append(date.strftime(" %d/%m/%Y klukkan %H:%M\n")) + date_number: int = 3 if len(dates) >= 3 else len(dates) + start_string: str = ( + "Eftirfarandi dagsetning er í boði:\n" + if date_number == 1 + else "Næstu tvær dagsetningar eru:\n" + if date_number == 2 + else "Næstu þrjár dagsetningar eru:\n" + ) + return resource.prompts["initial"].format( + show=title, + dates=start_string + "".join(dates), + ) if resource.is_fulfilled: return resource.prompts["confirm"].format(date=result.get("date")) @@ -217,9 +295,19 @@ def QTheaterShowQuery(node: Node, params: QueryStateDict, result: Result) -> Non def _add_show( resource: Resource, dsm: DialogueStateManager, result: Result ) -> None: - resource.data = [dsm.get_result().show_name] - resource.state = ResourceState.FULFILLED - print("Show resource data: ", resource.data) + selected_show: str = dsm.get_result().show_name + show_exists = False + for show in _SHOWS: + if show["title"] == selected_show: + resource.data = [show["title"]] + resource.state = ResourceState.FULFILLED + show_exists = True + break + if not show_exists: + if resource.is_unfulfilled: + result.no_show_matched = True + if resource.is_fulfilled: + result.no_show_matched_data_exists = True if "callbacks" not in result: result["callbacks"] = [] @@ -227,12 +315,188 @@ def _add_show( result.callbacks.append((filter_func, _add_show)) -def QTheaterShowDateQuery(node: Node, params: QueryStateDict, result: Result) -> None: - pass +def _date_callback( + resource: DateResource, dsm: DialogueStateManager, result: Result +) -> None: + print("In date callback") + if dsm.get_resource("Show").is_confirmed: + print("Show was confirmed") + show_title: str = dsm.get_resource("Show").data[0] + for show in _SHOWS: + if show["title"] == show_title: + for date in show["date"]: + if result["show_date"] == date.date(): + resource.set_date(date.date()) + resource.state = ResourceState.FULFILLED + break + time_resource: TimeResource = cast(TimeResource, dsm.get_resource("ShowTime")) + datetime_resource: Resource = dsm.get_resource("ShowDateTime") + if time_resource.is_fulfilled: + print("Time resource was fulfilled") + datetime_resource.state = ResourceState.FULFILLED + else: + print("Time resource not fulfilled, trying to add time") + show_times: list[datetime.time] = [] + for show in _SHOWS: + if show["title"] == show_title: + for date in show["date"]: + print("Date: ", date) + print("Time: ", date.time()) + print("Resource date: ", resource.date) + print("Date date: ", date.date()) + print("if: ", date.date() == resource.date) + if resource.date == date.date(): + print( + "Adding showtime: ", date.time(), " fyrir date: ", date + ) + show_times.append(date.time()) + print("Show times: ", show_times) + if len(show_times) == 1: + time_resource.set_time(show_times[0]) + time_resource.state = ResourceState.FULFILLED + datetime_resource.state = ResourceState.FULFILLED + print("One show time") + else: + result.multiple_times_for_date = True + print("Many showtimes", show_times) + datetime_resource.state = ResourceState.PARTIALLY_FULFILLED + else: + dsm.set_error() + + +def _time_callback( + resource: TimeResource, dsm: DialogueStateManager, result: Result +) -> None: + if dsm.get_resource("Show").is_confirmed: + show_title: str = dsm.get_resource("Show").data[0] + date_resource: DateResource = cast(DateResource, dsm.get_resource("ShowDate")) + datetime_resource: Resource = dsm.get_resource("ShowDateTime") + if date_resource.is_fulfilled: + for show in _SHOWS: + if show["title"] == show_title: + for date in show["date"]: + if ( + date_resource.date == date.date() + and result["show_time"] == date.time() + ): + print("Time callback, date there, setting time") + resource.set_time(date.time()) + resource.state = ResourceState.FULFILLED + break + if resource.is_fulfilled: + datetime_resource.state = ResourceState.FULFILLED + else: + result.wrong_show_time = True + else: + first_matching_date: Optional[datetime.datetime] = None + for show in _SHOWS: + if show["title"] == show_title: + for date in show["date"]: + if result["show_time"] == date.time(): + if first_matching_date is None: + print("Setting first_matching_date") + first_matching_date = cast(datetime.datetime, date) + else: + print("Result matched many times, returning") + result.many_matching_times = True + return + if first_matching_date is not None: + date_resource: DateResource = cast( + DateResource, dsm.get_resource("ShowDate") + ) + date_resource.set_date(first_matching_date.date()) + resource.set_time(first_matching_date.time()) + else: + dsm.set_error() + + +def QTheaterDateTime(node: Node, params: QueryStateDict, result: Result) -> None: + datetimenode = node.first_child(lambda n: True) + assert isinstance(datetimenode, TerminalNode) + now = datetime.datetime.now() + y, m, d, h, min, _ = (i if i != 0 else None for i in json.loads(datetimenode.aux)) + if y is None: + y = now.year + if m is None: + m = now.month + if d is None: + d = now.day + if h is None: + h = 12 + if min is None: + min = 0 + result["show_time"] = datetime.time(h, min) + result["show_date"] = datetime.date(y, m, d) + + if "callbacks" not in result: + result["callbacks"] = [] + result.callbacks.append((lambda r: r.name == "ShowDate", _date_callback)) + result.callbacks.append((lambda r: r.name == "ShowTime", _time_callback)) + + +def QTheaterDate(node: Node, params: QueryStateDict, result: Result) -> None: + datenode = node.first_child(lambda n: True) + assert isinstance(datenode, TerminalNode) + cdate = datenode.contained_date + if cdate: + y, m, d = cdate + now = datetime.datetime.utcnow() + + # This is a date that contains at least month & mday + if d and m: + if not y: + y = now.year + # Bump year if month/day in the past + if m < now.month or (m == now.month and d < now.day): + y += 1 + result["show_date"] = datetime.date(day=d, month=m, year=y) + + if "callbacks" not in result: + result["callbacks"] = [] + filter_func: Callable[[Resource], bool] = lambda r: r.name == "ShowDate" + result.callbacks.append((filter_func, _date_callback)) + return + raise ValueError("No date in {0}".format(str(datenode))) + + +def QTheaterTime(node: Node, params: QueryStateDict, result: Result) -> None: + # Extract time from time terminal nodes + tnode = cast(TerminalNode, node.first_child(lambda n: n.has_t_base("tími"))) + if tnode: + aux_str = tnode.aux.strip("[]") + hour, minute, _ = (int(i) for i in aux_str.split(", ")) + + result["show_time"] = datetime.time(hour, minute) + + if "callbacks" not in result: + result["callbacks"] = [] + filter_func: Callable[[Resource], bool] = lambda r: r.name == "ShowTime" + result.callbacks.append((filter_func, _time_callback)) def QTheaterShowSeatsQuery(node: Node, params: QueryStateDict, result: Result) -> None: - pass + datenode = node.first_child(lambda n: True) + assert isinstance(datenode, TerminalNode) + cdate = datenode.contained_date + if cdate: + y, m, d = cdate + now = datetime.datetime.utcnow() + + # This is a date that contains at least month & mday + if d and m: + if not y: + y = now.year + # Bump year if month/day in the past + if m < now.month or (m == now.month and d < now.day): + y += 1 + result["delivery_date"] = datetime.date(day=d, month=m, year=y) + + if "callbacks" not in result: + result["callbacks"] = [] + filter_func: Callable[[Resource], bool] = lambda r: r.name == "Date" + result.callbacks.append((filter_func, _date_callback)) + return + raise ValueError("No date in {0}".format(str(datenode))) def QTheaterShowLocationQuery( @@ -254,11 +518,11 @@ def QTheaterShowName(node: Node, params: QueryStateDict, result: Result) -> None def QNum(node: Node, params: QueryStateDict, result: Result): - fruitnumber = int(parse_num(node, result._nominative)) - if fruitnumber is not None: - result.fruitnumber = fruitnumber + number = int(parse_num(node, result._nominative)) + if number is not None: + result.number = number else: - result.fruitnumber = 1 + result.number = 1 def QCancel(node: Node, params: QueryStateDict, result: Result): @@ -274,6 +538,26 @@ def _cancel_order( result.callbacks.append((filter_func, _cancel_order)) +def QYes(node: Node, params: QueryStateDict, result: Result): + def _parse_yes( + resource: Resource, dsm: DialogueStateManager, result: Result + ) -> None: + if "yes_used" not in result and resource.is_fulfilled: + resource.state = ResourceState.CONFIRMED + result.yes_used = True + if resource.name == "ShowDateTime": + for rname in resource.requires: + dsm.get_resource(rname).state = ResourceState.CONFIRMED + + if "callbacks" not in result: + result["callbacks"] = [] + filter_func: Callable[[Resource], bool] = ( + lambda r: r.name in ("Show", "ShowDateTime") and not r.is_confirmed + ) + result.callbacks.append((filter_func, _parse_yes)) + result.qtype = "QYes" + + SHOW_URL = "https://leikhusid.is/wp-json/shows/v1/categories/938" @@ -286,7 +570,7 @@ def _fetch_shows() -> Any: _ANSWERING_FUNCTIONS = { "Show": _generate_show_answer, - "ShowDate": _generate_date_answer, + "ShowDateTime": _generate_date_answer, "ShowSeats": _generate_seat_answer, "SeatLocation": _generate_location_answer, "Final": _generate_final_answer, From b13e47bc72f7d4787cb324b2979a6d4c2fade92a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Wed, 29 Jun 2022 16:08:18 +0000 Subject: [PATCH 128/371] Handling for number of seats --- queries/theater_module.py | 57 +++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 30 deletions(-) diff --git a/queries/theater_module.py b/queries/theater_module.py index 05dd1d55..8a2812eb 100644 --- a/queries/theater_module.py +++ b/queries/theater_module.py @@ -92,7 +92,9 @@ def help_text(lemma: str) -> str: | QCancel # TODO: Hvað er í boði, ég vil sýningu X, dagsetningu X, X mörg sæti, staðsetningu X -QTheaterShowQuery → QTheaterEgVil? "velja" 'sýning' QTheaterShowName > QTheaterShowName +QTheaterShowQuery → QTheaterEgVil? "velja" 'sýning' QTheaterShowName + > QTheaterEgVil? "fara" "á" 'sýning' QTheaterShowName + > QTheaterShowName QTheaterShowName → Nl @@ -113,7 +115,7 @@ def help_text(lemma: str) -> str: "klukkan"? tími QTheaterShowSeatsQuery → - "ég"? "vil"? "fá"? QNum "sæti"? + QTheaterEgVil "fá"? QNum "sæti"? QTheaterShowLocationQuery → "ég"? "vil"? "sæti"? QNum til? QNum "í" "röð" QNum @@ -178,9 +180,10 @@ def _generate_show_answer( ) -> Optional[str]: result = dsm.get_result() if result.get("showOptions"): - return resource.prompts["options"].format( - options=", ".join(dsm.get_result().shows) - ) + shows: list[str] = [] + for show in _SHOWS: + shows.append(show["title"]) + return resource.prompts["options"].format(options=", ".join(shows)) if result.get("no_show_matched"): return resource.prompts["no_show_matched"] if result.get("no_show_matched_data_exists"): @@ -238,7 +241,14 @@ def _generate_date_answer( dates=start_string + "".join(dates), ) if resource.is_fulfilled: - return resource.prompts["confirm"].format(date=result.get("date")) + date_resource = dsm.get_resource("ShowDate") + time_resource = dsm.get_resource("ShowTime") + return resource.prompts["confirm"].format( + date=datetime.datetime.combine( + date_resource.data, + time_resource.data, + ).strftime("%Y/%m/%d %H:%M") + ) def _generate_seat_answer( @@ -248,7 +258,7 @@ def _generate_seat_answer( if resource.is_unfulfilled: return resource.prompts["initial"] if resource.is_fulfilled: - return resource.prompts["confirm"].format(seats=result.get("seats")) + return resource.prompts["confirm"].format(seats=resource.data[0]) def _generate_location_answer( @@ -475,28 +485,16 @@ def QTheaterTime(node: Node, params: QueryStateDict, result: Result) -> None: def QTheaterShowSeatsQuery(node: Node, params: QueryStateDict, result: Result) -> None: - datenode = node.first_child(lambda n: True) - assert isinstance(datenode, TerminalNode) - cdate = datenode.contained_date - if cdate: - y, m, d = cdate - now = datetime.datetime.utcnow() - - # This is a date that contains at least month & mday - if d and m: - if not y: - y = now.year - # Bump year if month/day in the past - if m < now.month or (m == now.month and d < now.day): - y += 1 - result["delivery_date"] = datetime.date(day=d, month=m, year=y) + def _add_seats( + resource: Resource, dsm: DialogueStateManager, result: Result + ) -> None: + resource.data = [result.number] + resource.state = ResourceState.FULFILLED - if "callbacks" not in result: - result["callbacks"] = [] - filter_func: Callable[[Resource], bool] = lambda r: r.name == "Date" - result.callbacks.append((filter_func, _date_callback)) - return - raise ValueError("No date in {0}".format(str(datenode))) + if "callbacks" not in result: + result["callbacks"] = [] + filter_func: Callable[[Resource], bool] = lambda r: r.name == "ShowSeats" + result.callbacks.append((filter_func, _add_seats)) def QTheaterShowLocationQuery( @@ -552,10 +550,9 @@ def _parse_yes( if "callbacks" not in result: result["callbacks"] = [] filter_func: Callable[[Resource], bool] = ( - lambda r: r.name in ("Show", "ShowDateTime") and not r.is_confirmed + lambda r: r.name in ("Show", "ShowDateTime", "ShowSeats") and not r.is_confirmed ) result.callbacks.append((filter_func, _parse_yes)) - result.qtype = "QYes" SHOW_URL = "https://leikhusid.is/wp-json/shows/v1/categories/938" From c4e7c6bf0bebe509ae17ad8f0520cda9acb8124b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Wed, 29 Jun 2022 17:14:55 +0000 Subject: [PATCH 129/371] Start of row and seat selection handling --- queries/theater/theater.toml | 6 ++-- queries/theater_module.py | 68 ++++++++++++++++++++++++++++++------ 2 files changed, 60 insertions(+), 14 deletions(-) diff --git a/queries/theater/theater.toml b/queries/theater/theater.toml index bc1a0cee..03fd2749 100644 --- a/queries/theater/theater.toml +++ b/queries/theater/theater.toml @@ -38,9 +38,9 @@ prompts.confirm = "Þú valdir {seats} sæti, viltu halda áfram og velja staðs name = "SeatLocation" type = "ListResource" requires = ["ShowSeats"] -prompts.initial = "{seats} sæti eru í boði á svæðum {seat_location}. Á hvaða svæði viltu fá sæti?" -prompts.options = "Svæðin með {seats} laus sæti eru: {seat_location}." -prompts.confirm = "Þú valdir svæðið {seat_location}, viltu halda áfram?" +prompts.initial = "{seats} sæti eru í boði í röðum {seat_rows}. Á hvaða svæði viltu fá sæti?" +prompts.options = "Raðirnar með {seats} laus sæti eru: {seat_rows}." +prompts.confirm = "Þú valdir sæti {seats} í röð {row}, viltu halda áfram?" [[resources]] name = "Final" diff --git a/queries/theater_module.py b/queries/theater_module.py index 8a2812eb..c2d62e02 100644 --- a/queries/theater_module.py +++ b/queries/theater_module.py @@ -27,9 +27,11 @@ import random import datetime + from query import Query, QueryStateDict from tree import Result, Node, TerminalNode from queries import gen_answer, parse_num, query_json_api +from queries.num import number_to_neutral from queries.dialogue import ( DateResource, DialogueStateManager, @@ -118,8 +120,18 @@ def help_text(lemma: str) -> str: QTheaterEgVil "fá"? QNum "sæti"? QTheaterShowLocationQuery → - "ég"? "vil"? "sæti"? QNum til? QNum "í" "röð" QNum - | "bekkur" QNum "sæti" QNum "til"? QNum + QLocationRowFirst + | QLocationSeatsFirst + +QLocationRowFirst → + "bekkur" QNum "sæti" QNum "til"? QNum + | "röð" QNum "sæti" QNum "til"? QNum + +QLocationSeatsFirst → + "ég"? "vil"? "sæti"? QNum "til"? QNum "í" "röð" QNum + | "ég"? "vil"? "sæti"? QNum "til"? QNum "í" QNum "röð" + | "ég"? "vil"? "sæti"? QNum "til"? QNum "á" "bekk" QNum + | "ég"? "vil"? "sæti"? QNum "til"? QNum "á" QNum "bekk" QTheaterShowOptions → "sýningar" | "hvaða" "sýningar" "eru" "í" "boði" @@ -127,6 +139,7 @@ def help_text(lemma: str) -> str: | "hverjir"? "eru"? "valmöguleikarnir" | "hvert" "er" "úrvalið" +QTheaterRodBekk → "röð" | "bekk" QTheaterEgVil → "ég"? "vil" @@ -265,12 +278,24 @@ def _generate_location_answer( resource: ListResource, dsm: DialogueStateManager ) -> Optional[str]: result = dsm.get_result() + seat_resource = dsm.get_resource("ShowSeats") if result.get("locationOptions"): return resource.prompts["options"] if resource.is_unfulfilled: - return resource.prompts["initial"] + return resource.prompts["initial"].format( + seats=seat_resource.data[0], seat_rows=10 + ) if resource.is_fulfilled: - return resource.prompts["confirm"].format(location=result.get("location")) + location_resource = dsm.get_resource("SeatLocation") + number_to_neutral() + seat_string = "{first_seat} til {last_seat}".format( + first_seat=number_to_neutral(location_resource.data[0][1]), + last_seat=number_to_neutral(location_resource.data[-1][1]), + ) + return resource.prompts["confirm"].format( + seats=seat_string, row=location_resource.data[0][0] + ) + return resource.prompts["confirm"].format(seats=result.get("location")) def _generate_final_answer( @@ -281,7 +306,7 @@ def _generate_final_answer( resource.state = ResourceState.CONFIRMED seat_resource = dsm.get_resource("ShowSeats") - location_resource = dsm.get_resource("ShowLocation") + location_resource = dsm.get_resource("SeatLocation") date_resource = dsm.get_resource("ShowDate") show_resource = dsm.get_resource("Show") ans = resource.prompts["final"].format( @@ -500,7 +525,28 @@ def _add_seats( def QTheaterShowLocationQuery( node: Node, params: QueryStateDict, result: Result ) -> None: - pass + print("In QTheaterShowLocationQuery") + + def _add_location( + resource: ListResource, dsm: DialogueStateManager, result: Result + ) -> None: + print("ADD LOCATION CALLBACK") + for seat in range(result.numbers[1], result.numbers[2] + 1): + print("Adding seat to list: ", seat) + resource.data.append((result.numbers[0], seat)) + print("Location data: ", resource.data) + resource.state = ResourceState.FULFILLED + + if "callbacks" not in result: + result["callbacks"] = [] + filter_func: Callable[[Resource], bool] = lambda r: r.name == "SeatLocation" + result.callbacks.append((filter_func, _add_location)) + + +def QLocationSeatsFirst(node: Node, params: QueryStateDict, result: Result) -> None: + # Making sure that the row comes before the seats in the list + result.numbers.insert(0, result.numbers.pop()) + print("Result numbers: ", result.numbers) def QTheaterShowOptions(node: Node, params: QueryStateDict, result: Result) -> None: @@ -516,11 +562,11 @@ def QTheaterShowName(node: Node, params: QueryStateDict, result: Result) -> None def QNum(node: Node, params: QueryStateDict, result: Result): - number = int(parse_num(node, result._nominative)) - if number is not None: - result.number = number - else: - result.number = 1 + number: int = int(parse_num(node, result._nominative)) + if "numbers" not in result: + result["numbers"] = [] + result.numbers.append(number) + result.number = number def QCancel(node: Node, params: QueryStateDict, result: Result): From 087243a73787b271172a2e50df83e5b5b6ee876c Mon Sep 17 00:00:00 2001 From: Logi E Date: Thu, 30 Jun 2022 10:55:19 +0000 Subject: [PATCH 130/371] Added docstrings for dialogue classes Breaking changes to storing data in querydata --- queries/dialogue.py | 167 +++++++++++++++++++++++++--------- queries/fruitseller_module.py | 42 ++++----- queries/theater/theater.toml | 3 +- 3 files changed, 143 insertions(+), 69 deletions(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index b9218e84..145e3b40 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -3,6 +3,7 @@ Callable, Dict, Mapping, + Set, Tuple, Union, List, @@ -24,12 +25,13 @@ from tree import Result from query import Query, ClientDataDict +from queries import natlang_seq # Global key for storing client data for dialogues DIALOGUE_KEY = "dialogue" -DIALOGUE_DATA_KEY = "dialogue_data" DIALOGUE_NAME_KEY = "dialogue_name" DIALOGUE_RESOURCES_KEY = "resources" +DIALOGUE_LAST_INTERACTED_WITH_KEY = "last_interacted_with" EMPTY_DIALOGUE_DATA = "{}" FINAL_RESOURCE_NAME = "Final" @@ -66,33 +68,23 @@ class ResourceState(IntEnum): @dataclass class Resource: + """ + Base class representing a dialogue resource. + Keeps track of the state of the resource, and the data it contains. + """ + + # Name of resource name: str = "" + # Type (child class) of Resource type: str = "" + # Contained data data: Any = None + # Resource state (unfulfilled, partially fulfilled, etc.) state: ResourceState = ResourceState.UNFULFILLED + # Resources that must be confirmed before moving on to this resource requires: List[str] = field(default_factory=list) + # Dictionary containing different prompts/responses prompts: Mapping[str, str] = field(default_factory=dict) - _answer: Optional[str] = None - - def set_answer(self, answer_name: str, **kwargs: str) -> None: - print("SETTING ANSWER:", answer_name, kwargs, self.name) - self._answer = self.prompts[answer_name].format(**kwargs) - print("ANSWER SET AS:", self._answer) - - def get_answer(self, dsm: "DialogueStateManager") -> Optional[str]: - print("CURRENT RESOURCE:", self.name, "ANSWER:", self._answer) - if self._answer is not None: - return self._answer - ans: Optional[str] = None - if self.requires: - for rname in self.requires: - resource = dsm.get_resource(rname) - if not resource.is_confirmed: - ans = resource.get_answer(dsm) - if ans: - break - # assert ans is not None, "No answer was generated from resource " + self.name - return ans @property def is_unfulfilled(self) -> bool: @@ -123,23 +115,42 @@ def is_cancelled(self) -> bool: return self.state is ResourceState.CANCELLED def update(self, new_data: Optional["Resource"]) -> None: + """Update resource with attributes from another resource.""" if new_data: self.__dict__.update(new_data.__dict__) + def format_data(self, format_func: Optional[Callable[[Any], str]] = None) -> str: + """ + Function to format data for display, + optionally taking in a formatting function. + """ + return format_func(self.data) if format_func else self.data + @dataclass class ListResource(Resource): + """Resource representing a list of items.""" + data: ListResourceType = field(default_factory=list) + max_items: Optional[int] = None + + def format_data(self, format_func: Optional[Callable[[Any], str]] = None) -> str: + if format_func: + return format_func(self.data) + return natlang_seq([str(x) for x in self.data]) # TODO: ? # ExactlyOneResource (choose one resource from options) # SetResource (a set of resources)? +# UserInfoResource (user info, e.g. name, age, home address, etc., can use saved data to autofill) # ... @dataclass class YesNoResource(Resource): + """Resource representing a yes/no answer.""" + data: bool = False def set_yes(self): @@ -150,9 +161,40 @@ def set_no(self): self.data = False self.state = ResourceState.CONFIRMED + def format_data(self, format_func: Optional[Callable[[Any], str]] = None) -> str: + if format_func: + return format_func(self.data) + return "já" if self.data else "nei" + + +@dataclass +class ConfirmResource(YesNoResource): + """Resource representing a confirmation of other resources.""" + + def set_no(self): + self.data = False + self.state = ResourceState.CANCELLED # TODO: ? + + def confirm_children(self, dsm: "DialogueStateManager") -> None: + """Confirm all child/required resources.""" + ConfirmResource._confirm_children(self, dsm) + + @staticmethod + def _confirm_children( + res: Resource, + dsm: "DialogueStateManager", + ) -> None: + for req in res.requires: + req_res = dsm.get_resource(req) + if not isinstance(req_res, ConfirmResource): + ConfirmResource._confirm_children(req_res, dsm) + req_res.state = ResourceState.CONFIRMED + @dataclass class DateResource(Resource): + """Resource representing a date.""" + data: datetime.date = field(default_factory=datetime.date.today) @property @@ -162,9 +204,16 @@ def date(self) -> Optional[datetime.date]: def set_date(self, new_date: datetime.date) -> None: self.data = new_date + def format_data(self, format_func: Optional[Callable[[Any], str]] = None) -> str: + if format_func: + return format_func(self.data) + return self.data.strftime("%x") + @dataclass class TimeResource(Resource): + """Resource representing a time (00:00-23:59).""" + data: datetime.time = field(default_factory=datetime.time) @property @@ -174,16 +223,23 @@ def time(self) -> Optional[datetime.time]: def set_time(self, new_time: datetime.time) -> None: self.data = new_time + def format_data(self, format_func: Optional[Callable[[Any], str]] = None) -> str: + if format_func: + return format_func(self.data) + return self.data.strftime("%X") + @dataclass class DatetimeResource(Resource): - data: List[Union[Optional[datetime.date], Optional[datetime.time]]] = field( - default_factory=lambda: [None, None] - ) + """Resource for wrapping date and time resources.""" + + pass @dataclass class NumberResource(Resource): + """Resource representing a number.""" + data: int = 0 @@ -199,6 +255,8 @@ class AndResource(Resource): # For answering multiple resources at the same tim @dataclass class FinalResource(Resource): + """Resource representing the final state of a dialogue.""" + data: Any = None @@ -213,16 +271,20 @@ class FinalResource(Resource): "FinalResource": FinalResource, } -############################## -# RESOURCE CLASSES END # -############################## +################################ +# DIALOGUE STATE MANAGER # +################################ class DialogueStructureType(TypedDict): - """ """ + """ + Representation of the dialogue structure, + as it is read from the TOML files and saved to the database. + """ dialogue_name: str resources: Dict[str, Resource] + last_interacted_with: Optional[datetime.datetime] def _load_dialogue_structure(filename: str) -> DialogueStructureType: @@ -274,10 +336,7 @@ def not_in_dialogue(self) -> bool: and self._saved_state.get(DIALOGUE_NAME_KEY) != self._dialogue_name ) - def setup_dialogue( - self, - answering_functions: AnsweringFunctionMap, - ) -> None: + def setup_dialogue(self, answering_functions: AnsweringFunctionMap) -> None: obj = _load_dialogue_structure(self._dialogue_name) for rname, resource in obj[DIALOGUE_RESOURCES_KEY].items(): if rname in self._saved_state[DIALOGUE_RESOURCES_KEY]: @@ -295,6 +354,7 @@ def start_dialogue(self): { DIALOGUE_NAME_KEY: self._dialogue_name, DIALOGUE_RESOURCES_KEY: {}, + DIALOGUE_LAST_INTERACTED_WITH_KEY: datetime.datetime.now(), } ) @@ -305,6 +365,7 @@ def update_dialogue_state(self): { DIALOGUE_NAME_KEY: self._dialogue_name, DIALOGUE_RESOURCES_KEY: self._resources, + DIALOGUE_LAST_INTERACTED_WITH_KEY: datetime.datetime.now(), } ) @@ -319,7 +380,7 @@ def get_answer(self) -> Optional[str]: cbs: Optional[List[CallbackTupleType]] = self._result.get("callbacks") curr_resource = self._resources[FINAL_RESOURCE_NAME] if cbs: - self._execute_callbacks_postorder(curr_resource, cbs) + self._execute_callbacks_postorder(curr_resource, cbs, set()) if self._error: # An error was raised somewhere during the callbacks @@ -336,7 +397,7 @@ def get_answer(self) -> Optional[str]: # Iterate through resources (inorder traversal) # until one generates an answer - self._answer = self._get_answer_postorder(curr_resource) + self._answer = self._get_answer_postorder(curr_resource, set()) if self._resources[FINAL_RESOURCE_NAME].is_confirmed: # Final callback (performing some operation with the dialogue's data) @@ -346,20 +407,26 @@ def get_answer(self) -> Optional[str]: self.update_dialogue_state() return self._answer - def _get_answer_postorder(self, curr_resource: Resource) -> Optional[str]: + def _get_answer_postorder( + self, curr_resource: Resource, finished: Set[str] + ) -> Optional[str]: for rname in curr_resource.requires: - ans = self._get_answer_postorder(self._resources[rname]) - if ans: - return ans + if rname not in finished: + finished.add(rname) + ans = self._get_answer_postorder(self._resources[rname], finished) + if ans: + return ans if curr_resource.name in self._answering_functions: return self._answering_functions[curr_resource.name](curr_resource, self) return None def _execute_callbacks_postorder( - self, curr_resource: Resource, cbs: List[CallbackTupleType] + self, curr_resource: Resource, cbs: List[CallbackTupleType], finished: Set[str] ) -> None: for rname in curr_resource.requires: - self._execute_callbacks_postorder(self._resources[rname], cbs) + if rname not in finished: + finished.add(rname) + self._execute_callbacks_postorder(self._resources[rname], cbs, finished) for filter_func, cb in cbs: if filter_func(curr_resource): @@ -372,9 +439,10 @@ def _get_saved_dialogue_state(self) -> DialogueStructureType: dialogue_struct: DialogueStructureType = { DIALOGUE_NAME_KEY: "", DIALOGUE_RESOURCES_KEY: {}, + DIALOGUE_LAST_INTERACTED_WITH_KEY: datetime.datetime.now(), } if cd: - ds_str = cd.get(DIALOGUE_DATA_KEY) + ds_str = cd.get(self._dialogue_name) if isinstance(ds_str, str) and ds_str != EMPTY_DIALOGUE_DATA: # TODO: Add try-except block dialogue_struct = json.loads(ds_str, cls=DialogueJSONDecoder) @@ -382,21 +450,30 @@ def _get_saved_dialogue_state(self) -> DialogueStructureType: def _set_dialogue_state(self, ds: DialogueStructureType) -> None: """Save the state of a dialogue for a client""" + # TODO: Add try-except block? ds_json: str = json.dumps(ds, cls=DialogueJSONEncoder) # Wrap data before saving dialogue state into client data # (due to custom JSON serialization) - cd = {DIALOGUE_DATA_KEY: ds_json} + cd = {self._dialogue_name: ds_json} self._q.set_client_data(DIALOGUE_KEY, cast(ClientDataDict, cd)) def end_dialogue(self) -> None: """End the client's current dialogue""" - # TODO: Remove line from database? - self._q.set_client_data(DIALOGUE_KEY, {DIALOGUE_DATA_KEY: EMPTY_DIALOGUE_DATA}) + # TODO: Doesn't allow multiple conversations at once + # (set_client_data overwrites other conversations) + self._q.set_client_data( + DIALOGUE_KEY, {self._dialogue_name: EMPTY_DIALOGUE_DATA} + ) def set_error(self) -> None: self._error = True +################################### +# ENCODING/DECODING CLASSES # +################################### + + class DialogueJSONEncoder(json.JSONEncoder): def default(self, o: Any) -> Any: # Add JSON encoding for any new classes here diff --git a/queries/fruitseller_module.py b/queries/fruitseller_module.py index 40412fc4..f6256f38 100644 --- a/queries/fruitseller_module.py +++ b/queries/fruitseller_module.py @@ -331,10 +331,8 @@ def _parse_no( if resource.name == "Fruits": if resource.is_partially_fulfilled: resource.state = ResourceState.FULFILLED - resource.set_answer("confirm", list_items=_list_items(resource.data)) elif resource.is_fulfilled: resource.state = ResourceState.PARTIALLY_FULFILLED - resource.set_answer("repeat", list_items=_list_items(resource.data)) if "callbacks" not in result: result["callbacks"] = [] @@ -433,13 +431,15 @@ def QFruitTime(node: Node, params: QueryStateDict, result: Result): if tnode: aux_str = tnode.aux.strip("[]") hour, minute, _ = (int(i) for i in aux_str.split(", ")) + if hour in range(0, 24) and minute in range(0, 60): + result["delivery_time"] = datetime.time(hour, minute) - result["delivery_time"] = datetime.time(hour, minute) - - if "callbacks" not in result: - result["callbacks"] = [] - filter_func: Callable[[Resource], bool] = lambda r: r.name == "Time" - result.callbacks.append((filter_func, _time_callback)) + if "callbacks" not in result: + result["callbacks"] = [] + filter_func: Callable[[Resource], bool] = lambda r: r.name == "Time" + result.callbacks.append((filter_func, _time_callback)) + else: + result["parse_error"] = True def QFruitDateTime(node: Node, params: QueryStateDict, result: Result) -> None: @@ -447,24 +447,20 @@ def QFruitDateTime(node: Node, params: QueryStateDict, result: Result) -> None: datetimenode = node.first_child(lambda n: True) assert isinstance(datetimenode, TerminalNode) now = datetime.datetime.now() + if "callbacks" not in result: + result["callbacks"] = [] y, m, d, h, min, _ = (i if i != 0 else None for i in json.loads(datetimenode.aux)) if y is None: y = now.year - if m is None: - m = now.month - if d is None: - d = now.day - if h is None: - h = 12 - if min is None: - min = 0 - result["delivery_time"] = datetime.time(h, min) - result["delivery_date"] = datetime.date(y, m, d) + if d is not None and m is not None: + result["delivery_date"] = datetime.date(y, m, d) + if result["delivery_date"] < now.date(): + result["delivery_date"].year += 1 + result.callbacks.append((lambda r: r.name == "Date", _date_callback)) - if "callbacks" not in result: - result["callbacks"] = [] - result.callbacks.append((lambda r: r.name == "Date", _date_callback)) - result.callbacks.append((lambda r: r.name == "Time", _time_callback)) + if h is not None and min is not None: + result["delivery_time"] = datetime.time(h, min) + result.callbacks.append((lambda r: r.name == "Time", _time_callback)) def QFruitInfoQuery(node: Node, params: QueryStateDict, result: Result): @@ -486,7 +482,7 @@ def sentence(state: QueryStateDict, result: Result) -> None: q: Query = state["query"] dsm = DialogueStateManager(_DIALOGUE_NAME, _START_DIALOGUE_QTYPE, q, result) - if dsm.not_in_dialogue(): + if dsm.not_in_dialogue() or result.get("parse_error"): q.set_error("E_QUERY_NOT_UNDERSTOOD") return diff --git a/queries/theater/theater.toml b/queries/theater/theater.toml index bc1a0cee..1a7adea2 100644 --- a/queries/theater/theater.toml +++ b/queries/theater/theater.toml @@ -3,6 +3,7 @@ dialogue_name = "theater" [[resources]] name = "Show" type = "ListResource" +max_items = 1 prompts.initial = "Hvaða sýningu má bjóða þér að fara á?" prompts.options = "Sýningarnar sem eru í boði eru: {options}" prompts.confirm = "Þú valdir sýninguna {show}, viltu halda áfram með pöntunina?" @@ -46,7 +47,7 @@ prompts.confirm = "Þú valdir svæðið {seat_location}, viltu halda áfram?" name = "Final" type = "FinalResource" requires = ["SeatLocation"] -prompts.final = "Þú valdir {seats} sæti á svæðinum {seat_location} fyrir sýninguna {show} þann {date}." +prompts.final = "Þú valdir {seats} sæti á svæðinu {seat_location} á sýninguna {show} þann {date}." prompts.cancelled = "Móttekið, hætti við leikhús pöntunina." # TODO: Add a resource for the payment method From bc40327640c259d336529d0525e37b3474d5ab53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Thu, 30 Jun 2022 14:49:12 +0000 Subject: [PATCH 131/371] Whole theater convo flow ready, just unexpected query answers left to handle --- queries/theater/theater.toml | 29 +++- queries/theater_module.py | 325 ++++++++++++++++++++++++++++++----- 2 files changed, 301 insertions(+), 53 deletions(-) diff --git a/queries/theater/theater.toml b/queries/theater/theater.toml index 03fd2749..b921d075 100644 --- a/queries/theater/theater.toml +++ b/queries/theater/theater.toml @@ -26,27 +26,40 @@ prompts.options = "Sýningardagsetningar eru: {options}" prompts.confirm = "Þú valdir dagsetninguna {date}, viltu halda áfram og velja fjölda sæta?" prompts.many_matching_times = "Margar dagsetningar pössuðu við gefna tímasetningu, vinsamlegast reyndu aftur." prompts.multiple_times_for_date = "Fyrir dagsetninguna {date} eru nokkrar tímasetningar, hverja af þeim viltu bóka?\nValmöguleikarnir eru:\n{times}" +prompts.no_date_matched = "Engin sýning er í boði fyrir gefna dagsetningu, vinsamlegast reyndu aftur." +prompts.no_time_matched = "Engin sýning er í boði fyrir gefna tímasetningu, vinsamlegast reyndu aftur." [[resources]] -name = "ShowSeats" +name = "ShowSeatCount" type = "NumberResource" requires = ["ShowDateTime"] prompts.initial = "Hversu mörg sæti viltu bóka?" prompts.confirm = "Þú valdir {seats} sæti, viltu halda áfram og velja staðsetningu sætanna?" [[resources]] -name = "SeatLocation" +name = "ShowSeatRow" type = "ListResource" -requires = ["ShowSeats"] -prompts.initial = "{seats} sæti eru í boði í röðum {seat_rows}. Á hvaða svæði viltu fá sæti?" -prompts.options = "Raðirnar með {seats} laus sæti eru: {seat_rows}." -prompts.confirm = "Þú valdir sæti {seats} í röð {row}, viltu halda áfram?" +requires = ["ShowSeatCount"] +prompts.initial = "Að minnsta kosti {seats} sæti eru í boði í röðum {seat_rows}. Í hvaða röð viltu sitja?" +prompts.options = "Raðirnar {rows} eru með {seats} laus sæti." +prompts.confirm = "Þú valdir röð {row}, viltu halda áfram?" +prompts.no_row_matched = "Því miður er þessi röð ekki með {seats} laus sæti. Vinsamlegast reyndu aftur." + +[[resources]] +name = "ShowSeatNumber" +type = "ListResource" +requires = ["ShowSeatRow"] +prompts.initial = "Sæti {seats} eru í boði í röð {row}, hvaða sæti má bjóða þér?" +prompts.options = "Sætin sem eru í boði í röð {row} eru {options}" +prompts.confirm = "Þú valdir sæti {seats}, viltu halda áfram?" +prompts.wrong_number_seats_selected = "Þú valdir {chosen_seats} sæti, en þú baðst um {seats}. Vinsamlegast reyndur aftur." +prompts.seats_unavailable = "Valin sæti eru ekki laus, vinsamlegast reyndu aftur." [[resources]] name = "Final" type = "FinalResource" -requires = ["SeatLocation"] -prompts.final = "Þú valdir {seats} sæti á svæðinum {seat_location} fyrir sýninguna {show} þann {date}." +requires = ["ShowSeatNumber"] +prompts.final = "Þú bókaðir sæti {seats} í röð {row} fyrir sýninguna {show} þann {date_time}." prompts.cancelled = "Móttekið, hætti við leikhús pöntunina." # TODO: Add a resource for the payment method diff --git a/queries/theater_module.py b/queries/theater_module.py index c2d62e02..381b1dda 100644 --- a/queries/theater_module.py +++ b/queries/theater_module.py @@ -30,7 +30,7 @@ from query import Query, QueryStateDict from tree import Result, Node, TerminalNode -from queries import gen_answer, parse_num, query_json_api +from queries import gen_answer, natlang_seq, parse_num, query_json_api from queries.num import number_to_neutral from queries.dialogue import ( DateResource, @@ -86,7 +86,7 @@ def help_text(lemma: str) -> str: QTheaterDialogue → QTheaterShowQuery | QTheaterShowDateQuery - | QTheaterShowSeatsQuery + | QTheaterShowSeatCountQuery | QTheaterShowLocationQuery | QTheaterShowOptions | QYes @@ -101,7 +101,7 @@ def help_text(lemma: str) -> str: QTheaterShowName → Nl QTheaterShowDateQuery → - "ég"? "vil"? "fara"? "á"? 'sýning'? QTheaterShowDate + QTheaterEgVil? "fara"? "á"? 'sýning'? QTheaterShowDate QTheaterShowDate → QTheaterDateTime | QTheaterDate | QTheaterTime @@ -116,22 +116,31 @@ def help_text(lemma: str) -> str: QTheaterTime → "klukkan"? tími -QTheaterShowSeatsQuery → - QTheaterEgVil "fá"? QNum "sæti"? +QTheaterShowSeatCountQuery → + QTheaterEgVil? "fá"? QNum "sæti"? QTheaterShowLocationQuery → - QLocationRowFirst - | QLocationSeatsFirst - -QLocationRowFirst → - "bekkur" QNum "sæti" QNum "til"? QNum - | "röð" QNum "sæti" QNum "til"? QNum - -QLocationSeatsFirst → - "ég"? "vil"? "sæti"? QNum "til"? QNum "í" "röð" QNum - | "ég"? "vil"? "sæti"? QNum "til"? QNum "í" QNum "röð" - | "ég"? "vil"? "sæti"? QNum "til"? QNum "á" "bekk" QNum - | "ég"? "vil"? "sæti"? QNum "til"? QNum "á" QNum "bekk" + QTheaterShowRow + | QTheaterShowSeats + +QTheaterShowRow → + QTheaterRodBekkur + | QTheaterEgVil QTheaterVeljaRod QTheaterRodBekkur + +QTheaterVeljaRod → + "velja" "sæti"? "í"? + | "sitja" "í" + | "fá" "sæti" "í" + | "fá" "sæti" "á" + +QTheaterRodBekkur → + "röð"? QNum + | "bekk" QNum + | QNum "bekk" + | QNum "röð" + +QTheaterShowSeats → + QTheaterEgVil? "sæti"? QNum "til" QNum QTheaterShowOptions → "sýningar" | "hvaða" "sýningar" "eru" "í" "boði" @@ -145,6 +154,7 @@ def help_text(lemma: str) -> str: "ég"? "vil" | "ég" "vill" | "mig" "langar" "að" + | "mig" "langar" "í" QNum → # to is a declinable number word ('tveir/tvo/tveim/tveggja') @@ -157,11 +167,21 @@ def help_text(lemma: str) -> str: QNo → "nei" "takk"? | "nei" "nei"* | "neitakk" | "ómögulega" QCancel → "ég" "hætti" "við" - | "ég" "vil" "hætta" "við" "pöntunina" - | "ég" "vill" "hætta" "við" "pöntunina" + | QTheaterEgVil "hætta" "við" "pöntunina" + | QTheaterEgVil "hætta" "við" "pöntunina" """ +# QLocationRowFirst → +# "bekkur" QNum "sæti" QNum "til"? QNum +# | "röð" QNum "sæti" QNum "til"? QNum + +# QLocationSeatsFirst → +# "ég"? "vil"? "sæti"? QNum "til"? QNum "í" "röð" QNum +# | "ég"? "vil"? "sæti"? QNum "til"? QNum "í" QNum "röð" +# | "ég"? "vil"? "sæti"? QNum "til"? QNum "á" "bekk" QNum +# | "ég"? "vil"? "sæti"? QNum "til"? QNum "á" QNum "bekk" + _SHOWS = [ { "title": "Emil í Kattholti", @@ -192,7 +212,7 @@ def _generate_show_answer( resource: ListResource, dsm: DialogueStateManager ) -> Optional[str]: result = dsm.get_result() - if result.get("showOptions"): + if result.get("show_options"): shows: list[str] = [] for show in _SHOWS: shows.append(show["title"]) @@ -216,6 +236,10 @@ def _generate_date_answer( result = dsm.get_result() if result.get("dateOptions"): return resource.prompts["options"] + if result.get("no_date_matched"): + return resource.prompts["no_date_matched"] + if result.get("no_time_matched"): + return resource.prompts["no_time_matched"] if result.get("many_matching_times"): return resource.prompts["many_matching_times"] if result.get("multiple_times_for_date"): @@ -235,7 +259,7 @@ def _generate_date_answer( date=show_date, times="".join(show_times) ) if resource.is_unfulfilled: - title = dsm.get_resource("Show").data[0] + title: str = dsm.get_resource("Show").data[0] dates: list[str] = [] for show in _SHOWS: if show["title"] == title: @@ -264,29 +288,124 @@ def _generate_date_answer( ) -def _generate_seat_answer( +def _generate_seat_count_answer( resource: ListResource, dsm: DialogueStateManager ) -> Optional[str]: - result = dsm.get_result() if resource.is_unfulfilled: return resource.prompts["initial"] if resource.is_fulfilled: return resource.prompts["confirm"].format(seats=resource.data[0]) +def _generate_row_answer( + resource: ListResource, dsm: DialogueStateManager +) -> Optional[str]: + result = dsm.get_result() + title: str = dsm.get_resource("Show").data[0] + seats: int = dsm.get_resource("ShowSeatCount").data[0] + available_rows: list[str] = [] + for show in _SHOWS: + if show["title"] == title: + checking_row: int = 1 + seats_in_row: int = 0 + for (row, _) in show["location"]: + if checking_row == row: + seats_in_row += 1 + if seats_in_row >= seats: + available_rows.append(str(row)) + seats_in_row = 0 + else: + checking_row = row + seats_in_row = 1 + for index, row in enumerate(available_rows): + available_rows[index] = number_to_neutral(row) + if result.get("rowOptions"): + return resource.prompts["options"].format(rows=available_rows, seats=seats) + if result.get("no_row_matched"): + return resource.prompts["no_row_matched"].format(seats=seats) + if resource.is_unfulfilled: + return resource.prompts["initial"].format( + seats=number_to_neutral(seats), seat_rows=natlang_seq(available_rows) + ) + if resource.is_fulfilled: + row = dsm.get_resource("ShowSeatRow").data[0] + return resource.prompts["confirm"].format(row=number_to_neutral(row)) + + +def _generate_seat_number_answer( + resource: ListResource, dsm: DialogueStateManager +) -> Optional[str]: + print("_generate_seat_number_answer", resource.state) + result = dsm.get_result() + title: str = dsm.get_resource("Show").data[0] + seats: int = dsm.get_resource("ShowSeatCount").data[0] + chosen_row: int = dsm.get_resource("ShowSeatRow").data[0] + available_seats: list[str] = [] + for show in _SHOWS: + if show["title"] == title: + for (row, seat) in show["location"]: + if chosen_row == row: + available_seats.append(number_to_neutral(seat)) + if result.get("seatOptions"): + return resource.prompts["options"].format( + row=number_to_neutral(row), options=natlang_seq(available_seats) + ) + if result.get("wrong_number_seats_selected"): + print("wrong_number_seats_selected prompt") + chosen_seats = len( + range(result.get("numbers")[0], result.get("numbers")[1] + 1) + ) + return resource.prompts["wrong_number_seats_selected"].format( + chosen_seats=number_to_neutral(chosen_seats), seats=number_to_neutral(seats) + ) + if result.get("seats_unavailable"): + print("seats_unavailable prompt") + return resource.prompts["seats_unavailable"] + if resource.is_unfulfilled: + print("initial prompt") + return resource.prompts["initial"].format( + seats=natlang_seq(available_seats), row=number_to_neutral(chosen_row) + ) + if resource.is_fulfilled: + print("confirm prompt") + chosen_seats_string: str = "{first_seat} til {last_seat}".format( + first_seat=number_to_neutral(result.get("numbers")[0]), + last_seat=number_to_neutral(result.get("numbers")[1]), + ) + return resource.prompts["confirm"].format(seats=chosen_seats_string) + + def _generate_location_answer( resource: ListResource, dsm: DialogueStateManager ) -> Optional[str]: result = dsm.get_result() - seat_resource = dsm.get_resource("ShowSeats") + seat_resource = dsm.get_resource("ShowSeatCount") if result.get("locationOptions"): return resource.prompts["options"] if resource.is_unfulfilled: + title: str = dsm.get_resource("Show").data[0] + seats: int = seat_resource.data[0] + available_rows: list[str] = [] + for show in _SHOWS: + if show["title"] == title: + checking_row: int = 1 + seats_in_row: int = 0 + for (row, seat) in show["location"]: + if checking_row == row: + seats_in_row += 1 + if seats_in_row >= seats: + available_rows.append(str(row)) + seats_in_row = 0 + else: + checking_row = row + seats_in_row = 1 + for index, row in enumerate(available_rows): + available_rows[index] = number_to_neutral(row) return resource.prompts["initial"].format( - seats=seat_resource.data[0], seat_rows=10 + seats=number_to_neutral(seats), seat_rows=natlang_seq(available_rows) ) if resource.is_fulfilled: - location_resource = dsm.get_resource("SeatLocation") + location_resource = dsm.get_resource("ShowSeatRow") number_to_neutral() seat_string = "{first_seat} til {last_seat}".format( first_seat=number_to_neutral(location_resource.data[0][1]), @@ -295,7 +414,6 @@ def _generate_location_answer( return resource.prompts["confirm"].format( seats=seat_string, row=location_resource.data[0][0] ) - return resource.prompts["confirm"].format(seats=result.get("location")) def _generate_final_answer( @@ -305,15 +423,23 @@ def _generate_final_answer( return resource.prompts["cancelled"] resource.state = ResourceState.CONFIRMED - seat_resource = dsm.get_resource("ShowSeats") - location_resource = dsm.get_resource("SeatLocation") - date_resource = dsm.get_resource("ShowDate") - show_resource = dsm.get_resource("Show") + title = dsm.get_resource("Show").data[0] + date = cast(DateResource, dsm.get_resource("ShowDate")).data + time = cast(TimeResource, dsm.get_resource("ShowTime")).data + seats = dsm.get_resource("ShowSeatNumber").data + seat_string: str = "{first_seat} til {last_seat}".format( + first_seat=number_to_neutral(seats[0]), + last_seat=number_to_neutral(seats[-1]), + ) + row = dsm.get_resource("ShowSeatRow").data[0] ans = resource.prompts["final"].format( - seats=seat_resource.data, - location=location_resource.data[0], - show=show_resource.data[0], - date=date_resource.data[0], + seats=seat_string, + row=row, + show=title, + date_time=datetime.datetime.combine( + date, + time, + ).strftime("%Y/%m/%d %H:%M"), ) return ans @@ -353,6 +479,7 @@ def _add_show( def _date_callback( resource: DateResource, dsm: DialogueStateManager, result: Result ) -> None: + resource.state = ResourceState.UNFULFILLED print("In date callback") if dsm.get_resource("Show").is_confirmed: print("Show was confirmed") @@ -386,6 +513,10 @@ def _date_callback( ) show_times.append(date.time()) print("Show times: ", show_times) + if len(show_times) == 0: + print("No show times found") + result.no_date_matched = True + return if len(show_times) == 1: time_resource.set_time(show_times[0]) time_resource.state = ResourceState.FULFILLED @@ -402,10 +533,14 @@ def _date_callback( def _time_callback( resource: TimeResource, dsm: DialogueStateManager, result: Result ) -> None: + resource.state = ResourceState.UNFULFILLED + if result.get("no_date_matched"): + return if dsm.get_resource("Show").is_confirmed: show_title: str = dsm.get_resource("Show").data[0] date_resource: DateResource = cast(DateResource, dsm.get_resource("ShowDate")) datetime_resource: Resource = dsm.get_resource("ShowDateTime") + first_matching_date: Optional[datetime.datetime] = None if date_resource.is_fulfilled: for show in _SHOWS: if show["title"] == show_title: @@ -414,6 +549,7 @@ def _time_callback( date_resource.date == date.date() and result["show_time"] == date.time() ): + first_matching_date = cast(datetime.datetime, date) print("Time callback, date there, setting time") resource.set_time(date.time()) resource.state = ResourceState.FULFILLED @@ -423,7 +559,6 @@ def _time_callback( else: result.wrong_show_time = True else: - first_matching_date: Optional[datetime.datetime] = None for show in _SHOWS: if show["title"] == show_title: for date in show["date"]: @@ -441,11 +576,14 @@ def _time_callback( ) date_resource.set_date(first_matching_date.date()) resource.set_time(first_matching_date.time()) + if first_matching_date is None: + result.no_time_matched = True else: dsm.set_error() def QTheaterDateTime(node: Node, params: QueryStateDict, result: Result) -> None: + print("In theater datetime") datetimenode = node.first_child(lambda n: True) assert isinstance(datetimenode, TerminalNode) now = datetime.datetime.now() @@ -462,6 +600,8 @@ def QTheaterDateTime(node: Node, params: QueryStateDict, result: Result) -> None min = 0 result["show_time"] = datetime.time(h, min) result["show_date"] = datetime.date(y, m, d) + print("Show date: ", result["show_date"]) + print("Show time: ", result["show_time"]) if "callbacks" not in result: result["callbacks"] = [] @@ -509,8 +649,10 @@ def QTheaterTime(node: Node, params: QueryStateDict, result: Result) -> None: result.callbacks.append((filter_func, _time_callback)) -def QTheaterShowSeatsQuery(node: Node, params: QueryStateDict, result: Result) -> None: - def _add_seats( +def QTheaterShowSeatCountQuery( + node: Node, params: QueryStateDict, result: Result +) -> None: + def _add_seat_number( resource: Resource, dsm: DialogueStateManager, result: Result ) -> None: resource.data = [result.number] @@ -518,13 +660,14 @@ def _add_seats( if "callbacks" not in result: result["callbacks"] = [] - filter_func: Callable[[Resource], bool] = lambda r: r.name == "ShowSeats" - result.callbacks.append((filter_func, _add_seats)) + filter_func: Callable[[Resource], bool] = lambda r: r.name == "ShowSeatCount" + result.callbacks.append((filter_func, _add_seat_number)) def QTheaterShowLocationQuery( node: Node, params: QueryStateDict, result: Result ) -> None: + """ print("In QTheaterShowLocationQuery") def _add_location( @@ -539,8 +682,10 @@ def _add_location( if "callbacks" not in result: result["callbacks"] = [] - filter_func: Callable[[Resource], bool] = lambda r: r.name == "SeatLocation" + filter_func: Callable[[Resource], bool] = lambda r: r.name == "ShowSeatRow" result.callbacks.append((filter_func, _add_location)) + """ + pass def QLocationSeatsFirst(node: Node, params: QueryStateDict, result: Result) -> None: @@ -549,8 +694,92 @@ def QLocationSeatsFirst(node: Node, params: QueryStateDict, result: Result) -> N print("Result numbers: ", result.numbers) +def QTheaterShowRow(node: Node, params: QueryStateDict, result: Result) -> None: + def _add_row( + resource: ListResource, dsm: DialogueStateManager, result: Result + ) -> None: + title: str = dsm.get_resource("Show").data[0] + seats: int = dsm.get_resource("ShowSeatCount").data[0] + available_rows: list[str] = [] + for show in _SHOWS: + if show["title"] == title: + checking_row: int = 1 + seats_in_row: int = 0 + for (row, _) in show["location"]: + if checking_row == row: + seats_in_row += 1 + if seats_in_row >= seats: + available_rows.append(row) + seats_in_row = 0 + else: + checking_row = row + seats_in_row = 1 + print("Add row: ", result.number) + print("Available rows: ", available_rows) + if result.number in available_rows: + print("Appending row") + resource.data.append(result.number) + resource.state = ResourceState.FULFILLED + else: + print("Emptying row data") + resource.data = [] + resource.state = ResourceState.UNFULFILLED + result.no_row_matched = True + + if "callbacks" not in result: + result["callbacks"] = [] + filter_func: Callable[[Resource], bool] = lambda r: r.name == "ShowSeatRow" + result.callbacks.append((filter_func, _add_row)) + + +def QTheaterShowSeats(node: Node, params: QueryStateDict, result: Result) -> None: + def _add_seats( + resource: ListResource, dsm: DialogueStateManager, result: Result + ) -> None: + print("Add seats callback, state: ", resource.state) + title: str = dsm.get_resource("Show").data[0] + print("Row data: ", dsm.get_resource("ShowSeatRow").data) + row: int = dsm.get_resource("ShowSeatRow").data[0] + number_of_seats: int = dsm.get_resource("ShowSeatCount").data[0] + print("Result.numbers: ", len(result.numbers)) + selected_seats: list[int] = [ + seat for seat in range(result.numbers[0], result.numbers[1] + 1) + ] + if len(selected_seats) != number_of_seats: + print("Selected seats does not match number of seats") + print("Resource name that is being emptied: ", resource.name) + resource.data = [] + resource.state = ResourceState.UNFULFILLED + result.wrong_number_seats_selected = True + return + for show in _SHOWS: + if show["title"] == title: + seats: list[int] = [] + for seat in selected_seats: + if (row, seat) in show["location"]: + seats.append(seat) + else: + print("Seat not unavailable") + resource.data = [] + resource.state = ResourceState.UNFULFILLED + result.seats_unavailable = True + return + for seat in seats: + print("Appending seat: ", seat) + resource.data.append(seat) + print("Length of data: ", len(resource.data)) + if len(resource.data) > 0: + print("Setting state to fulfilled") + resource.state = ResourceState.FULFILLED + + if "callbacks" not in result: + result["callbacks"] = [] + filter_func: Callable[[Resource], bool] = lambda r: r.name == "ShowSeatNumber" + result.callbacks.append((filter_func, _add_seats)) + + def QTheaterShowOptions(node: Node, params: QueryStateDict, result: Result) -> None: - result.showOptions = True + result.show_options = True def QTheaterShowName(node: Node, params: QueryStateDict, result: Result) -> None: @@ -587,6 +816,7 @@ def _parse_yes( resource: Resource, dsm: DialogueStateManager, result: Result ) -> None: if "yes_used" not in result and resource.is_fulfilled: + print("YES USED", resource.name, " confirming") resource.state = ResourceState.CONFIRMED result.yes_used = True if resource.name == "ShowDateTime": @@ -596,7 +826,9 @@ def _parse_yes( if "callbacks" not in result: result["callbacks"] = [] filter_func: Callable[[Resource], bool] = ( - lambda r: r.name in ("Show", "ShowDateTime", "ShowSeats") and not r.is_confirmed + lambda r: r.name + in ("Show", "ShowDateTime", "ShowSeatCount", "ShowSeatRow", "ShowSeatNumber") + and not r.is_confirmed ) result.callbacks.append((filter_func, _parse_yes)) @@ -614,8 +846,9 @@ def _fetch_shows() -> Any: _ANSWERING_FUNCTIONS = { "Show": _generate_show_answer, "ShowDateTime": _generate_date_answer, - "ShowSeats": _generate_seat_answer, - "SeatLocation": _generate_location_answer, + "ShowSeatCount": _generate_seat_count_answer, + "ShowSeatRow": _generate_row_answer, + "ShowSeatNumber": _generate_seat_number_answer, "Final": _generate_final_answer, } @@ -640,6 +873,8 @@ def sentence(state: QueryStateDict, result: Result) -> None: print("C") print(dsm._resources) ans = dsm.get_answer() + if "show_options" not in result: + q.query_is_command() print("D") if not ans: print("No answer generated") From 01c3d77cddf6939537a2708d3a41641911334464 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Thu, 30 Jun 2022 17:09:52 +0000 Subject: [PATCH 132/371] Made listing options available for every state that has options and fixed some edge cases --- queries/theater/theater.toml | 7 +- queries/theater_module.py | 219 +++++++++++++++++++---------------- 2 files changed, 126 insertions(+), 100 deletions(-) diff --git a/queries/theater/theater.toml b/queries/theater/theater.toml index b921d075..70952a2c 100644 --- a/queries/theater/theater.toml +++ b/queries/theater/theater.toml @@ -22,12 +22,14 @@ name = "ShowDateTime" type = "ListResource" requires = ["Show", "ShowDate", "ShowTime"] prompts.initial = "Hvenær viltu fara á sýninguna {show}?\n{dates}" -prompts.options = "Sýningardagsetningar eru: {options}" +prompts.options = "{options}" prompts.confirm = "Þú valdir dagsetninguna {date}, viltu halda áfram og velja fjölda sæta?" prompts.many_matching_times = "Margar dagsetningar pössuðu við gefna tímasetningu, vinsamlegast reyndu aftur." prompts.multiple_times_for_date = "Fyrir dagsetninguna {date} eru nokkrar tímasetningar, hverja af þeim viltu bóka?\nValmöguleikarnir eru:\n{times}" prompts.no_date_matched = "Engin sýning er í boði fyrir gefna dagsetningu, vinsamlegast reyndu aftur." prompts.no_time_matched = "Engin sýning er í boði fyrir gefna tímasetningu, vinsamlegast reyndu aftur." +prompts.no_date_available = "{show} hefur engar dagsetningar í boði. Vinsamlegast veldu aðra sýningu." +prompts.no_date_chosen = "Vinsamlegast veldu dagsetningu til að fá mögulegar tímasetningar." [[resources]] name = "ShowSeatCount" @@ -41,9 +43,10 @@ name = "ShowSeatRow" type = "ListResource" requires = ["ShowSeatCount"] prompts.initial = "Að minnsta kosti {seats} sæti eru í boði í röðum {seat_rows}. Í hvaða röð viltu sitja?" -prompts.options = "Raðirnar {rows} eru með {seats} laus sæti." +prompts.options = "Raðir {rows} eru með {seats} laus sæti." prompts.confirm = "Þú valdir röð {row}, viltu halda áfram?" prompts.no_row_matched = "Því miður er þessi röð ekki með {seats} laus sæti. Vinsamlegast reyndu aftur." +prompts.not_enough_seats = "Því miður er engin röð með {seats} sæti, vinsamlegast veldu færri sæti eða prófaðu aðra dagsetningu." [[resources]] name = "ShowSeatNumber" diff --git a/queries/theater_module.py b/queries/theater_module.py index 381b1dda..fb4857de 100644 --- a/queries/theater_module.py +++ b/queries/theater_module.py @@ -31,7 +31,7 @@ from query import Query, QueryStateDict from tree import Result, Node, TerminalNode from queries import gen_answer, natlang_seq, parse_num, query_json_api -from queries.num import number_to_neutral +from queries.num import number_to_text from queries.dialogue import ( DateResource, DialogueStateManager, @@ -88,12 +88,42 @@ def help_text(lemma: str) -> str: | QTheaterShowDateQuery | QTheaterShowSeatCountQuery | QTheaterShowLocationQuery - | QTheaterShowOptions + | QTheaterOptions | QYes | QNo | QCancel # TODO: Hvað er í boði, ég vil sýningu X, dagsetningu X, X mörg sæti, staðsetningu X +QTheaterOptions → + QTheaterGeneralOptions + | QTheaterShowOptions + | QTheaterDateOptions + | QTheaterRowOptions + | QTheaterSeatOptions + +QTheaterGeneralOptions → + "hverjir"? "eru"? "valmöguleikarnir" + | "hvert" "er" "úrvalið" + | "hvað" "er" "í" "boði" + +QTheaterShowOptions → + "hvaða" "sýningar" "eru" "í" "boði" + +QTheaterDateOptions → + "hvaða" "dagsetningar" "eru" "í" "boði" + | "hvaða" "dagar" "eru" "í" "boði" + | "hvaða" "dagsetningar" "er" "hægt" "að" "velja" "á" "milli" + +QTheaterRowOptions → + "hvaða" "raðir" "eru" "í" "boði" + | "hvaða" "röð" "er" "í" "boði" + | "hvaða" "bekkir" "eru" "í" "boði" + | "hvaða" "bekkur" "er" "í" "boði" + +QTheaterSeatOptions → + "hvaða" "sæti" "eru" "í" "boði" + "hverjir" "eru" "sæta" "valmöguleikarnir" + QTheaterShowQuery → QTheaterEgVil? "velja" 'sýning' QTheaterShowName > QTheaterEgVil? "fara" "á" 'sýning' QTheaterShowName > QTheaterShowName @@ -142,11 +172,8 @@ def help_text(lemma: str) -> str: QTheaterShowSeats → QTheaterEgVil? "sæti"? QNum "til" QNum -QTheaterShowOptions → "sýningar" - | "hvaða" "sýningar" "eru" "í" "boði" - | "hvað" "er" "í" "boði" - | "hverjir"? "eru"? "valmöguleikarnir" - | "hvert" "er" "úrvalið" +QTheaterDateOptions → + "hvaða" "dagsetningar" "eru" "í" "boði" QTheaterRodBekk → "röð" | "bekk" @@ -212,7 +239,9 @@ def _generate_show_answer( resource: ListResource, dsm: DialogueStateManager ) -> Optional[str]: result = dsm.get_result() - if result.get("show_options"): + if (not resource.is_confirmed and result.get("options_info")) or result.get( + "show_options" + ): shows: list[str] = [] for show in _SHOWS: shows.append(show["title"]) @@ -233,9 +262,31 @@ def _generate_show_answer( def _generate_date_answer( resource: ListResource, dsm: DialogueStateManager ) -> Optional[str]: + print("Generating date answer") result = dsm.get_result() - if result.get("dateOptions"): - return resource.prompts["options"] + title = dsm.get_resource("Show").data[0] + + if (not resource.is_confirmed and result.get("options_info")) or result.get( + "date_options" + ): + dates: list[str] = [] + for show in _SHOWS: + if show["title"] == title: + for date in show["date"]: + dates.append(date.strftime(" %d/%m/%Y klukkan %H:%M\n")) + date_number: int = 3 if len(dates) >= 3 else len(dates) + options_string: str = ( + "Eftirfarandi dagsetning er í boði:\n" + if date_number == 1 + else "Næstu tvær dagsetningar eru:\n" + if date_number == 2 + else "Næstu þrjár dagsetningar eru:\n" + ) + options_string += "".join(dates) + if len(dates) > 0: + return resource.prompts["options"].format(options=options_string) + else: + return resource.prompts["no_date_available"].format(show=title) if result.get("no_date_matched"): return resource.prompts["no_date_matched"] if result.get("no_time_matched"): @@ -243,7 +294,6 @@ def _generate_date_answer( if result.get("many_matching_times"): return resource.prompts["many_matching_times"] if result.get("multiple_times_for_date"): - title = dsm.get_resource("Show").data[0] show_date: Optional[datetime.date] = cast( DateResource, dsm.get_resource("ShowDate") ).date @@ -273,10 +323,13 @@ def _generate_date_answer( if date_number == 2 else "Næstu þrjár dagsetningar eru:\n" ) - return resource.prompts["initial"].format( - show=title, - dates=start_string + "".join(dates), - ) + if len(dates) > 0: + return resource.prompts["initial"].format( + show=title, + dates=start_string + "".join(dates), + ) + else: + return resource.prompts["no_date_available"].format(show=title) if resource.is_fulfilled: date_resource = dsm.get_resource("ShowDate") time_resource = dsm.get_resource("ShowTime") @@ -294,12 +347,15 @@ def _generate_seat_count_answer( if resource.is_unfulfilled: return resource.prompts["initial"] if resource.is_fulfilled: - return resource.prompts["confirm"].format(seats=resource.data[0]) + return resource.prompts["confirm"].format( + seats=number_to_text(cast(int, resource.data[0])) + ) def _generate_row_answer( resource: ListResource, dsm: DialogueStateManager ) -> Optional[str]: + print("Generating row answer") result = dsm.get_result() title: str = dsm.get_resource("Show").data[0] seats: int = dsm.get_resource("ShowSeatCount").data[0] @@ -308,28 +364,35 @@ def _generate_row_answer( if show["title"] == title: checking_row: int = 1 seats_in_row: int = 0 + row_added: int = 0 for (row, _) in show["location"]: - if checking_row == row: + if checking_row == row and row != row_added: seats_in_row += 1 if seats_in_row >= seats: - available_rows.append(str(row)) + available_rows.append(number_to_text(row)) seats_in_row = 0 + row_added = row else: checking_row = row seats_in_row = 1 - for index, row in enumerate(available_rows): - available_rows[index] = number_to_neutral(row) - if result.get("rowOptions"): - return resource.prompts["options"].format(rows=available_rows, seats=seats) + available_row_strings: list[str] = [] + if (not resource.is_confirmed and result.get("options_info")) or result.get( + "row_options" + ): + return resource.prompts["options"].format( + rows=natlang_seq(available_rows), seats=number_to_text(seats) + ) if result.get("no_row_matched"): return resource.prompts["no_row_matched"].format(seats=seats) if resource.is_unfulfilled: + if len(available_rows) == 0: + return resource.prompts["not_enough_seats"].format(seats=seats) return resource.prompts["initial"].format( - seats=number_to_neutral(seats), seat_rows=natlang_seq(available_rows) + seats=number_to_text(seats), seat_rows=natlang_seq(available_rows) ) if resource.is_fulfilled: row = dsm.get_resource("ShowSeatRow").data[0] - return resource.prompts["confirm"].format(row=number_to_neutral(row)) + return resource.prompts["confirm"].format(row=number_to_text(row)) def _generate_seat_number_answer( @@ -345,10 +408,12 @@ def _generate_seat_number_answer( if show["title"] == title: for (row, seat) in show["location"]: if chosen_row == row: - available_seats.append(number_to_neutral(seat)) - if result.get("seatOptions"): + available_seats.append(number_to_text(seat)) + if (not resource.is_confirmed and result.get("options_info")) or result.get( + "seat_options" + ): return resource.prompts["options"].format( - row=number_to_neutral(row), options=natlang_seq(available_seats) + row=number_to_text(chosen_row), options=natlang_seq(available_seats) ) if result.get("wrong_number_seats_selected"): print("wrong_number_seats_selected prompt") @@ -356,7 +421,7 @@ def _generate_seat_number_answer( range(result.get("numbers")[0], result.get("numbers")[1] + 1) ) return resource.prompts["wrong_number_seats_selected"].format( - chosen_seats=number_to_neutral(chosen_seats), seats=number_to_neutral(seats) + chosen_seats=number_to_text(chosen_seats), seats=number_to_text(seats) ) if result.get("seats_unavailable"): print("seats_unavailable prompt") @@ -364,58 +429,17 @@ def _generate_seat_number_answer( if resource.is_unfulfilled: print("initial prompt") return resource.prompts["initial"].format( - seats=natlang_seq(available_seats), row=number_to_neutral(chosen_row) + seats=natlang_seq(available_seats), row=number_to_text(chosen_row) ) if resource.is_fulfilled: print("confirm prompt") chosen_seats_string: str = "{first_seat} til {last_seat}".format( - first_seat=number_to_neutral(result.get("numbers")[0]), - last_seat=number_to_neutral(result.get("numbers")[1]), + first_seat=number_to_text(result.get("numbers")[0]), + last_seat=number_to_text(result.get("numbers")[1]), ) return resource.prompts["confirm"].format(seats=chosen_seats_string) -def _generate_location_answer( - resource: ListResource, dsm: DialogueStateManager -) -> Optional[str]: - result = dsm.get_result() - seat_resource = dsm.get_resource("ShowSeatCount") - if result.get("locationOptions"): - return resource.prompts["options"] - if resource.is_unfulfilled: - title: str = dsm.get_resource("Show").data[0] - seats: int = seat_resource.data[0] - available_rows: list[str] = [] - for show in _SHOWS: - if show["title"] == title: - checking_row: int = 1 - seats_in_row: int = 0 - for (row, seat) in show["location"]: - if checking_row == row: - seats_in_row += 1 - if seats_in_row >= seats: - available_rows.append(str(row)) - seats_in_row = 0 - else: - checking_row = row - seats_in_row = 1 - for index, row in enumerate(available_rows): - available_rows[index] = number_to_neutral(row) - return resource.prompts["initial"].format( - seats=number_to_neutral(seats), seat_rows=natlang_seq(available_rows) - ) - if resource.is_fulfilled: - location_resource = dsm.get_resource("ShowSeatRow") - number_to_neutral() - seat_string = "{first_seat} til {last_seat}".format( - first_seat=number_to_neutral(location_resource.data[0][1]), - last_seat=number_to_neutral(location_resource.data[-1][1]), - ) - return resource.prompts["confirm"].format( - seats=seat_string, row=location_resource.data[0][0] - ) - - def _generate_final_answer( resource: ListResource, dsm: DialogueStateManager ) -> Optional[str]: @@ -428,8 +452,8 @@ def _generate_final_answer( time = cast(TimeResource, dsm.get_resource("ShowTime")).data seats = dsm.get_resource("ShowSeatNumber").data seat_string: str = "{first_seat} til {last_seat}".format( - first_seat=number_to_neutral(seats[0]), - last_seat=number_to_neutral(seats[-1]), + first_seat=number_to_text(seats[0]), + last_seat=number_to_text(seats[-1]), ) row = dsm.get_resource("ShowSeatRow").data[0] ans = resource.prompts["final"].format( @@ -649,6 +673,10 @@ def QTheaterTime(node: Node, params: QueryStateDict, result: Result) -> None: result.callbacks.append((filter_func, _time_callback)) +def QTheaterDateOptions(node: Node, params: QueryStateDict, result: Result) -> None: + result.date_options = True + + def QTheaterShowSeatCountQuery( node: Node, params: QueryStateDict, result: Result ) -> None: @@ -664,30 +692,6 @@ def _add_seat_number( result.callbacks.append((filter_func, _add_seat_number)) -def QTheaterShowLocationQuery( - node: Node, params: QueryStateDict, result: Result -) -> None: - """ - print("In QTheaterShowLocationQuery") - - def _add_location( - resource: ListResource, dsm: DialogueStateManager, result: Result - ) -> None: - print("ADD LOCATION CALLBACK") - for seat in range(result.numbers[1], result.numbers[2] + 1): - print("Adding seat to list: ", seat) - resource.data.append((result.numbers[0], seat)) - print("Location data: ", resource.data) - resource.state = ResourceState.FULFILLED - - if "callbacks" not in result: - result["callbacks"] = [] - filter_func: Callable[[Resource], bool] = lambda r: r.name == "ShowSeatRow" - result.callbacks.append((filter_func, _add_location)) - """ - pass - - def QLocationSeatsFirst(node: Node, params: QueryStateDict, result: Result) -> None: # Making sure that the row comes before the seats in the list result.numbers.insert(0, result.numbers.pop()) @@ -778,10 +782,29 @@ def _add_seats( result.callbacks.append((filter_func, _add_seats)) +def QTheaterGeneralOptions(node: Node, params: QueryStateDict, result: Result) -> None: + print("QTheaterGeneralOptions") + result.options_info = True + + def QTheaterShowOptions(node: Node, params: QueryStateDict, result: Result) -> None: + print("QTheaterShowOptions") result.show_options = True +def QTheaterDateOptions(node: Node, params: QueryStateDict, result: Result) -> None: + print("QTheaterDateOptions") + result.date_options = True + + +def QTheaterRowOptions(node: Node, params: QueryStateDict, result: Result) -> None: + result.row_options = True + + +def QTheaterSeatOptions(node: Node, params: QueryStateDict, result: Result) -> None: + result.seat_options = True + + def QTheaterShowName(node: Node, params: QueryStateDict, result: Result) -> None: result.show_name = ( " ".join(result._text.split()[1:]) From c86fd23f2db24257643d3e98dfe7c721bb0e73b8 Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Thu, 30 Jun 2022 23:46:56 +0000 Subject: [PATCH 133/371] added music hotword banword --- queries/iot_hue.py | 166 ++++++++++++++++++++++------------------ queries/iot_speakers.py | 23 ++++-- 2 files changed, 106 insertions(+), 83 deletions(-) diff --git a/queries/iot_hue.py b/queries/iot_hue.py index 75ba7e5b..b5b16610 100755 --- a/queries/iot_hue.py +++ b/queries/iot_hue.py @@ -33,7 +33,7 @@ # TODO: No specified location # TODO: Fix scene issues -from typing import Dict, Mapping, Optional, cast +from typing import Dict, Mapping, Optional, cast, FrozenSet from typing_extensions import TypedDict import logging @@ -41,6 +41,8 @@ import json import flask +from reynir.lemmatize import simple_lemmatize + from query import Query, QueryStateDict, AnswerTuple from queries import gen_answer, read_jsfile, read_grammar_file from tree import Result, Node @@ -574,86 +576,98 @@ def QIoTLightName(node: Node, params: QueryStateDict, result: Result) -> None: # "Rauð": 360 * 65535 / 360, } +_SPEAKER_WORDS: FrozenSet[str] = frozenset( + ( + "tónlist", + "hátalari", + ) +) + def sentence(state: QueryStateDict, result: Result) -> None: """Called when sentence processing is complete""" q: Query = state["query"] - changing_color = result.get("changing_color", False) - changing_scene = result.get("changing_scene", False) - changing_brightness = result.get("changing_brightness", False) - print("error?", sum((changing_color, changing_scene, changing_brightness)) > 1) - if ( - sum((changing_color, changing_scene, changing_brightness)) > 1 - or "qtype" not in result - ): + lemmas = set(i[0] for i in simple_lemmatize(q.query.lower().split())) + if not _SPEAKER_WORDS.isdisjoint(lemmas): + print("matched with music word list") q.set_error("E_QUERY_NOT_UNDERSTOOD") - return - - q.set_qtype(result.qtype) - - smartdevice_type = "iot_lights" - client_id = str(q.client_id) - print("client_id:", client_id) - - # Fetch relevant data from the device_data table to perform an action on the lights - device_data = cast(Optional[DeviceData], q.client_data(smartdevice_type)) - print("location :", q.location) - print("device data :", device_data) - - selected_light: Optional[str] = None - print("selected light:", selected_light) - hue_credentials: Optional[Dict[str, str]] = None - - if device_data is not None: - dev = device_data - assert dev is not None - light = dev.get("philips_hue") - hue_credentials = light.get("credentials") - bridge_ip = hue_credentials.get("ip_address") - username = hue_credentials.get("username") - - if not device_data or not hue_credentials: - answer = "Það vantar að tengja Philips Hub-inn." - q.set_answer(*gen_answer(answer)) - return - - # Successfully matched a query type - print("bridge_ip: ", bridge_ip) - print("username: ", username) - print("selected light :", selected_light) - print("hue credentials :", hue_credentials) - - try: - # kalla í javascripts stuff - light_or_group_name = result.get("light_name", result.get("group_name", "")) - color_name = result.get("color_name", "") - print("GROUP NAME:", light_or_group_name) - print("COLOR NAME:", color_name) - print(result.hue_obj) - q.set_answer( - *gen_answer( - "ég var að kveikja ljósin! " - # + group_name - # + " " - # + color_name - # + " " - # + result.action - # + " " - # + str(result.hue_obj.get("hue", "enginn litur")) + else: + changing_color = result.get("changing_color", False) + changing_scene = result.get("changing_scene", False) + changing_brightness = result.get("changing_brightness", False) + print("error?", sum((changing_color, changing_scene, changing_brightness)) > 1) + if ( + sum((changing_color, changing_scene, changing_brightness)) > 1 + or "qtype" not in result + ): + q.set_error("E_QUERY_NOT_UNDERSTOOD") + return + + q.set_qtype(result.qtype) + + smartdevice_type = "iot_lights" + client_id = str(q.client_id) + print("client_id:", client_id) + + # Fetch relevant data from the device_data table to perform an action on the lights + device_data = cast(Optional[DeviceData], q.client_data(smartdevice_type)) + print("location :", q.location) + print("device data :", device_data) + + selected_light: Optional[str] = None + print("selected light:", selected_light) + hue_credentials: Optional[Dict[str, str]] = None + + if device_data is not None: + dev = device_data + assert dev is not None + light = dev.get("philips_hue") + hue_credentials = light.get("credentials") + bridge_ip = hue_credentials.get("ip_address") + username = hue_credentials.get("username") + + if not device_data or not hue_credentials: + answer = "Það vantar að tengja Philips Hub-inn." + q.set_answer(*gen_answer(answer)) + return + + # Successfully matched a query type + print("bridge_ip: ", bridge_ip) + print("username: ", username) + print("selected light :", selected_light) + print("hue credentials :", hue_credentials) + + try: + # kalla í javascripts stuff + light_or_group_name = result.get("light_name", result.get("group_name", "")) + color_name = result.get("color_name", "") + print("GROUP NAME:", light_or_group_name) + print("COLOR NAME:", color_name) + print(result.hue_obj) + q.set_answer( + *gen_answer( + "ég var að kveikja ljósin! " + # + group_name + # + " " + # + color_name + # + " " + # + result.action + # + " " + # + str(result.hue_obj.get("hue", "enginn litur")) + ) ) - ) - js = ( - read_jsfile("IoT_Embla/fuse.js") - + f"var BRIDGE_IP = '{bridge_ip}';var USERNAME = '{username}';" - + read_jsfile("IoT_Embla/Philips_Hue/fuse_search.js") - + read_jsfile("IoT_Embla/Philips_Hue/lights.js") - + read_jsfile("IoT_Embla/Philips_Hue/set_lights.js") - ) - js += f"setLights('{light_or_group_name}', '{json.dumps(result.hue_obj)}');" - q.set_command(js) - except Exception as e: - logging.warning("Exception while processing random query: {0}".format(e)) - q.set_error("E_EXCEPTION: {0}".format(e)) - raise + js = ( + read_jsfile("IoT_Embla/fuse.js") + + f"var BRIDGE_IP = '{bridge_ip}';var USERNAME = '{username}';" + + read_jsfile("IoT_Embla/Philips_Hue/fuse_search.js") + + read_jsfile("IoT_Embla/Philips_Hue/lights.js") + + read_jsfile("IoT_Embla/Philips_Hue/set_lights.js") + ) + js += f"setLights('{light_or_group_name}', '{json.dumps(result.hue_obj)}');" + q.set_command(js) + except Exception as e: + logging.warning("Exception while processing random query: {0}".format(e)) + q.set_error("E_EXCEPTION: {0}".format(e)) + raise # f"var BRIDGE_IP = '192.168.1.68';var USERNAME = 'p3obluiXT13IbHMpp4X63ZvZnpNRdbqqMt723gy2';" diff --git a/queries/iot_speakers.py b/queries/iot_speakers.py index 63c85bad..5e41ad6d 100644 --- a/queries/iot_speakers.py +++ b/queries/iot_speakers.py @@ -43,11 +43,13 @@ import flask from datetime import datetime, timedelta +from reynir.lemmatize import simple_lemmatize + from query import Query, QueryStateDict, AnswerTuple from queries import gen_answer, read_jsfile, read_grammar_file from queries.sonos import SonosClient, update_sonos_token +from tree import Result, Node, TerminalNode from util import read_api_key -from tree import Result, Node _IoT_QTYPE = "IoT" @@ -174,7 +176,7 @@ def help_text(lemma: str) -> str: # | QCHANGEHvar? QCHANGEHvernigLet QCHANGESubject/þf # | QCHANGEHvernigLet QCHANGESubject/þf QCHANGEHvar? # | QCHANGEHvernigLet QCHANGEHvar? QCHANGESubject/þf - 'Vera' QIoTSpeakerMusicWord QIoTSpeakerHvar? + QIoTSpeakerBe QIoTSpeakerMusicWord QIoTSpeakerHvar? | "á" QIoTSpeakerMusicWord QIoTSpeakerHvar? QIoTSpeakerTurnOnRest -> @@ -251,8 +253,8 @@ def help_text(lemma: str) -> str: # I think these verbs only appear in these forms. # In which case these terminals should be deleted and a direct reference should be made in the relevant non-terminals. -# QCHANGEBe -> -# "vera" +QIoTSpeakerBe -> + 'vera:so'_nh # QCHANGEBecome -> # "verða" @@ -426,8 +428,6 @@ def help_text(lemma: str) -> str: # QCHANGESettingWord/fall -> # 'stilling'/fall -$score(+35) QIoTSpeakerMusicWord - """ @@ -451,6 +451,13 @@ def QIoTMusicWord(node: Node, params: QueryStateDict, result: Result) -> None: def sentence(state: QueryStateDict, result: Result) -> None: # try: print("sentence") + print( + "DESCENDANTS:", + list( + i[0].root(state, result.params) + for i in result.enum_descendants(lambda x: isinstance(x, TerminalNode)) + ), + ) """Called when sentence processing is complete""" q: Query = state["query"] @@ -508,7 +515,9 @@ def sentence(state: QueryStateDict, result: Result) -> None: # sonos_client.set_volume(result.get["volume"]) answer_list = gen_answer(answer) - answer_list[1].replace("Sonos", "Sónos") + answer_list[1].replace( + "Sonos", "Sónos" + ) # Accounting for Embla's Icelandic pronunciation q.set_answer(*answer_list) # f"var BRIDGE_IP = '192.168.1.68';var USERNAME = 'p3obluiXT13IbHMpp4X63ZvZnpNRdbqqMt723gy2';" From e5cdc144e9778c97fbc7be9a807b29932ec7c7f7 Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Fri, 1 Jul 2022 10:38:28 +0000 Subject: [PATCH 134/371] SonosClient v1 --- queries/__init__.py | 29 ++- queries/iot_connect.py | 68 +++--- queries/iot_speakers.py | 27 +-- queries/sonos.py | 475 ++++++++++++++++++++++++++++------------ query.py | 1 + routes/api.py | 15 +- 6 files changed, 417 insertions(+), 198 deletions(-) diff --git a/queries/__init__.py b/queries/__init__.py index 4e2ece21..beff2e49 100755 --- a/queries/__init__.py +++ b/queries/__init__.py @@ -453,6 +453,33 @@ def query_json_api( return None +def post_to_json_api( + url: str, json_data: Optional[Any] = None, headers: Optional[Dict[str, str]] = None +) -> Union[None, List[Any], Dict[str, Any]]: + """Send a POST request to the URL, expecting a JSON response which is + parsed and returned as a Python data structure.""" + + # Send request + try: + r = requests.post(url, data=json_data, headers=headers) + except Exception as e: + logging.warning(str(e)) + return None + + # Verify that status is OK + if r.status_code not in range(200, 300): + logging.warning("Received status {0} from API server".format(r.status_code)) + return None + + # Parse json API response + try: + res = json.loads(r.text) + return res + except Exception as e: + logging.warning("Error parsing JSON API response: {0}".format(e)) + return None + + def query_xml_api(url: str) -> Any: """Request the URL, expecting an XML response which is parsed and returned as an XML document object.""" @@ -672,7 +699,7 @@ def read_jsfile(filename: str) -> str: fpath = os.path.join(basepath, "js", filename) with open(fpath, mode="r") as file: return cast(str, jsmin(file.read())) - + def read_grammar_file(filename: str, **format_kwargs: str) -> str: """ diff --git a/queries/iot_connect.py b/queries/iot_connect.py index 7ee772c6..40083134 100644 --- a/queries/iot_connect.py +++ b/queries/iot_connect.py @@ -39,6 +39,7 @@ from query import Query, QueryStateDict, AnswerTuple from queries import gen_answer, read_jsfile, read_grammar_file +from queries.sonos import SonosClient from tree import Result, Node from routes import better_jsonify from util import read_api_key @@ -179,39 +180,46 @@ def sentence(state: QueryStateDict, result: Result) -> None: elif result.qtype == "create_speaker_token": device_data = q.client_data("iot_speakers") - code = device_data.get("sonos").get("credentials").get("code") + try: + code = device_data.get("sonos").get("credentials").get("code") or None + except AttributeError: + print("Missing device data") if device_data is None or code is None: - q.set_error("E_NO_DEVICE_DATA") + print("Missing device data or code") + q.set_error("Missing sonos code") return - sonos_encoded_credentials = read_api_key("SonosEncodedCredentials") - response = create_token(code, sonos_encoded_credentials, host) - if response.status_code != 200: - print("Error:", response.status_code) - print(response.text) - print("Invalid request usually means that the code is invalid") - return - response_json = response.json() - access_token, refresh_token = ( - response_json.get("access_token"), - response_json.get("refresh_token"), - ) - timestamp = str(datetime.now()) - data_dict = create_sonos_data_dict(access_token, q) - cred_dict = create_sonos_cred_dict(access_token, refresh_token, timestamp, q) - print("data dict for update", data_dict) - print("cred dict for update", data_dict) - store_sonos_data_and_credentials(data_dict, cred_dict, q) + sonos_client = SonosClient(device_data, q) + # code = device_data.get("sonos").get("credentials").get("code") + # if device_data is None or code is None: + # q.set_error("Missing sonos code") + # return + # sonos_encoded_credentials = read_api_key("SonosEncodedCredentials") + # response = create_token(code, sonos_encoded_credentials, host) + # response = sonos_client.create_token() + # access_token, refresh_token = ( + # response.get("access_token"), + # response.get("refresh_token"), + # ) + # if access_token is None or refresh_token is None: + # q.set_error("Missing sonos access token") + # return + # sonos_client.set_credentials(access_token, refresh_token) + sonos_client.set_data() + # timestamp = str(datetime.now()) + # data_dict = sonos_client.create_sonos_data_dict() + # cred_dict = sonos_client.create_sonos_cred_dict() + # print("data dict for update", data_dict) + # print("cred dict for update", cred_dict) + sonos_client.store_sonos_data_and_credentials() + answer = "Ég bjó til tóka frá Sónos" + response = dict(answer=answer) voice_answer = answer - # voice_answer = f"Ég ætla að tengja Sónos hátalarann. Hlustaðu vel. {_BREAK_SSML} Ég tengdi Sónos hátalarann. Góða skemmtun." - # sonos_voice_clip = ( - # f"{_BREAK_SSML} Hæ!, ég er búin að tengja þennan Sónos hátalara." - # ) - # audio_clip( - # text_to_audio_url(sonos_voice_clip), - # sonos_dict["player_id"], - # sonos_dict["access_token"], - # ) + voice_answer = f"Ég ætla að tengja Sónos hátalarann. Hlustaðu vel. {_BREAK_SSML} Ég tengdi Sónos hátalarann. Góða skemmtun." + sonos_voice_clip = ( + f"{_BREAK_SSML} Hæ!, ég er búin að tengja þennan Sónos hátalara." + ) + sonos_client.audio_clip(text_to_audio_url(sonos_voice_clip)) q.set_answer(response, answer, voice_answer) return @@ -312,7 +320,7 @@ def create_token(code, sonos_encoded_credentials, host): response = requests.request("POST", url, headers=headers, data=payload) - return response + return response.json() def toggle_play_pause(group_id, token): diff --git a/queries/iot_speakers.py b/queries/iot_speakers.py index 80e8041e..2221a6d1 100644 --- a/queries/iot_speakers.py +++ b/queries/iot_speakers.py @@ -45,7 +45,7 @@ from query import Query, QueryStateDict, AnswerTuple from queries import gen_answer, read_jsfile, read_grammar_file -from queries.sonos import SonosClient, update_sonos_token +from queries.sonos import SonosClient from util import read_api_key from tree import Result, Node @@ -459,35 +459,12 @@ def sentence(state: QueryStateDict, result: Result) -> None: smartdevice_type = "smart_speaker" # Fetch relevant data from the device_data table to perform an action on the lights - # sonos_code = q.client_data("sonos_code") device_data = q.client_data("iot_speakers") print(device_data) # TODO: Need to add check for if there are no registered devices to an account, probably when initilazing the querydata if device_data is not None: - # timestamp = device_data["sonos"]["credentials"]["timestamp"] - # timestamp = datetime.strptime(timestamp, "%Y-%m-%d %H:%M:%S.%f") - # if (datetime.now() - timestamp) > timedelta(hours=24): - # update_sonos_token(q, device_data) - # print("if clause") - # try: - # access_token = device_data["sonos"]["credentials"]["access_token"] - # refresh_token = device_data["sonos"]["credentials"]["refresh_token"] - # household_id = device_data["sonos"]["data"]["households"][0]["id"] - # group_id = device_data["sonos"]["data"]["groups"][0]["Family Room"] - # player_id = device_data["sonos"]["data"]["players"][0]["Family Room"] - # except KeyError: - # print("No device data found for this account") - - # Successfully fetched data from the device_data table - # print("access_token: " + access_token) - # print("refresh_token: " + refresh_token) - # print("household_id: " + household_id) - # print("group_id: " + group_id) - # print("player_id: " + player_id) - - # Create a SonosClient object - sonos_client = SonosClient(device_data, q) + sonos_client = SonosClient(device_data, q.client_id) else: print("No device data found for this account") return diff --git a/queries/sonos.py b/queries/sonos.py index aff6e9ec..df0387bc 100644 --- a/queries/sonos.py +++ b/queries/sonos.py @@ -24,15 +24,25 @@ import requests from datetime import datetime, timedelta +import flask + from util import read_api_key +from queries import query_json_api, post_to_json_api +from query import Query + +import json + -# TODO: Refresh token functionality class SonosClient: - def __init__(self, device_data, q, query=None): - self._q = q + def __init__(self, device_data, client_id, query=None): + self._client_id = client_id self._device_data = device_data self._query = query - + self._sonos_encoded_credentials = read_api_key("SonosEncodedCredentials") + self._code = self._device_data["sonos"]["credentials"]["code"] + print("code :", self._code) + self._timestamp = datetime.now() + print("device data :", self._device_data) try: self._access_token = self._device_data["sonos"]["credentials"][ "access_token" @@ -40,34 +50,35 @@ def __init__(self, device_data, q, query=None): self._refresh_token = self._device_data["sonos"]["credentials"][ "refresh_token" ] - self.check_token_expiration() - self._households = ( - self._device_data["sonos"]["data"]["households"] - or self.get_households() - ) - self._household_id = self.get_household_id() - self._groups = ( - self._device_data["sonos"]["data"]["groups"][0]["Family Room"] - or self.get_groups() - ) - self._players = ( - self._device_data["sonos"]["data"]["players"][0]["Family Room"] - or self.get_players() - ) - self._group_id = self.get_group_id() except KeyError: - print("Missing device data found for this account") - - def check_token_expiration(self): - timestamp = self._device_data["sonos"]["credentials"]["timestamp"] + print("Missing credentials for sonos") + self._create_token() + print("continue") + self._check_token_expiration() + self._households = self._get_households() + print("households :", self._households) + self._household_id = self._households[0]["id"] + # groups_and_players = self._get_groups_and_players() + self._groups = self._get_groups() + self._players = self._get_players() + print("exited get groups") + print(self._groups) + self.store_sonos_data_and_credentials() + + def _check_token_expiration(self): + try: + timestamp = self._device_data["sonos"]["credentials"]["timestamp"] + except KeyError: + print("Missing timestamp for sonos access token") + return timestamp = datetime.strptime(timestamp, "%Y-%m-%d %H:%M:%S.%f") if (datetime.now() - timestamp) > timedelta(hours=24): - self.update_sonos_token() + self._update_sonos_token() - def update_sonos_token(self): + def _update_sonos_token(self): print("update sonos token") self._sonos_encoded_credentials = read_api_key("SonosEncodedCredentials") - self._access_token = self.refresh_token() + self._access_token = self._refresh_expired_token() self._access_token = self._access_token["access_token"] sonos_dict = { "sonos": { @@ -78,195 +89,381 @@ def update_sonos_token(self): } } - self._q.set_client_data("iot_speakers", sonos_dict, update_in_place=True) + self._store_data(sonos_dict) - def refresh_token(self): + def _refresh_expired_token(self): """ Refreshes token """ + print("_refresh_expired_token") url = f"https://api.sonos.com/login/v3/oauth/access?grant_type=refresh_token&refresh_token={self._refresh_token}" - - payload = {} headers = {"Authorization": f"Basic {self._sonos_encoded_credentials}"} - response = requests.request("POST", url, headers=headers, data=payload) + response = post_to_json_api(url, headers=headers) - return response.json() + return response + + def _create_token(self): + """ + Creates a token given a code + """ + print("_create_token") + host = str(flask.request.host) + url = f"https://api.sonos.com/login/v3/oauth/access?grant_type=authorization_code&code={self._code}&redirect_uri=http://{host}/connect_sonos.api" + headers = { + "Authorization": f"Basic {self._sonos_encoded_credentials}", + "Cookie": "JSESSIONID=F710019AF0A3B7126A8702577C883B5F; AWSELB=69BFEFC914A689BF6DC8E4652748D7B501ED60290D5EA56F2E543ABD7CF357A5F65186AEBCFB059E28075D83A700FD504C030A53CC28683B515BE3DCA3CC587AFAF606E171; AWSELBCORS=69BFEFC914A689BF6DC8E4652748D7B501ED60290D5EA56F2E543ABD7CF357A5F65186AEBCFB059E28075D83A700FD504C030A53CC28683B515BE3DCA3CC587AFAF606E171", + } + + response = post_to_json_api(url, headers=headers) + + self._access_token = response.get("access_token") + self._refresh_token = response.get("refresh_token") + return response def toggle_play_pause(self): """ Toggles the play/pause of a group """ - url = f"https://api.ws.sonos.com/control/api/v1/groups/{self._group_id}/playback/togglePlayPause" - - payload = {} + print("toggle playpause") + group_id = self._get_group_id() + url = f"https://api.ws.sonos.com/control/api/v1/groups/{group_id}/playback/togglePlayPause" headers = { "Content-Type": "application/json", "Authorization": f"Bearer {self._access_token}", } - response = requests.request("POST", url, headers=headers, data=payload) + # response = requests.request("POST", url, headers=headers, data=payload) + response = post_to_json_api(url, headers=headers) return response - def get_households(self): + def _get_households(self): """ Returns the list of households of the user """ - url = f"https://api.ws.sonos.com/control/api/v1/households" - - payload = {} - headers = {"Authorization": f"Bearer {self._access_token}"} - - response = requests.request("GET", url, headers=headers, data=payload) + print("get households") + try: + print("households try") + return self._device_data["sonos"]["data"]["households"] + except KeyError: + print("key error get households") + url = f"https://api.ws.sonos.com/control/api/v1/households" + headers = {"Authorization": f"Bearer {self._access_token}"} - return response.json() + response = query_json_api(url, headers=headers) + return response["households"] - def get_household_id(self): + def _get_household_id(self): """ Returns the household id for the given query """ url = f"https://api.ws.sonos.com/control/api/v1/households" - - payload = {} headers = { "Content-Type": "application/json", "Authorization": f"Bearer {self._access_token}", } - response = requests.request("GET", url, headers=headers, data=payload) + response = query_json_api(url, headers) - return response.json()["households"][0]["id"] + return response["households"][0]["id"] - def get_groups(self): + def _get_groups(self): """ Returns the list of groups of the user """ - url = f"https://api.ws.sonos.com/control/api/v1/households/{self.household_id}/groups" + print("get groups") + try: + print("try get groups") + print("device data get groups :", self._device_data) + return self._device_data["sonos"]["data"]["groups"] + except KeyError: + print("keyerror") + for i in range(len(self._households)): + url = f"https://api.ws.sonos.com/control/api/v1/households/{self._household_id}/groups" + headers = {"Authorization": f"Bearer {self._access_token}"} - payload = {} - headers = {"Authorization": f"Bearer {self._access_token}"} + response = query_json_api(url, headers=headers) + cleaned_groups_list = self._create_grouplist_for_db(response["groups"]) + print("cleaned_groups_list :", cleaned_groups_list) + return cleaned_groups_list - response = requests.request("GET", url, headers=headers, data=payload) + def get_groups_and_players(self): + """ + Returns the list of groups of the user + """ + print("get groups and players") + url = f"https://api.ws.sonos.com/control/api/v1/households/{self._household_id}/groups" + headers = {"Authorization": f"Bearer {self._access_token}"} + + response = query_json_api(url, headers) return response.json() + # return response.json()["groups"] - def get_group_id(self): + def _get_group_id(self): """ Returns the group id for the given query """ - url = f"https://api.ws.sonos.com/control/api/v1/households/{self._household_id}/groups" + print("get group_id") + try: + group_id = self._groups[0]["id"] + return group_id + except KeyError: + print("_get_group_id keyerror") + url = f"https://api.ws.sonos.com/control/api/v1/households/{self._household_id}/groups" + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {self._access_token}", + } + + response = query_json_api(url, headers) + + return response["groups"][0]["id"] + + def _get_players(self): + """ + Returns the list of groups of the user + """ + print("get players") + try: + print("try get players") + print("device data get players :", self._device_data) + return self._device_data["sonos"]["data"]["players"] + except KeyError: + print("keyerror") + for i in range(len(self._households)): + url = f"https://api.ws.sonos.com/control/api/v1/households/{self._household_id}/groups" + headers = {"Authorization": f"Bearer {self._access_token}"} + + response = query_json_api(url, headers) + cleaned_players_list = self._create_playerlist_for_db( + response["players"] + ) + print("cleaned_groups_list :", cleaned_players_list) + return cleaned_players_list + + def _get_player_id(self): + """ + Returns the player id for the given query + """ + print("get player_id") + try: + player_id = self._players[0]["id"] + return player_id + except KeyError: + print("_get_player_id keyerror") + url = f"https://api.ws.sonos.com/control/api/v1/households/{self._household_id}/groups" + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {self._access_token}", + } - payload = {} + response = query_json_api(url, headers) + + return response["players"][0]["id"] + + def _create_sonos_data_dict(self): + print("_create_sonos_data_dict") + data_dict = {"households": self._households} + # groups_list = [] + # players_list = [] + for i in range(len(self._households)): + groups_raw = self._groups + players_raw = self._players + # groups_list += self._create_grouplist_for_db(groups_raw) + groups_list = self._groups + players_list = self._players + + data_dict["groups"] = groups_list + data_dict["players"] = players_list + return data_dict + + def _create_sonos_cred_dict(self): + print("_create_sonos_cred_dict") + cred_dict = {} + cred_dict.update( + { + "access_token": self._access_token, + "refresh_token": self._refresh_token, + "timestamp": str(datetime.now()), + } + ) + return cred_dict + + def store_sonos_data_and_credentials(self): + print("store_sonos_data_and_credentials") + data_dict = self._create_sonos_data_dict() + print("data dict :", data_dict) + cred_dict = self._create_sonos_cred_dict() + print("cred_dict :", cred_dict) + sonos_dict = {} + sonos_dict["sonos"] = {"credentials": cred_dict, "data": data_dict} + print("final dict for db :", sonos_dict) + self._store_data(sonos_dict) + + def _store_data(self, data): + Query.store_query_data( + self._client_id, "iot_speakers", data, update_in_place=True + ) + + def _create_grouplist_for_db(self, groups): + print("create_grouplist_for_db") + groups_list = [] + for i in range(len(groups)): + groups_list.append({groups[i]["name"]: groups[i]["id"]}) + return groups_list + + def _create_playerlist_for_db(self, players): + print("create_playerlist_for_db") + player_list = [] + for i in range(len(players)): + player_list.append({players[i]["name"]: players[i]["id"]}) + return player_list + + def set_credentials(self, access_token, refresh_token): + print("set_credentials") + self._access_token = access_token + self._refresh_token = refresh_token + return + + def set_data(self): + print("set_data") + try: + self._households = self._get_households() + self._household_id = self._get_household_id() + self._groups = self._get_groups() + self._players = self._get_players() + self._group_id = self._get_group_id() + except KeyError: + print("Missing device data for this account") + return + + def audio_clip(self, audioclip_url): + """ + Plays an audioclip from link to .mp3 file + """ + player_id = self._get_player_id() + url = f"https://api.ws.sonos.com/control/api/v1/players/{player_id}/audioClip" + + payload = json.dumps( + { + "name": "Embla", + "appId": "com.acme.app", + "streamUrl": f"{audioclip_url}", + "volume": 50, + "priority": "HIGH", + "clipType": "CUSTOM", + } + ) headers = { "Content-Type": "application/json", "Authorization": f"Bearer {self._access_token}", } - response = requests.request("GET", url, headers=headers, data=payload) + response = post_to_json_api(url, payload, headers) + return response - return response.json()["groups"][0]["id"] +# # TODO: Check whether this should return the ids themselves instead of the json response +# def _get_households(token): +# """ +# Returns the list of households of the user +# """ +# url = f"https://api.ws.sonos.com/control/api/v1/households" -# TODO: Check whether this should return the ids themselves instead of the json response -def get_households(token): - """ - Returns the list of households of the user - """ - url = f"https://api.ws.sonos.com/control/api/v1/households" +# payload = {} +# headers = {"Authorization": f"Bearer {token}"} - payload = {} - headers = {"Authorization": f"Bearer {token}"} +# response = requests.request("GET", url, headers=headers, data=payload) - response = requests.request("GET", url, headers=headers, data=payload) +# return response.json() - return response.json() +# def _get_groups(household_id, token): +# """ +# Returns the list of groups of the user +# """ +# url = f"https://api.ws.sonos.com/control/api/v1/households/{household_id}/groups" -def get_groups(household_id, token): - """ - Returns the list of groups of the user - """ - url = f"https://api.ws.sonos.com/control/api/v1/households/{household_id}/groups" +# payload = {} +# headers = {"Authorization": f"Bearer {token}"} - payload = {} - headers = {"Authorization": f"Bearer {token}"} +# response = requests.request("GET", url, headers=headers, data=payload) - response = requests.request("GET", url, headers=headers, data=payload) +# return response - return response +# def _create_token(code, sonos_encoded_credentials, host): +# """ +# Creates a token given a code +# """ +# url = f"https://api.sonos.com/login/v3/oauth/access?grant_type=authorization_code&code={code}&redirect_uri=http://{host}/connect_sonos.api" -def create_token(code, sonos_encoded_credentials, host): - """ - Creates a token given a code - """ - url = f"https://api.sonos.com/login/v3/oauth/access?grant_type=authorization_code&code={code}&redirect_uri=http://{host}/connect_sonos.api" +# payload = {} +# headers = { +# "Authorization": f"Basic {sonos_encoded_credentials}", +# "Cookie": "JSESSIONID=F710019AF0A3B7126A8702577C883B5F; AWSELB=69BFEFC914A689BF6DC8E4652748D7B501ED60290D5EA56F2E543ABD7CF357A5F65186AEBCFB059E28075D83A700FD504C030A53CC28683B515BE3DCA3CC587AFAF606E171; AWSELBCORS=69BFEFC914A689BF6DC8E4652748D7B501ED60290D5EA56F2E543ABD7CF357A5F65186AEBCFB059E28075D83A700FD504C030A53CC28683B515BE3DCA3CC587AFAF606E171", +# } - payload = {} - headers = { - "Authorization": f"Basic {sonos_encoded_credentials}", - "Cookie": "JSESSIONID=F710019AF0A3B7126A8702577C883B5F; AWSELB=69BFEFC914A689BF6DC8E4652748D7B501ED60290D5EA56F2E543ABD7CF357A5F65186AEBCFB059E28075D83A700FD504C030A53CC28683B515BE3DCA3CC587AFAF606E171; AWSELBCORS=69BFEFC914A689BF6DC8E4652748D7B501ED60290D5EA56F2E543ABD7CF357A5F65186AEBCFB059E28075D83A700FD504C030A53CC28683B515BE3DCA3CC587AFAF606E171", - } +# response = requests.request("POST", url, headers=headers, data=payload) - response = requests.request("POST", url, headers=headers, data=payload) +# return response - return response +# def refresh_token(sonos_encoded_credentials, refresh_token): +# """ +# Refreshes token +# """ +# url = f"https://api.sonos.com/login/v3/oauth/access?grant_type=refresh_token&refresh_token={refresh_token}" -def refresh_token(sonos_encoded_credentials, refresh_token): - """ - Refreshes token - """ - url = f"https://api.sonos.com/login/v3/oauth/access?grant_type=refresh_token&refresh_token={refresh_token}" +# payload = {} +# headers = {"Authorization": f"Basic {sonos_encoded_credentials}"} - payload = {} - headers = {"Authorization": f"Basic {sonos_encoded_credentials}"} +# response = requests.request("POST", url, headers=headers, data=payload) - response = requests.request("POST", url, headers=headers, data=payload) +# return response - return response +# def audio_clip(audioclip_url, player_id, token): +# """ +# Plays an audioclip from link to .mp3 file +# """ +# import requests +# import json -def audio_clip(audioclip_url, player_id, token): - """ - Plays an audioclip from link to .mp3 file - """ - import requests - import json +# url = f"https://api.ws.sonos.com/control/api/v1/players/{player_id}/audioClip" - url = f"https://api.ws.sonos.com/control/api/v1/players/{player_id}/audioClip" +# payload = json.dumps( +# { +# "name": "Embla", +# "appId": "com.acme.app", +# "streamUrl": f"{audioclip_url}", +# "volume": 50, +# "priority": "HIGH", +# "clipType": "CUSTOM", +# } +# ) +# headers = { +# "Content-Type": "application/json", +# "Authorization": f"Bearer {token}", +# } - payload = json.dumps( - { - "name": "Embla", - "appId": "com.acme.app", - "streamUrl": f"{audioclip_url}", - "volume": 50, - "priority": "HIGH", - "clipType": "CUSTOM", - } - ) - headers = { - "Content-Type": "application/json", - "Authorization": f"Bearer {token}", - } - - response = requests.request("POST", url, headers=headers, data=payload) - - -def update_sonos_token(q, device_data): - print("update sonos token") - sonos_encoded_credentials = read_api_key("SonosEncodedCredentials") - refresh_token_str = device_data["sonos"]["credentials"]["refresh_token"] - access_token = refresh_token(sonos_encoded_credentials, refresh_token_str).json() - access_token = access_token["access_token"] - sonos_dict = { - "sonos": { - "credentials": { - "access_token": access_token, - "timestamp": str(datetime.now()), - } - } - } - q.set_client_data("iot_speakers", sonos_dict, update_in_place=True) +# response = requests.request("POST", url, headers=headers, data=payload) + + +# def _update_sonos_token(q, device_data): +# print("update sonos token") +# sonos_encoded_credentials = read_api_key("SonosEncodedCredentials") +# refresh_token_str = device_data["sonos"]["credentials"]["refresh_token"] +# access_token = refresh_token(sonos_encoded_credentials, refresh_token_str).json() +# access_token = access_token["access_token"] +# sonos_dict = { +# "sonos": { +# "credentials": { +# "access_token": access_token, +# "timestamp": str(datetime.now()), +# } +# } +# } +# q.set_client_data("iot_speakers", sonos_dict, update_in_place=True) diff --git a/query.py b/query.py index 91fbbf4a..f598aaf7 100755 --- a/query.py +++ b/query.py @@ -935,6 +935,7 @@ def store_query_data( row.data = data # type: ignore row.modified = now # type: ignore # The session is auto-committed upon exit from the context manager + print("return True") return True except Exception as e: logging.error("Error storing query data in db: {0}".format(e)) diff --git a/routes/api.py b/routes/api.py index 1dac1d9a..0cdc6f8f 100755 --- a/routes/api.py +++ b/routes/api.py @@ -50,6 +50,7 @@ RECOMMENDED_VOICES, ) from util import read_api_key, icelandic_asciify +from queries.sonos import SonosClient from . import routes, better_jsonify, text_from_request, bool_from_request from . import MAX_URL_LENGTH, MAX_UUID_LENGTH @@ -727,21 +728,29 @@ def sonos_code(version: int = 1) -> Response: code = args.get("code") code = {"sonos": {"credentials": {"code": code}}} if client_id and code: + print("if client_id and code") success = QueryObject.store_query_data( client_id, "iot_speakers", code, update_in_place=True ) + print("store querydata done") + print(success) if success: + print("success") + device_data = code + sonos_client = SonosClient(device_data, client_id) + sonos_voice_clip = f"Hæ!, ég er búin að tengja þennan Sónos hátalara." + sonos_client.audio_clip(text_to_audio_url(sonos_voice_clip)) return better_jsonify(valid=True, msg="Registered sonos code") - + print("else") return better_jsonify(valid=False, errmsg="Error registering sonos code.") -def sonos_code(version: int = 1) -> Response: +def sonos_code2(version: int = 1) -> Response: print("sonos code") args = request.args client_id = args.get("state") code = args.get("code") - code = {"spotify": {"credentials": {"code": code}}} + code = {"sonos": {"credentials": {"code": code}}} if client_id and code: success = QueryObject.store_query_data( client_id, "iot_speakers", code, update_in_place=True From 2d681de11c37b94df8603076dbb3664a58c2b10e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Fri, 1 Jul 2022 11:07:07 +0000 Subject: [PATCH 135/371] fixed numbers being said incorrectly, works now to switch row --- queries/theater_module.py | 210 +++++++++++++++++++++++--------------- 1 file changed, 127 insertions(+), 83 deletions(-) diff --git a/queries/theater_module.py b/queries/theater_module.py index fb4857de..43c14c35 100644 --- a/queries/theater_module.py +++ b/queries/theater_module.py @@ -36,6 +36,7 @@ DateResource, DialogueStateManager, ListResource, + NumberResource, Resource, ResourceState, TimeResource, @@ -170,7 +171,7 @@ def help_text(lemma: str) -> str: | QNum "röð" QTheaterShowSeats → - QTheaterEgVil? "sæti"? QNum "til" QNum + QTheaterEgVil? "sæti"? QNum "til"? QNum? QTheaterDateOptions → "hvaða" "dagsetningar" "eru" "í" "boði" @@ -239,6 +240,7 @@ def _generate_show_answer( resource: ListResource, dsm: DialogueStateManager ) -> Optional[str]: result = dsm.get_result() + print("Generate show answer") if (not resource.is_confirmed and result.get("options_info")) or result.get( "show_options" ): @@ -348,7 +350,7 @@ def _generate_seat_count_answer( return resource.prompts["initial"] if resource.is_fulfilled: return resource.prompts["confirm"].format( - seats=number_to_text(cast(int, resource.data[0])) + seats=number_to_text(cast(int, resource.data)) ) @@ -358,7 +360,7 @@ def _generate_row_answer( print("Generating row answer") result = dsm.get_result() title: str = dsm.get_resource("Show").data[0] - seats: int = dsm.get_resource("ShowSeatCount").data[0] + seats: int = dsm.get_resource("ShowSeatCount").data available_rows: list[str] = [] for show in _SHOWS: if show["title"] == title: @@ -383,7 +385,7 @@ def _generate_row_answer( rows=natlang_seq(available_rows), seats=number_to_text(seats) ) if result.get("no_row_matched"): - return resource.prompts["no_row_matched"].format(seats=seats) + return resource.prompts["no_row_matched"].format(seats=number_to_text(seats)) if resource.is_unfulfilled: if len(available_rows) == 0: return resource.prompts["not_enough_seats"].format(seats=seats) @@ -401,7 +403,7 @@ def _generate_seat_number_answer( print("_generate_seat_number_answer", resource.state) result = dsm.get_result() title: str = dsm.get_resource("Show").data[0] - seats: int = dsm.get_resource("ShowSeatCount").data[0] + seats: int = dsm.get_resource("ShowSeatCount").data chosen_row: int = dsm.get_resource("ShowSeatRow").data[0] available_seats: list[str] = [] for show in _SHOWS: @@ -433,10 +435,14 @@ def _generate_seat_number_answer( ) if resource.is_fulfilled: print("confirm prompt") - chosen_seats_string: str = "{first_seat} til {last_seat}".format( - first_seat=number_to_text(result.get("numbers")[0]), - last_seat=number_to_text(result.get("numbers")[1]), - ) + chosen_seats_string: str = "" + if seats > 1: + chosen_seats_string = "{first_seat} til {last_seat}".format( + first_seat=number_to_text(result.get("numbers")[0]), + last_seat=number_to_text(result.get("numbers")[1]), + ) + else: + chosen_seats_string = number_to_text(result.get("numbers")[0]) return resource.prompts["confirm"].format(seats=chosen_seats_string) @@ -450,15 +456,20 @@ def _generate_final_answer( title = dsm.get_resource("Show").data[0] date = cast(DateResource, dsm.get_resource("ShowDate")).data time = cast(TimeResource, dsm.get_resource("ShowTime")).data + number_of_seats = cast(NumberResource, dsm.get_resource("ShowSeatCount")).data seats = dsm.get_resource("ShowSeatNumber").data - seat_string: str = "{first_seat} til {last_seat}".format( - first_seat=number_to_text(seats[0]), - last_seat=number_to_text(seats[-1]), - ) + seat_string: str = "" + if number_of_seats > 1: + seat_string = "{first_seat} til {last_seat}".format( + first_seat=number_to_text(seats[0]), + last_seat=number_to_text(seats[-1]), + ) + else: + seat_string = number_to_text(seats[0]) row = dsm.get_resource("ShowSeatRow").data[0] ans = resource.prompts["final"].format( seats=seat_string, - row=row, + row=number_to_text(row), show=title, date_time=datetime.datetime.combine( date, @@ -550,8 +561,6 @@ def _date_callback( result.multiple_times_for_date = True print("Many showtimes", show_times) datetime_resource.state = ResourceState.PARTIALLY_FULFILLED - else: - dsm.set_error() def _time_callback( @@ -602,8 +611,6 @@ def _time_callback( resource.set_time(first_matching_date.time()) if first_matching_date is None: result.no_time_matched = True - else: - dsm.set_error() def QTheaterDateTime(node: Node, params: QueryStateDict, result: Result) -> None: @@ -622,6 +629,9 @@ def QTheaterDateTime(node: Node, params: QueryStateDict, result: Result) -> None h = 12 if min is None: min = 0 + # Change before noon times to afternoon + if h < 12: + h += 12 result["show_time"] = datetime.time(h, min) result["show_date"] = datetime.date(y, m, d) print("Show date: ", result["show_date"]) @@ -681,10 +691,13 @@ def QTheaterShowSeatCountQuery( node: Node, params: QueryStateDict, result: Result ) -> None: def _add_seat_number( - resource: Resource, dsm: DialogueStateManager, result: Result + resource: NumberResource, dsm: DialogueStateManager, result: Result ) -> None: - resource.data = [result.number] - resource.state = ResourceState.FULFILLED + if dsm.get_resource("ShowDateTime").is_confirmed: + print("Number count resource data: ", resource.data) + resource.data = result.number + print("Result.number: ", result.number) + resource.state = ResourceState.FULFILLED if "callbacks" not in result: result["callbacks"] = [] @@ -702,33 +715,34 @@ def QTheaterShowRow(node: Node, params: QueryStateDict, result: Result) -> None: def _add_row( resource: ListResource, dsm: DialogueStateManager, result: Result ) -> None: - title: str = dsm.get_resource("Show").data[0] - seats: int = dsm.get_resource("ShowSeatCount").data[0] - available_rows: list[str] = [] - for show in _SHOWS: - if show["title"] == title: - checking_row: int = 1 - seats_in_row: int = 0 - for (row, _) in show["location"]: - if checking_row == row: - seats_in_row += 1 - if seats_in_row >= seats: - available_rows.append(row) - seats_in_row = 0 - else: - checking_row = row - seats_in_row = 1 - print("Add row: ", result.number) - print("Available rows: ", available_rows) - if result.number in available_rows: - print("Appending row") - resource.data.append(result.number) - resource.state = ResourceState.FULFILLED - else: - print("Emptying row data") - resource.data = [] - resource.state = ResourceState.UNFULFILLED - result.no_row_matched = True + if dsm.get_resource("ShowSeatCount").is_confirmed: + title: str = dsm.get_resource("Show").data[0] + seats: int = dsm.get_resource("ShowSeatCount").data + available_rows: list[str] = [] + for show in _SHOWS: + if show["title"] == title: + checking_row: int = 1 + seats_in_row: int = 0 + for (row, _) in show["location"]: + if checking_row == row: + seats_in_row += 1 + if seats_in_row >= seats: + available_rows.append(row) + seats_in_row = 0 + else: + checking_row = row + seats_in_row = 1 + print("Add row: ", result.number) + print("Available rows: ", available_rows) + if result.number in available_rows: + print("Appending row") + resource.data = [result.number] + resource.state = ResourceState.FULFILLED + else: + print("Emptying row data") + resource.data = [] + resource.state = ResourceState.UNFULFILLED + result.no_row_matched = True if "callbacks" not in result: result["callbacks"] = [] @@ -740,41 +754,49 @@ def QTheaterShowSeats(node: Node, params: QueryStateDict, result: Result) -> Non def _add_seats( resource: ListResource, dsm: DialogueStateManager, result: Result ) -> None: - print("Add seats callback, state: ", resource.state) - title: str = dsm.get_resource("Show").data[0] - print("Row data: ", dsm.get_resource("ShowSeatRow").data) - row: int = dsm.get_resource("ShowSeatRow").data[0] - number_of_seats: int = dsm.get_resource("ShowSeatCount").data[0] - print("Result.numbers: ", len(result.numbers)) - selected_seats: list[int] = [ - seat for seat in range(result.numbers[0], result.numbers[1] + 1) - ] - if len(selected_seats) != number_of_seats: - print("Selected seats does not match number of seats") - print("Resource name that is being emptied: ", resource.name) - resource.data = [] - resource.state = ResourceState.UNFULFILLED - result.wrong_number_seats_selected = True - return - for show in _SHOWS: - if show["title"] == title: - seats: list[int] = [] - for seat in selected_seats: - if (row, seat) in show["location"]: - seats.append(seat) - else: - print("Seat not unavailable") - resource.data = [] - resource.state = ResourceState.UNFULFILLED - result.seats_unavailable = True - return - for seat in seats: - print("Appending seat: ", seat) - resource.data.append(seat) - print("Length of data: ", len(resource.data)) - if len(resource.data) > 0: - print("Setting state to fulfilled") - resource.state = ResourceState.FULFILLED + if dsm.get_resource("ShowSeatRow").is_confirmed: + print("Add seats callback, state: ", resource.state) + title: str = dsm.get_resource("Show").data[0] + print("Row data: ", dsm.get_resource("ShowSeatRow").data) + row: int = dsm.get_resource("ShowSeatRow").data[0] + number_of_seats: int = dsm.get_resource("ShowSeatCount").data + print("Result.numbers: ", len(result.numbers)) + selected_seats: list[int] = [] + if number_of_seats > 1: + selected_seats = [ + seat for seat in range(result.numbers[0], result.numbers[1] + 1) + ] + else: + print("Result.numbers: ", result.numbers) + print("Result.number: ", result.number) + selected_seats = [result.numbers[0]] + print("Selected seats: ", selected_seats) + if len(selected_seats) != number_of_seats: + print("Selected seats does not match number of seats") + print("Resource name that is being emptied: ", resource.name) + resource.data = [] + resource.state = ResourceState.UNFULFILLED + result.wrong_number_seats_selected = True + return + for show in _SHOWS: + if show["title"] == title: + seats: list[int] = [] + for seat in selected_seats: + if (row, seat) in show["location"]: + seats.append(seat) + else: + print("Seat unavailable") + resource.data = [] + resource.state = ResourceState.UNFULFILLED + result.seats_unavailable = True + return + resource.data = [] + for seat in seats: + resource.data.append(seat) + print("Length of data: ", len(resource.data)) + if len(resource.data) > 0: + print("Setting state to fulfilled") + resource.state = ResourceState.FULFILLED if "callbacks" not in result: result["callbacks"] = [] @@ -856,6 +878,28 @@ def _parse_yes( result.callbacks.append((filter_func, _parse_yes)) +def QNo(node: Node, params: QueryStateDict, result: Result): + def _parse_no( + resource: Resource, dsm: DialogueStateManager, result: Result + ) -> None: + if "no_used" not in result and resource.is_fulfilled: + print("NO USED", resource.name, " confirming") + resource.state = ResourceState.UNFULFILLED + result.no_used = True + if resource.name == "ShowDateTime": + for rname in resource.requires: + dsm.get_resource(rname).state = ResourceState.UNFULFILLED + + if "callbacks" not in result: + result["callbacks"] = [] + filter_func: Callable[[Resource], bool] = ( + lambda r: r.name + in ("Show", "ShowDateTime", "ShowSeatCount", "ShowSeatRow", "ShowSeatNumber") + and not r.is_confirmed + ) + result.callbacks.append((filter_func, _parse_no)) + + SHOW_URL = "https://leikhusid.is/wp-json/shows/v1/categories/938" From 9db7780ac9b71686dd614b719277a144e135f8d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Fri, 1 Jul 2022 11:19:37 +0000 Subject: [PATCH 136/371] now klukkan 5 will be changed to klukkan 17 --- queries/theater_module.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/queries/theater_module.py b/queries/theater_module.py index 43c14c35..43dff7d9 100644 --- a/queries/theater_module.py +++ b/queries/theater_module.py @@ -597,10 +597,8 @@ def _time_callback( for date in show["date"]: if result["show_time"] == date.time(): if first_matching_date is None: - print("Setting first_matching_date") first_matching_date = cast(datetime.datetime, date) else: - print("Result matched many times, returning") result.many_matching_times = True return if first_matching_date is not None: @@ -608,13 +606,15 @@ def _time_callback( DateResource, dsm.get_resource("ShowDate") ) date_resource.set_date(first_matching_date.date()) + date_resource.state = ResourceState.FULFILLED resource.set_time(first_matching_date.time()) + resource.state = ResourceState.FULFILLED + datetime_resource.state = ResourceState.FULFILLED if first_matching_date is None: result.no_time_matched = True def QTheaterDateTime(node: Node, params: QueryStateDict, result: Result) -> None: - print("In theater datetime") datetimenode = node.first_child(lambda n: True) assert isinstance(datetimenode, TerminalNode) now = datetime.datetime.now() @@ -634,8 +634,6 @@ def QTheaterDateTime(node: Node, params: QueryStateDict, result: Result) -> None h += 12 result["show_time"] = datetime.time(h, min) result["show_date"] = datetime.date(y, m, d) - print("Show date: ", result["show_date"]) - print("Show time: ", result["show_time"]) if "callbacks" not in result: result["callbacks"] = [] @@ -674,6 +672,9 @@ def QTheaterTime(node: Node, params: QueryStateDict, result: Result) -> None: if tnode: aux_str = tnode.aux.strip("[]") hour, minute, _ = (int(i) for i in aux_str.split(", ")) + # Change before noon times to afternoon + if hour < 12: + hour += 12 result["show_time"] = datetime.time(hour, minute) From b15bc88925a0f9890521b0e0446df9f632e455c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Fri, 1 Jul 2022 11:45:43 +0000 Subject: [PATCH 137/371] Fixed QNo making Show state unfulfilled when making date unfulfilled --- queries/theater_module.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/queries/theater_module.py b/queries/theater_module.py index 43dff7d9..9e75f634 100644 --- a/queries/theater_module.py +++ b/queries/theater_module.py @@ -884,12 +884,11 @@ def _parse_no( resource: Resource, dsm: DialogueStateManager, result: Result ) -> None: if "no_used" not in result and resource.is_fulfilled: - print("NO USED", resource.name, " confirming") resource.state = ResourceState.UNFULFILLED result.no_used = True if resource.name == "ShowDateTime": - for rname in resource.requires: - dsm.get_resource(rname).state = ResourceState.UNFULFILLED + dsm.get_resource("ShowDate").state = ResourceState.UNFULFILLED + dsm.get_resource("ShowTime").state = ResourceState.UNFULFILLED if "callbacks" not in result: result["callbacks"] = [] From f5820493e0d1e37d805492e041d69a5d5a130590 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Fri, 1 Jul 2022 11:48:51 +0000 Subject: [PATCH 138/371] =?UTF-8?q?Fixed=20grammar=20to=20include=20n?= =?UTF-8?q?=C3=BAmer=20when=20saying=20row=20and=20seats?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- queries/theater_module.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/queries/theater_module.py b/queries/theater_module.py index 9e75f634..214add6f 100644 --- a/queries/theater_module.py +++ b/queries/theater_module.py @@ -165,13 +165,12 @@ def help_text(lemma: str) -> str: | "fá" "sæti" "á" QTheaterRodBekkur → - "röð"? QNum - | "bekk" QNum + QTheaterRodBekk? "númer"? QNum | QNum "bekk" | QNum "röð" QTheaterShowSeats → - QTheaterEgVil? "sæti"? QNum "til"? QNum? + QTheaterEgVil? "sæti"? "númer"? QNum "til"? QNum? QTheaterDateOptions → "hvaða" "dagsetningar" "eru" "í" "boði" From 049220c6f4d9bea0a1181eb821d5efeda6d01aec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Fri, 1 Jul 2022 11:51:31 +0000 Subject: [PATCH 139/371] Added to seat and row option query --- queries/theater_module.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/queries/theater_module.py b/queries/theater_module.py index 214add6f..d002258b 100644 --- a/queries/theater_module.py +++ b/queries/theater_module.py @@ -116,15 +116,21 @@ def help_text(lemma: str) -> str: | "hvaða" "dagsetningar" "er" "hægt" "að" "velja" "á" "milli" QTheaterRowOptions → - "hvaða" "raðir" "eru" "í" "boði" - | "hvaða" "röð" "er" "í" "boði" - | "hvaða" "bekkir" "eru" "í" "boði" - | "hvaða" "bekkur" "er" "í" "boði" + "hvaða" "raðir" "eru" QTheaterIBodiLausar + | "hvaða" "röð" "er" QTheaterIBodiLausar + | "hvaða" "bekkir" "eru" QTheaterIBodiLausar + | "hvaða" "bekkur" "er" QTheaterIBodiLausar QTheaterSeatOptions → - "hvaða" "sæti" "eru" "í" "boði" + "hvaða" "sæti" "eru" QTheaterIBodiLausar "hverjir" "eru" "sæta" "valmöguleikarnir" +QTheaterIBodiLausar → + "í" "boði" + | "lausar" + | "lausir" + | "laus" + QTheaterShowQuery → QTheaterEgVil? "velja" 'sýning' QTheaterShowName > QTheaterEgVil? "fara" "á" 'sýning' QTheaterShowName > QTheaterShowName From 15f8339de1164860f609270b9af06eecfcc36aa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Fri, 1 Jul 2022 12:01:24 +0000 Subject: [PATCH 140/371] Added status grammar and more start possibilities --- queries/theater_module.py | 44 +++++++++++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/queries/theater_module.py b/queries/theater_module.py index d002258b..39d6ee07 100644 --- a/queries/theater_module.py +++ b/queries/theater_module.py @@ -75,14 +75,27 @@ def help_text(lemma: str) -> str: QTheaterHotWord | QTheaterDialogue QTheaterHotWord → - "leikhús" - | "þjóðleikhúsið" - | "þjóðleikhús" - | 'Þjóðleikhúsið' + QTheaterNames + | QTheaterEgVil? QTheaterKaupaFaraFaPanta "leikhúsmiða" + | QTheaterEgVil? QTheaterKaupaFaraFaPanta "miða" "í" QTheaterNames + | QTheaterEgVil?QTheaterKaupaFaraFaPanta "miða" "á" QTheaterNames "sýningu" + | QTheaterEgVil? QTheaterKaupaFaraFaPanta QTheaterNames + | QTheaterEgVil? QTheaterKaupaFaraFaPanta "leikhússýningu" + +QTheaterNames → + 'leikhús' + | 'þjóðleikhús' | 'Þjóðleikhús' - | QTheaterEgVil? "kaupa" "leikhúsmiða" - | QTheaterEgVil? "fara" "í" "leikhús" - | QTheaterEgVil? "fara" "á" "leikhússýningu" + | 'Borgarleikhús' + | 'borgarleikhús' + + +QTheaterKaupaFaraFaPanta → + "kaupa" + | "fara" "á" + | "fara" "í" + | "fá" + | "panta" QTheaterDialogue → QTheaterShowQuery @@ -200,8 +213,21 @@ def help_text(lemma: str) -> str: QNo → "nei" "takk"? | "nei" "nei"* | "neitakk" | "ómögulega" QCancel → "ég" "hætti" "við" - | QTheaterEgVil "hætta" "við" "pöntunina" - | QTheaterEgVil "hætta" "við" "pöntunina" + | QTheaterEgVil "hætta" "við" QTheaterPontun + +QStatus → + "hver" "er" "staðan" + | "staðan" + | "hvert" "var" "ég" 'komin' + | "hvar" "var" "ég" 'komin'? + | "hver" "var" "staðan" "á"? QTheaterPontun + | QTheaterEgVil "halda" "áfram" "með" QTheaterPontun + +QTheaterPontun → + 'pöntun' + | "leikhús" 'pöntun' + | "leikhúsmiða" 'pöntun' + | "leikhús" "miða" 'pöntun' """ From 80ea4ef73a8163a2cfa3780cde802fa673be5248 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Fri, 1 Jul 2022 12:29:33 +0000 Subject: [PATCH 141/371] changed status grammar and added handling of status grammar --- queries/theater_module.py | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/queries/theater_module.py b/queries/theater_module.py index 39d6ee07..4d5417a7 100644 --- a/queries/theater_module.py +++ b/queries/theater_module.py @@ -78,7 +78,7 @@ def help_text(lemma: str) -> str: QTheaterNames | QTheaterEgVil? QTheaterKaupaFaraFaPanta "leikhúsmiða" | QTheaterEgVil? QTheaterKaupaFaraFaPanta "miða" "í" QTheaterNames - | QTheaterEgVil?QTheaterKaupaFaraFaPanta "miða" "á" QTheaterNames "sýningu" + | QTheaterEgVil? QTheaterKaupaFaraFaPanta "miða" "á" QTheaterNames "sýningu" | QTheaterEgVil? QTheaterKaupaFaraFaPanta QTheaterNames | QTheaterEgVil? QTheaterKaupaFaraFaPanta "leikhússýningu" @@ -91,7 +91,7 @@ def help_text(lemma: str) -> str: QTheaterKaupaFaraFaPanta → - "kaupa" + "kaupa" "mér"? | "fara" "á" | "fara" "í" | "fá" @@ -106,6 +106,7 @@ def help_text(lemma: str) -> str: | QYes | QNo | QCancel + | QStatus # TODO: Hvað er í boði, ég vil sýningu X, dagsetningu X, X mörg sæti, staðsetningu X QTheaterOptions → @@ -216,18 +217,24 @@ def help_text(lemma: str) -> str: | QTheaterEgVil "hætta" "við" QTheaterPontun QStatus → - "hver" "er" "staðan" + "staðan" + | "hver" "er" "staðan" "á" QTheaterPontun? | "staðan" - | "hvert" "var" "ég" 'komin' - | "hvar" "var" "ég" 'komin'? + | "hvert" "var" "ég" 'komin'? "í" QTheaterPontun? + | "hvar" "var" "ég" 'komin'? "í"? QTheaterPontun? | "hver" "var" "staðan" "á"? QTheaterPontun | QTheaterEgVil "halda" "áfram" "með" QTheaterPontun QTheaterPontun → - 'pöntun' - | "leikhús" 'pöntun' - | "leikhúsmiða" 'pöntun' - | "leikhús" "miða" 'pöntun' + "pöntuninni" + | "leikhús" "pöntuninni" + | "leikhús" "pöntunina" + | "leikhúsmiða" "pöntuninni" + | "leikhúsmiða" "pöntunina" + | "leikhúsmiðapöntunina" + | "leikhúsmiðapöntuninni" + | "leikhús" "miða" "pöntunina" + | "leikhús" "miða" "pöntuninni" """ @@ -511,7 +518,8 @@ def _generate_final_answer( def QTheaterDialogue(node: Node, params: QueryStateDict, result: Result) -> None: - result.qtype = _THEATER_QTYPE + if "qtype" not in result: + result.qtype = _THEATER_QTYPE def QTheaterHotWord(node: Node, params: QueryStateDict, result: Result) -> None: @@ -931,6 +939,10 @@ def _parse_no( result.callbacks.append((filter_func, _parse_no)) +def QStatus(node: Node, params: QueryStateDict, result: Result): + result.qtype = "QStatus" + + SHOW_URL = "https://leikhusid.is/wp-json/shows/v1/categories/938" @@ -968,6 +980,12 @@ def sentence(state: QueryStateDict, result: Result) -> None: if result.qtype == _START_DIALOGUE_QTYPE: print("B") dsm.start_dialogue() + elif result.qtype == "QStatus": + # Example info handling functionality + ans = "Leikhúsmiðapöntunin þín gengur bara vel. " + ans += dsm.get_answer() or "" + q.set_answer(*gen_answer(ans)) + return print("C") print(dsm._resources) ans = dsm.get_answer() From f008949272b6d424eaecf6099a94c920f995c2cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Fri, 1 Jul 2022 13:40:26 +0000 Subject: [PATCH 142/371] Fixed time not resetting when selecting new date in theater --- queries/theater_module.py | 48 ++++++++++++--------------------------- 1 file changed, 15 insertions(+), 33 deletions(-) diff --git a/queries/theater_module.py b/queries/theater_module.py index 4d5417a7..919cb487 100644 --- a/queries/theater_module.py +++ b/queries/theater_module.py @@ -554,9 +554,7 @@ def _date_callback( resource: DateResource, dsm: DialogueStateManager, result: Result ) -> None: resource.state = ResourceState.UNFULFILLED - print("In date callback") if dsm.get_resource("Show").is_confirmed: - print("Show was confirmed") show_title: str = dsm.get_resource("Show").data[0] for show in _SHOWS: if show["title"] == show_title: @@ -566,40 +564,24 @@ def _date_callback( resource.state = ResourceState.FULFILLED break time_resource: TimeResource = cast(TimeResource, dsm.get_resource("ShowTime")) + time_resource.state = ResourceState.UNFULFILLED datetime_resource: Resource = dsm.get_resource("ShowDateTime") - if time_resource.is_fulfilled: - print("Time resource was fulfilled") + show_times: list[datetime.time] = [] + for show in _SHOWS: + if show["title"] == show_title: + for date in show["date"]: + if resource.date == date.date(): + show_times.append(date.time()) + if len(show_times) == 0: + result.no_date_matched = True + return + if len(show_times) == 1: + time_resource.set_time(show_times[0]) + time_resource.state = ResourceState.FULFILLED datetime_resource.state = ResourceState.FULFILLED else: - print("Time resource not fulfilled, trying to add time") - show_times: list[datetime.time] = [] - for show in _SHOWS: - if show["title"] == show_title: - for date in show["date"]: - print("Date: ", date) - print("Time: ", date.time()) - print("Resource date: ", resource.date) - print("Date date: ", date.date()) - print("if: ", date.date() == resource.date) - if resource.date == date.date(): - print( - "Adding showtime: ", date.time(), " fyrir date: ", date - ) - show_times.append(date.time()) - print("Show times: ", show_times) - if len(show_times) == 0: - print("No show times found") - result.no_date_matched = True - return - if len(show_times) == 1: - time_resource.set_time(show_times[0]) - time_resource.state = ResourceState.FULFILLED - datetime_resource.state = ResourceState.FULFILLED - print("One show time") - else: - result.multiple_times_for_date = True - print("Many showtimes", show_times) - datetime_resource.state = ResourceState.PARTIALLY_FULFILLED + result.multiple_times_for_date = True + datetime_resource.state = ResourceState.PARTIALLY_FULFILLED def _time_callback( From 18059945fb254fd20b092216645960a46a7e8968 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Fri, 1 Jul 2022 13:49:06 +0000 Subject: [PATCH 143/371] Seat count can no longer be below 0 --- queries/theater/theater.toml | 1 + queries/theater_module.py | 14 ++++++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/queries/theater/theater.toml b/queries/theater/theater.toml index 70952a2c..0ee18713 100644 --- a/queries/theater/theater.toml +++ b/queries/theater/theater.toml @@ -37,6 +37,7 @@ type = "NumberResource" requires = ["ShowDateTime"] prompts.initial = "Hversu mörg sæti viltu bóka?" prompts.confirm = "Þú valdir {seats} sæti, viltu halda áfram og velja staðsetningu sætanna?" +prompts.invalid_seat_count = "Fjöldi sæta þarfa að vera hærri en einn. Vinsamlegast reyndu aftur." [[resources]] name = "ShowSeatRow" diff --git a/queries/theater_module.py b/queries/theater_module.py index 919cb487..5c514e6d 100644 --- a/queries/theater_module.py +++ b/queries/theater_module.py @@ -384,6 +384,9 @@ def _generate_date_answer( def _generate_seat_count_answer( resource: ListResource, dsm: DialogueStateManager ) -> Optional[str]: + result = dsm.get_result() + if result.get("invalid_seat_count"): + return resource.prompts["invalid_seat_count"] if resource.is_unfulfilled: return resource.prompts["initial"] if resource.is_fulfilled: @@ -590,6 +593,8 @@ def _time_callback( resource.state = ResourceState.UNFULFILLED if result.get("no_date_matched"): return + if result.get("multiple_times_for_date"): + result.multiple_times_for_date = False if dsm.get_resource("Show").is_confirmed: show_title: str = dsm.get_resource("Show").data[0] date_resource: DateResource = cast(DateResource, dsm.get_resource("ShowDate")) @@ -716,10 +721,11 @@ def _add_seat_number( resource: NumberResource, dsm: DialogueStateManager, result: Result ) -> None: if dsm.get_resource("ShowDateTime").is_confirmed: - print("Number count resource data: ", resource.data) - resource.data = result.number - print("Result.number: ", result.number) - resource.state = ResourceState.FULFILLED + if result.number > 0: + resource.data = result.number + resource.state = ResourceState.FULFILLED + else: + result.invalid_seat_count = True if "callbacks" not in result: result["callbacks"] = [] From ad3c8298beeaeb27ffa60e468694e973560648d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Fri, 1 Jul 2022 13:53:13 +0000 Subject: [PATCH 144/371] Cancel order now ends dialogue --- queries/fruitseller_module.py | 1 + queries/theater_module.py | 1 + 2 files changed, 2 insertions(+) diff --git a/queries/fruitseller_module.py b/queries/fruitseller_module.py index 40412fc4..a335d1f5 100644 --- a/queries/fruitseller_module.py +++ b/queries/fruitseller_module.py @@ -289,6 +289,7 @@ def _cancel_order( resource: Resource, dsm: DialogueStateManager, result: Result ) -> None: resource.state = ResourceState.CANCELLED + dsm.end_dialogue() result.qtype = "QCancelOrder" result.answer_key = ("Final", "cancelled") diff --git a/queries/theater_module.py b/queries/theater_module.py index 5c514e6d..16999324 100644 --- a/queries/theater_module.py +++ b/queries/theater_module.py @@ -876,6 +876,7 @@ def _cancel_order( resource: Resource, dsm: DialogueStateManager, result: Result ) -> None: resource.state = ResourceState.CANCELLED + dsm.end_dialogue() result.qtype = "QCancel" if "callbacks" not in result: From 292a01f1b49213e9e11ee9f41c2049c3953a3580 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Fri, 1 Jul 2022 14:36:22 +0000 Subject: [PATCH 145/371] Changed ShowDateTime to not require Show, but ShowDate and ShowTime do --- queries/theater/theater.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/queries/theater/theater.toml b/queries/theater/theater.toml index 0ee18713..1b5eb398 100644 --- a/queries/theater/theater.toml +++ b/queries/theater/theater.toml @@ -12,15 +12,17 @@ prompts.no_show_matched_data_exists = "Því miður er þessi sýning ekki í bo [[resources]] name = "ShowDate" type = "DateResource" +requires = ["Show"] [[resources]] name = "ShowTime" type = "TimeResource" +requires = ["Show"] [[resources]] name = "ShowDateTime" type = "ListResource" -requires = ["Show", "ShowDate", "ShowTime"] +requires = ["ShowDate", "ShowTime"] prompts.initial = "Hvenær viltu fara á sýninguna {show}?\n{dates}" prompts.options = "{options}" prompts.confirm = "Þú valdir dagsetninguna {date}, viltu halda áfram og velja fjölda sæta?" From b4b26d2edf2ff32c9d9bde5c34eb2d0ca04b0c7e Mon Sep 17 00:00:00 2001 From: Logi E Date: Fri, 1 Jul 2022 14:36:26 +0000 Subject: [PATCH 146/371] Working on type hints, changed answer func returns, added set_resource_state func --- queries/__init__.py | 4 +- queries/dialogue.py | 165 ++++++++++++++++++++++------------ queries/fruitseller_module.py | 79 ++++++++-------- queries/theater/theater.toml | 1 + queries/theater_module.py | 3 - 5 files changed, 154 insertions(+), 98 deletions(-) diff --git a/queries/__init__.py b/queries/__init__.py index e7538db5..10d0533f 100755 --- a/queries/__init__.py +++ b/queries/__init__.py @@ -44,7 +44,7 @@ import os import re import locale -import yaml + from urllib.parse import urlencode from functools import lru_cache from xml.dom import minidom # type: ignore @@ -662,7 +662,7 @@ def timezone4loc( return None -# @lru_cache(maxsize=32) +@lru_cache(maxsize=32) def read_jsfile(filename: str) -> str: """Read and return a minified JavaScript (.js) file""" # The file is read from the directory 'js' within the directory diff --git a/queries/dialogue.py b/queries/dialogue.py index 145e3b40..8cf7792e 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -2,15 +2,19 @@ Any, Callable, Dict, + Generic, Mapping, + NewType, Set, Tuple, - Union, List, Optional, + Type, + TypeVar, + Union, cast, ) -from typing_extensions import TypedDict +from typing_extensions import TypedDict, reveal_type import os.path import json @@ -21,41 +25,45 @@ try: import tomllib # type: ignore (module not available in Python <3.11) except ModuleNotFoundError: - import tomli as tomllib + import tomli as tomllib # Used for Python <3.11 -from tree import Result +from queries import AnswerTuple, natlang_seq from query import Query, ClientDataDict -from queries import natlang_seq +from tree import Result -# Global key for storing client data for dialogues -DIALOGUE_KEY = "dialogue" -DIALOGUE_NAME_KEY = "dialogue_name" -DIALOGUE_RESOURCES_KEY = "resources" -DIALOGUE_LAST_INTERACTED_WITH_KEY = "last_interacted_with" -EMPTY_DIALOGUE_DATA = "{}" -FINAL_RESOURCE_NAME = "Final" +# Keys for accessing saved client data for dialogues +_DIALOGUE_KEY = "dialogue" +_DIALOGUE_NAME_KEY = "dialogue_name" +_DIALOGUE_RESOURCES_KEY = "resources" +_DIALOGUE_LAST_INTERACTED_WITH_KEY = "last_interacted_with" +_EMPTY_DIALOGUE_DATA = "{}" +_FINAL_RESOURCE_NAME = "Final" -# Resource types -ResourceDataType = Union[str, int, float, bool, datetime.datetime, None] -ListResourceType = List[ResourceDataType] +# Generic resource type (covariant, see https://peps.python.org/pep-0484/#covariance-and-contravariance) +ResourceType_co = TypeVar("ResourceType_co", covariant=True) # Types for use in callbacks -CallbackType = Callable[["Resource", "DialogueStateManager", Result], None] -FilterFuncType = Callable[["Resource"], bool] -CallbackTupleType = Tuple[FilterFuncType, CallbackType] +_CallbackType = Callable[[ResourceType_co, "DialogueStateManager", Result], None] +_FilterFuncType = Type[Callable[[ResourceType_co], bool]] +_CallbackTupleType = Tuple[_FilterFuncType["Resource"], _CallbackType["Resource"]] + # Types for use in generating prompts/answers -AnsweringFunctionType = Callable[["Resource", "DialogueStateManager"], Optional[str]] +AnsweringFunctionType = Callable[ + ["Resource", "DialogueStateManager"], Optional[AnswerTuple] +] AnsweringFunctionMap = Mapping[str, AnsweringFunctionType] class ResourceState(IntEnum): """Enum representing the different states a dialogue resource can be in.""" + # Main states (order matters, lower state should equal a lower number) UNFULFILLED = auto() PARTIALLY_FULFILLED = auto() FULFILLED = auto() CONFIRMED = auto() + # ---- Extra states PAUSED = auto() SKIPPED = auto() CANCELLED = auto() @@ -85,6 +93,8 @@ class Resource: requires: List[str] = field(default_factory=list) # Dictionary containing different prompts/responses prompts: Mapping[str, str] = field(default_factory=dict) + # When this resource's state is changed, change all parent resource states as well + cascade_state: bool = False @property def is_unfulfilled(self) -> bool: @@ -131,7 +141,7 @@ def format_data(self, format_func: Optional[Callable[[Any], str]] = None) -> str class ListResource(Resource): """Resource representing a list of items.""" - data: ListResourceType = field(default_factory=list) + data: List[Any] = field(default_factory=list) max_items: Optional[int] = None def format_data(self, format_func: Optional[Callable[[Any], str]] = None) -> str: @@ -233,7 +243,7 @@ def format_data(self, format_func: Optional[Callable[[Any], str]] = None) -> str class DatetimeResource(Resource): """Resource for wrapping date and time resources.""" - pass + ... @dataclass @@ -250,7 +260,7 @@ class OrResource(Resource): @dataclass class AndResource(Resource): # For answering multiple resources at the same time - pass + ... @dataclass @@ -295,16 +305,16 @@ def _load_dialogue_structure(filename: str) -> DialogueStructureType: with open(fpath, mode="r") as file: f = file.read() obj: Dict[str, Any] = tomllib.loads(f) # type: ignore - assert DIALOGUE_NAME_KEY in obj - assert DIALOGUE_RESOURCES_KEY in obj + assert _DIALOGUE_NAME_KEY in obj + assert _DIALOGUE_RESOURCES_KEY in obj resource_dict: Dict[str, Resource] = {} - for resource in obj[DIALOGUE_RESOURCES_KEY]: + for resource in obj[_DIALOGUE_RESOURCES_KEY]: assert "name" in resource if "type" not in resource: resource["type"] = "Resource" # Create instances of Resource classes (and its subclasses) resource_dict[resource["name"]] = _RESOURCE_TYPES[resource["type"]](**resource) - obj[DIALOGUE_RESOURCES_KEY] = resource_dict + obj[_DIALOGUE_RESOURCES_KEY] = resource_dict return cast(DialogueStructureType, obj) @@ -324,7 +334,7 @@ def __init__( self._saved_state: DialogueStructureType = self._get_saved_dialogue_state() self._data: Dict[str, Any] = {} self._answering_functions: AnsweringFunctionMap = {} - self._answer: Optional[str] = None + self._answer_tuple: Optional[AnswerTuple] = None self._error: bool = False # TODO: Delegate answering from a resource to another resource or to another dialogue # TODO: í ávaxtasamtali "ég vil panta flug" "viltu að ég geymi ávaxtapöntunina eða eyði henni?" ... @@ -333,28 +343,39 @@ def not_in_dialogue(self) -> bool: """Check if the client is in or wants to start this dialogue""" return ( self._result.get("qtype") != self._start_qtype - and self._saved_state.get(DIALOGUE_NAME_KEY) != self._dialogue_name + and self._saved_state.get(_DIALOGUE_NAME_KEY) != self._dialogue_name ) def setup_dialogue(self, answering_functions: AnsweringFunctionMap) -> None: + """ + Load dialogue structure from TOML file and update resource states from client data. + Should be called after initializing an instance of + DialogueStateManager and before calling get_answer. + """ obj = _load_dialogue_structure(self._dialogue_name) - for rname, resource in obj[DIALOGUE_RESOURCES_KEY].items(): - if rname in self._saved_state[DIALOGUE_RESOURCES_KEY]: + for rname, resource in obj[_DIALOGUE_RESOURCES_KEY].items(): + if rname in self._saved_state[_DIALOGUE_RESOURCES_KEY]: # Update empty resource with serialized data - resource.update(self._saved_state[DIALOGUE_RESOURCES_KEY][rname]) + resource.update(self._saved_state[_DIALOGUE_RESOURCES_KEY][rname]) # Change from int to enum type resource.state = ResourceState(resource.state) self._resources[rname] = resource self._answering_functions = answering_functions - def start_dialogue(self): + if self._result.qtype == self._start_qtype: + # We just started this dialogue, + # save an empty dialogue state for this device + # (in order to resume dialogue upon next query) + self._start_dialogue() + + def _start_dialogue(self): """Save client's state as having started this dialogue""" # New empty dialogue state, with correct dialogue name self._set_dialogue_state( { - DIALOGUE_NAME_KEY: self._dialogue_name, - DIALOGUE_RESOURCES_KEY: {}, - DIALOGUE_LAST_INTERACTED_WITH_KEY: datetime.datetime.now(), + _DIALOGUE_NAME_KEY: self._dialogue_name, + _DIALOGUE_RESOURCES_KEY: {}, + _DIALOGUE_LAST_INTERACTED_WITH_KEY: datetime.datetime.now(), } ) @@ -363,9 +384,9 @@ def update_dialogue_state(self): # Save resources to client data self._set_dialogue_state( { - DIALOGUE_NAME_KEY: self._dialogue_name, - DIALOGUE_RESOURCES_KEY: self._resources, - DIALOGUE_LAST_INTERACTED_WITH_KEY: datetime.datetime.now(), + _DIALOGUE_NAME_KEY: self._dialogue_name, + _DIALOGUE_RESOURCES_KEY: self._resources, + _DIALOGUE_LAST_INTERACTED_WITH_KEY: datetime.datetime.now(), } ) @@ -375,10 +396,10 @@ def get_resource(self, name: str) -> Resource: def get_result(self) -> Result: return self._result - def get_answer(self) -> Optional[str]: + def get_answer(self) -> Optional[AnswerTuple]: # Executing callbacks - cbs: Optional[List[CallbackTupleType]] = self._result.get("callbacks") - curr_resource = self._resources[FINAL_RESOURCE_NAME] + cbs: Optional[List[_CallbackTupleType]] = self._result.get("callbacks") + curr_resource = self._resources[_FINAL_RESOURCE_NAME] if cbs: self._execute_callbacks_postorder(curr_resource, cbs, set()) @@ -388,28 +409,28 @@ def get_answer(self) -> Optional[str]: # Check if dialogue was cancelled if curr_resource.is_cancelled: - self._answer = self._answering_functions[FINAL_RESOURCE_NAME]( + self._answer_tuple = self._answering_functions[_FINAL_RESOURCE_NAME]( curr_resource, self ) - if not self._answer: + if not self._answer_tuple: raise ValueError("No answer for cancelled dialogue") - return self._answer + return self._answer_tuple # Iterate through resources (inorder traversal) # until one generates an answer - self._answer = self._get_answer_postorder(curr_resource, set()) + self._answer_tuple = self._get_answer_postorder(curr_resource, set()) - if self._resources[FINAL_RESOURCE_NAME].is_confirmed: + if self._resources[_FINAL_RESOURCE_NAME].is_confirmed: # Final callback (performing some operation with the dialogue's data) # should be called before ending dialogue self.end_dialogue() else: self.update_dialogue_state() - return self._answer + return self._answer_tuple def _get_answer_postorder( self, curr_resource: Resource, finished: Set[str] - ) -> Optional[str]: + ) -> Optional[AnswerTuple]: for rname in curr_resource.requires: if rname not in finished: finished.add(rname) @@ -421,7 +442,7 @@ def _get_answer_postorder( return None def _execute_callbacks_postorder( - self, curr_resource: Resource, cbs: List[CallbackTupleType], finished: Set[str] + self, curr_resource: Resource, cbs: List[_CallbackTupleType], finished: Set[str] ) -> None: for rname in curr_resource.requires: if rname not in finished: @@ -434,16 +455,16 @@ def _execute_callbacks_postorder( def _get_saved_dialogue_state(self) -> DialogueStructureType: """Load the dialogue state for a client""" - cd = self._q.client_data(DIALOGUE_KEY) + cd = self._q.client_data(_DIALOGUE_KEY) # Return empty DialogueStructureType in case no dialogue state exists dialogue_struct: DialogueStructureType = { - DIALOGUE_NAME_KEY: "", - DIALOGUE_RESOURCES_KEY: {}, - DIALOGUE_LAST_INTERACTED_WITH_KEY: datetime.datetime.now(), + _DIALOGUE_NAME_KEY: "", + _DIALOGUE_RESOURCES_KEY: {}, + _DIALOGUE_LAST_INTERACTED_WITH_KEY: datetime.datetime.now(), } if cd: ds_str = cd.get(self._dialogue_name) - if isinstance(ds_str, str) and ds_str != EMPTY_DIALOGUE_DATA: + if isinstance(ds_str, str) and ds_str != _EMPTY_DIALOGUE_DATA: # TODO: Add try-except block dialogue_struct = json.loads(ds_str, cls=DialogueJSONDecoder) return dialogue_struct @@ -455,14 +476,46 @@ def _set_dialogue_state(self, ds: DialogueStructureType) -> None: # Wrap data before saving dialogue state into client data # (due to custom JSON serialization) cd = {self._dialogue_name: ds_json} - self._q.set_client_data(DIALOGUE_KEY, cast(ClientDataDict, cd)) + self._q.set_client_data(_DIALOGUE_KEY, cast(ClientDataDict, cd)) + + def set_resource_state(self, resource_name: str, state: ResourceState): + """ + Set the state of a resource. + Sets state of all parent resources to unfulfilled + if cascade_state is set to True for the resource. + """ + resource = self._resources[resource_name] + lowered_state = resource.state > state + resource.state = state + if resource.cascade_state and lowered_state: + # Find all parent resources and set to corresponding state + parents = self._find_parent_resources(resource_name) + if parents: + for parent in parents: + parent.state = ResourceState.UNFULFILLED + + def _find_parent_resources(self, resource_name: str) -> Optional[Set[Resource]]: + """Find all parent resources of a resource""" + all_parents: Set[Resource] = set() + ap_len: int + i = 0 + while i < len(self._resources): + for resource in self._resources.values(): + ap_len = len(all_parents) + if resource_name in resource.requires and resource not in all_parents: + all_parents.add(resource) + break + # If no new parent resources added to ps, return all_parents + if len(all_parents) == ap_len: + return all_parents + i += 1 def end_dialogue(self) -> None: """End the client's current dialogue""" # TODO: Doesn't allow multiple conversations at once # (set_client_data overwrites other conversations) self._q.set_client_data( - DIALOGUE_KEY, {self._dialogue_name: EMPTY_DIALOGUE_DATA} + _DIALOGUE_KEY, {self._dialogue_name: _EMPTY_DIALOGUE_DATA} ) def set_error(self) -> None: diff --git a/queries/fruitseller_module.py b/queries/fruitseller_module.py index f6256f38..5927ae9e 100644 --- a/queries/fruitseller_module.py +++ b/queries/fruitseller_module.py @@ -6,7 +6,7 @@ from query import Query, QueryStateDict from tree import Result, Node, TerminalNode from reynir import NounPhrase -from queries import gen_answer, parse_num, natlang_seq, sing_or_plur +from queries import gen_answer, AnswerTuple, parse_num, natlang_seq, sing_or_plur from queries.dialogue import ( AnsweringFunctionMap, DateResource, @@ -146,65 +146,66 @@ def _generate_fruit_answer( resource: ListResource, dsm: DialogueStateManager -) -> Optional[str]: +) -> Optional[AnswerTuple]: result = dsm.get_result() if result.get("fruitsEmpty"): - return resource.prompts["empty"] + return gen_answer(resource.prompts["empty"]) if result.get("fruitOptions"): - return resource.prompts["options"] + return gen_answer(resource.prompts["options"]) if resource.is_unfulfilled: - return resource.prompts["initial"] + return gen_answer(resource.prompts["initial"]) if resource.is_partially_fulfilled: ans: str = "" if "actually_removed_something" in result: if not result["actually_removed_something"]: ans += "Ég fann ekki ávöxtinn sem þú vildir fjarlægja. " - return ( + return gen_answer( ans - + f"{resource.prompts['repeat'].format(list_items = _list_items(resource.data))}" + + resource.prompts["repeat"].format(list_items=_list_items(resource.data)) ) if resource.is_fulfilled: - return f"{resource.prompts['confirm'].format(list_items = _list_items(resource.data))}" + return gen_answer( + resource.prompts["confirm"].format(list_items=_list_items(resource.data)) + ) return None def _generate_datetime_answer( resource: Resource, dsm: DialogueStateManager -) -> Optional[str]: +) -> Optional[AnswerTuple]: ans: Optional[str] = None date_resource: DateResource = cast(DateResource, dsm.get_resource("Date")) time_resource: TimeResource = cast(TimeResource, dsm.get_resource("Time")) if resource.is_unfulfilled: - return resource.prompts["initial"] - - if resource.is_partially_fulfilled: + ans = resource.prompts["initial"] + elif resource.is_partially_fulfilled: if date_resource.is_fulfilled: ans = resource.prompts["date_fulfilled"].format( date=date_resource.data.strftime("%Y/%m/%d") ) - if time_resource.is_fulfilled: + elif time_resource.is_fulfilled: ans = resource.prompts["time_fulfilled"].format( time=time_resource.data.strftime("%H:%M") ) - return ans - - if resource.is_fulfilled: + elif resource.is_fulfilled: ans = resource.prompts["confirm"].format( date_time=datetime.datetime.combine( date_resource.data, time_resource.data, ).strftime("%Y/%m/%d %H:%M") ) - return ans + if ans: + return gen_answer(ans) + return None def _generate_final_answer( resource: FinalResource, dsm: DialogueStateManager -) -> Optional[str]: +) -> Optional[AnswerTuple]: ans: Optional[str] = None if resource.is_cancelled: - return resource.prompts["cancelled"] + return gen_answer(resource.prompts["cancelled"]) resource.state = ResourceState.CONFIRMED date_resource = dsm.get_resource("Date") @@ -216,7 +217,7 @@ def _generate_final_answer( time_resource.data, ).strftime("%Y/%m/%d %H:%M"), ) - return ans + return gen_answer(ans) def _list_items(items: Any) -> str: @@ -350,7 +351,7 @@ def QNumOfFruit(node: Node, params: QueryStateDict, result: Result): result.queryfruits.append([1, result.fruit]) else: result.queryfruits.append([result.fruitnumber, result.fruit]) - + _DIALOGUE_INFO["strings"] = result.queryfruits def QNum(node: Node, params: QueryStateDict, result: Result): fruitnumber = int(parse_num(node, result._nominative)) @@ -358,7 +359,7 @@ def QNum(node: Node, params: QueryStateDict, result: Result): result.fruitnumber = fruitnumber else: result.fruitnumber = 1 - + _DIALOGUE_INFO["numbers"] = result.fruitnumber def QFruit(node: Node, params: QueryStateDict, result: Result): fruit = result._root @@ -467,15 +468,15 @@ def QFruitInfoQuery(node: Node, params: QueryStateDict, result: Result): result.qtype = "QFruitInfo" -_ANSWERING_FUNCTIONS: AnsweringFunctionMap = cast( - AnsweringFunctionMap, - { - "Fruits": _generate_fruit_answer, - "DateTime": _generate_datetime_answer, - "Final": _generate_final_answer, - }, -) +def my_test(r: Resource, dsm: DialogueStateManager) -> Optional[AnswerTuple]: + return gen_answer("hello") +_ANSWERING_FUNCTIONS: AnsweringFunctionMap = { + "Fruits": _generate_fruit_answer, + "DateTime": _generate_datetime_answer, + "Final": _generate_final_answer, + "something": my_test, +} def sentence(state: QueryStateDict, result: Result) -> None: """Called when sentence processing is complete""" @@ -488,16 +489,20 @@ def sentence(state: QueryStateDict, result: Result) -> None: # Successfully matched a query type try: + print("INFO:", _DIALOGUE_INFO) dsm.setup_dialogue(_ANSWERING_FUNCTIONS) - if result.qtype == _START_DIALOGUE_QTYPE: - dsm.start_dialogue() - elif result.qtype == "QFruitInfo": + if result.qtype == "QFruitInfo": # Example info handling functionality - ans = "Ávaxtapöntunin þín er bara flott. " + # ans = "Ávaxtapöntunin þín er bara flott. " # f = dsm.get_resource("Fruits") # ans += str(f.data) - ans += dsm.get_answer() or "" - q.set_answer(*gen_answer(ans)) + ans = dsm.get_answer() + if not ans: + print("No answer generated") + q.set_error("E_QUERY_NOT_UNDERSTOOD") + return + + q.set_answer(*ans) return ans = dsm.get_answer() @@ -507,7 +512,7 @@ def sentence(state: QueryStateDict, result: Result) -> None: return q.set_qtype(result.qtype) - q.set_answer(*gen_answer(ans)) + q.set_answer(*ans) return except Exception as e: logging.warning( diff --git a/queries/theater/theater.toml b/queries/theater/theater.toml index e9fc49e8..3865743c 100644 --- a/queries/theater/theater.toml +++ b/queries/theater/theater.toml @@ -4,6 +4,7 @@ dialogue_name = "theater" name = "Show" type = "ListResource" max_items = 1 +cascade_state = true prompts.initial = "Hvaða sýningu má bjóða þér að fara á?" prompts.options = "Sýningarnar sem eru í boði eru: {options}" prompts.confirm = "Þú valdir sýninguna {show}, viltu halda áfram með pöntunina?" diff --git a/queries/theater_module.py b/queries/theater_module.py index 43c14c35..3632c27d 100644 --- a/queries/theater_module.py +++ b/queries/theater_module.py @@ -934,9 +934,6 @@ def sentence(state: QueryStateDict, result: Result) -> None: print("A") result.shows = _fetch_shows() dsm.setup_dialogue(_ANSWERING_FUNCTIONS) - if result.qtype == _START_DIALOGUE_QTYPE: - print("B") - dsm.start_dialogue() print("C") print(dsm._resources) ans = dsm.get_answer() From 2264d6e332670dbb400bbd3cf93ed4f0b926ab3f Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Fri, 1 Jul 2022 14:45:22 +0000 Subject: [PATCH 147/371] added grammar to grammar file --- queries/grammars/iot_hue.grammar | 19 +- queries/grammars/iot_speakers.grammar | 142 +++++++++++ queries/iot_hue.py | 326 +----------------------- queries/iot_speakers.py | 346 +------------------------- 4 files changed, 155 insertions(+), 678 deletions(-) create mode 100644 queries/grammars/iot_speakers.grammar diff --git a/queries/grammars/iot_hue.grammar b/queries/grammars/iot_hue.grammar index 02b6fd2a..99443577 100644 --- a/queries/grammars/iot_hue.grammar +++ b/queries/grammars/iot_hue.grammar @@ -1,15 +1,12 @@ + /þgf = þgf /ef = ef Query → - QIoT + QIoT '?'? QIoT → - QIoTQuery '?'? - | QIoTConnectLights '?'? - -QIoTConnectLights → - "tengdu" "ljósin" + QIoTQuery QIoTQuery -> QIoTMakeVerb QIoTMakeRest @@ -80,9 +77,9 @@ QIoTLetRest -> QIoTSubject/þf QIoTHvar? QIoTHvernigLet | QIoTSubject/þf QIoTHvernigLet QIoTHvar? | QIoTHvar? QIoTSubject/þf QIoTHvernigLet - | QIoTHvar? QIoTHvernigLet QIoTSubject/þf - | QIoTHvernigLet QIoTSubject/þf QIoTHvar? - | QIoTHvernigLet QIoTHvar? QIoTSubject/þf + # | QIoTHvar? QIoTHvernigLet QIoTSubject/þf + # | QIoTHvernigLet QIoTSubject/þf QIoTHvar? + # | QIoTHvernigLet QIoTHvar? QIoTSubject/þf QIoTTurnOnRest -> QIoTTurnOnLightsRest @@ -195,7 +192,7 @@ QIoTLightName/fall -> no/fall QIoTColorName -> - {" | ".join(f"'{color}:lo'" for color in _COLORS.keys())} + {color_names} QIoTSceneName -> no @@ -320,4 +317,4 @@ QIoTBrightnessOrSettingWord/fall -> | QIoTSettingWord/fall QIoTSettingWord/fall -> - 'stilling'/fall \ No newline at end of file + 'stilling'/fall diff --git a/queries/grammars/iot_speakers.grammar b/queries/grammars/iot_speakers.grammar new file mode 100644 index 00000000..4ba79481 --- /dev/null +++ b/queries/grammars/iot_speakers.grammar @@ -0,0 +1,142 @@ +/þgf = þgf +/ef = ef + +Query → + QIoTSpeaker '?'? + +QIoTSpeaker → + QIoTSpeakerQuery + +QIoTSpeakerQuery -> + QIoTSpeakerMakeVerb QIoTSpeakerMakeRest + | QIoTSpeakerSetVerb QIoTSpeakerSetRest + # | QIoTSpeakerChangeVerb QIoTSpeakerChangeRest + | QIoTSpeakerLetVerb QIoTSpeakerLetRest + | QIoTSpeakerTurnOnVerb QIoTSpeakerTurnOnRest + | QIoTSpeakerTurnOffVerb QIoTSpeakerTurnOffRest + | QIoTSpeakerPlayVerb QIoTSpeakerPlayRest + | QIoTSpeakerIncreaseOrDecreaseVerb QIoTSpeakerIncreaseOrDecreaseRest + +QIoTSpeakerMakeVerb -> + 'gera:so'_bh + +QIoTSpeakerSetVerb -> + 'setja:so'_bh + | 'stilla:so'_bh + +QIoTSpeakerChangeVerb -> + 'breyta:so'_bh + +QIoTSpeakerLetVerb -> + 'láta:so'_bh + +QIoTSpeakerTurnOnVerb -> + 'kveikja:so'_bh + +QIoTSpeakerTurnOffVerb -> + 'slökkva:so'_bh + +QIoTSpeakerPlayVerb -> + 'spila:so'_bh + | "spilaðu" + +QIoTSpeakerIncreaseOrDecreaseVerb -> + QIoTSpeakerIncreaseVerb + | QIoTSpeakerDecreaseVerb + +QIoTSpeakerIncreaseVerb -> + 'hækka:so'_bh + | 'auka:so'_bh + +QIoTSpeakerDecreaseVerb -> + 'lækka:so'_bh + | 'minnka:so'_bh + +QIoTSpeakerMakeRest -> + # QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigMake + # | QCHANGESubject/þf QCHANGEHvernigMake QCHANGEHvar? + # | QCHANGEHvar? QCHANGESubject/þf QCHANGEHvernigMake + # | QCHANGEHvar? QCHANGEHvernigMake QCHANGESubject/þf + # | QCHANGEHvernigMake QCHANGESubject/þf QCHANGEHvar? + # | QCHANGEHvernigMake QCHANGEHvar? QCHANGESubject/þf + QIoTSpeakerMusicWord QIoTSpeakerHvar? + +# TODO: Add support for "stilltu rauðan lit á ljósið í eldhúsinu" +QIoTSpeakerSetRest -> + # QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigSet + # | QCHANGESubject/þf QCHANGEHvernigSet QCHANGEHvar? + # | QCHANGEHvar? QCHANGESubject/þf QCHANGEHvernigSet + # | QCHANGEHvar? QCHANGEHvernigSet QCHANGESubject/þf + # | QCHANGEHvernigSet QCHANGESubject/þf QCHANGEHvar? + # | QCHANGEHvernigSet QCHANGEHvar? QCHANGESubject/þf + "á" QIoTSpeakerMusicWord QIoTSpeakerHvar? + +# QIoTSpeakerChangeRest -> + # QCHANGESubjectOne/þgf QCHANGEHvar? QCHANGEHvernigChange + # | QCHANGESubjectOne/þgf QCHANGEHvernigChange QCHANGEHvar? + # | QCHANGEHvar? QCHANGESubjectOne/þgf QCHANGEHvernigChange + # | QCHANGEHvar? QCHANGEHvernigChange QCHANGESubjectOne/þgf + # | QCHANGEHvernigChange QCHANGESubjectOne/þgf QCHANGEHvar? + # | QCHANGEHvernigChange QCHANGEHvar? QCHANGESubjectOne/þgf + +QIoTSpeakerLetRest -> + # QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigLet + # | QCHANGESubject/þf QCHANGEHvernigLet QCHANGEHvar? + # | QCHANGEHvar? QCHANGESubject/þf QCHANGEHvernigLet + # | QCHANGEHvar? QCHANGEHvernigLet QCHANGESubject/þf + # | QCHANGEHvernigLet QCHANGESubject/þf QCHANGEHvar? + # | QCHANGEHvernigLet QCHANGEHvar? QCHANGESubject/þf + QIoTSpeakerBe QIoTSpeakerMusicWord QIoTSpeakerHvar? + | "á" QIoTSpeakerMusicWord QIoTSpeakerHvar? + +QIoTSpeakerTurnOnRest -> + # QCHANGETurnOnLightsRest + # | QCHANGEAHverju QCHANGEHvar? + # | QCHANGEHvar? QCHANGEAHverju + "á" QIoTSpeakerMusicWord QIoTSpeakerHvar? + +# Would be good to add "slökktu á rauða litnum" functionality +QIoTSpeakerTurnOffRest -> + # QCHANGETurnOffLightsRest + "á" QIoTSpeakerMusicWord QIoTSpeakerHvar? + +QIoTSpeakerPlayRest -> + QIoTSpeakerMusicWord QIoTSpeakerHvar? + | "tónlist" + +# TODO: Make the subject categorization cleaner +QIoTSpeakerIncreaseOrDecreaseRest -> + # QCHANGELightSubject/þf QCHANGEHvar? + # | QCHANGEBrightnessSubject/þf QCHANGEHvar? + QIoTSpeakerMusicWord QIoTSpeakerHvar? + | "í" QIoTSpeakerMusicWord QIoTSpeakerHvar? + +# There is a bug when trying to +QIoTSpeakerMusicWord -> + "tónlist" + # | 'tónlist:no' + +QIoTSpeakerHvar -> + QIoTSpeakerLocationPreposition QIoTSpeakerGroupName/þgf + +QIoTSpeakerLocationPreposition -> + QIoTSpeakerLocationPrepositionFirstPart? QIoTSpeakerLocationPrepositionSecondPart + +# The latter proverbs are grammatically incorrect, but common errors, both in speech and transcription. +# The list provided is taken from StefnuAtv in Greynir.grammar. That includes "aftur:ao", which is not applicable here. +QIoTSpeakerLocationPrepositionFirstPart -> + StaðarAtv + | "fram:ao" + | "inn:ao" + | "niður:ao" + | "upp:ao" + | "út:ao" + +QIoTSpeakerLocationPrepositionSecondPart -> + "á" | "í" + +QIoTSpeakerGroupName/fall -> + no/fall + +QIoTSpeakerBe -> + 'vera:so'_nh \ No newline at end of file diff --git a/queries/iot_hue.py b/queries/iot_hue.py index b5b16610..cc95dc69 100755 --- a/queries/iot_hue.py +++ b/queries/iot_hue.py @@ -111,329 +111,9 @@ def help_text(lemma: str) -> str: # The context-free grammar for the queries recognized by this plug-in module # GRAMMAR = read_grammar_file("iot_hue") -GRAMMAR = f""" - -/þgf = þgf -/ef = ef - -Query → - QIoT '?'? - -QIoT → - QIoTQuery - -QIoTQuery -> - QIoTMakeVerb QIoTMakeRest - | QIoTSetVerb QIoTSetRest - | QIoTChangeVerb QIoTChangeRest - | QIoTLetVerb QIoTLetRest - | QIoTTurnOnVerb QIoTTurnOnRest - | QIoTTurnOffVerb QIoTTurnOffRest - | QIoTIncreaseOrDecreaseVerb QIoTIncreaseOrDecreaseRest - -QIoTMakeVerb -> - 'gera:so'_bh - -QIoTSetVerb -> - 'setja:so'_bh - | 'stilla:so'_bh - -QIoTChangeVerb -> - 'breyta:so'_bh - -QIoTLetVerb -> - 'láta:so'_bh - -QIoTTurnOnVerb -> - 'kveikja:so'_bh - -QIoTTurnOffVerb -> - 'slökkva:so'_bh - -QIoTIncreaseOrDecreaseVerb -> - QIoTIncreaseVerb - | QIoTDecreaseVerb - -QIoTIncreaseVerb -> - 'hækka:so'_bh - | 'auka:so'_bh - -QIoTDecreaseVerb -> - 'lækka:so'_bh - | 'minnka:so'_bh - -QIoTMakeRest -> - QIoTSubject/þf QIoTHvar? QIoTHvernigMake - | QIoTSubject/þf QIoTHvernigMake QIoTHvar? - | QIoTHvar? QIoTSubject/þf QIoTHvernigMake - | QIoTHvar? QIoTHvernigMake QIoTSubject/þf - | QIoTHvernigMake QIoTSubject/þf QIoTHvar? - | QIoTHvernigMake QIoTHvar? QIoTSubject/þf - -# TODO: Add support for "stilltu rauðan lit á ljósið í eldhúsinu" -QIoTSetRest -> - QIoTSubject/þf QIoTHvar? QIoTHvernigSet - | QIoTSubject/þf QIoTHvernigSet QIoTHvar? - | QIoTHvar? QIoTSubject/þf QIoTHvernigSet - | QIoTHvar? QIoTHvernigSet QIoTSubject/þf - | QIoTHvernigSet QIoTSubject/þf QIoTHvar? - | QIoTHvernigSet QIoTHvar? QIoTSubject/þf - -QIoTChangeRest -> - QIoTSubjectOne/þgf QIoTHvar? QIoTHvernigChange - | QIoTSubjectOne/þgf QIoTHvernigChange QIoTHvar? - | QIoTHvar? QIoTSubjectOne/þgf QIoTHvernigChange - | QIoTHvar? QIoTHvernigChange QIoTSubjectOne/þgf - | QIoTHvernigChange QIoTSubjectOne/þgf QIoTHvar? - | QIoTHvernigChange QIoTHvar? QIoTSubjectOne/þgf - -QIoTLetRest -> - QIoTSubject/þf QIoTHvar? QIoTHvernigLet - | QIoTSubject/þf QIoTHvernigLet QIoTHvar? - | QIoTHvar? QIoTSubject/þf QIoTHvernigLet - # | QIoTHvar? QIoTHvernigLet QIoTSubject/þf - # | QIoTHvernigLet QIoTSubject/þf QIoTHvar? - # | QIoTHvernigLet QIoTHvar? QIoTSubject/þf - -QIoTTurnOnRest -> - QIoTTurnOnLightsRest - | QIoTAHverju QIoTHvar? - | QIoTHvar? QIoTAHverju - -QIoTTurnOnLightsRest -> - QIoTLightSubject/þf QIoTHvar? - | QIoTHvar QIoTLightSubject/þf? - -# Would be good to add "slökktu á rauða litnum" functionality -QIoTTurnOffRest -> - QIoTTurnOffLightsRest - -QIoTTurnOffLightsRest -> - QIoTLightSubject/þf QIoTHvar? - | QIoTHvar QIoTLightSubject/þf? - -# TODO: Make the subject categorization cleaner -QIoTIncreaseOrDecreaseRest -> - QIoTLightSubject/þf QIoTHvar? - | QIoTBrightnessSubject/þf QIoTHvar? - -QIoTSubject/fall -> - QIoTSubjectOne/fall - | QIoTSubjectTwo/fall - -# TODO: Decide whether LightSubject/þgf should be accepted -QIoTSubjectOne/fall -> - QIoTLightSubject/fall - | QIoTColorSubject/fall - | QIoTBrightnessSubject/fall - | QIoTSceneSubject/fall - -QIoTSubjectTwo/fall -> - QIoTGroupNameSubject/fall # á bara að styðja "gerðu eldhúsið rautt", "gerðu eldhúsið rómó" "gerðu eldhúsið bjartara", t.d. - -QIoTHvar -> - QIoTLocationPreposition QIoTGroupName/þgf - -QIoTHvernigMake -> - QIoTAnnadAndlag # gerðu litinn rauðan í eldhúsinu EÐA gerðu birtuna meiri í eldhúsinu - | QIoTAdHverju # gerðu litinn að rauðum í eldhúsinu - | QIoTThannigAd - -QIoTHvernigSet -> - QIoTAHvad - | QIoTThannigAd - -QIoTHvernigChange -> - QIoTIHvad - | QIoTThannigAd - -QIoTHvernigLet -> - QIoTBecome QIoTSomethingOrSomehow - | QIoTBe QIoTSomehow - -QIoTThannigAd -> - "þannig" "að"? pfn_nf QIoTBeOrBecomeSubjunctive QIoTAnnadAndlag - -# I think these verbs only appear in these forms. -# In which case these terminals should be deleted and a direct reference should be made in the relevant non-terminals. -QIoTBe -> - "vera" - -QIoTBecome -> - "verða" - -QIoTBeOrBecomeSubjunctive -> - "verði" - | "sé" - -QIoTLightSubject/fall -> - QIoTLight/fall - -QIoTColorSubject/fall -> - QIoTColorWord/fall QIoTLight/ef? - | QIoTColorWord/fall "á" QIoTLight/þgf - -QIoTBrightnessSubject/fall -> - QIoTBrightnessWord/fall QIoTLight/ef? - | QIoTBrightnessWord/fall "á" QIoTLight/þgf - -QIoTSceneSubject/fall -> - QIoTSceneWord/fall - -QIoTGroupNameSubject/fall -> - QIoTGroupName/fall - -QIoTLocationPreposition -> - QIoTLocationPrepositionFirstPart? QIoTLocationPrepositionSecondPart - -# The latter proverbs are grammatically incorrect, but common errors, both in speech and transcription. -# The list provided is taken from StefnuAtv in Greynir.grammar. That includes "aftur:ao", which is not applicable here. -QIoTLocationPrepositionFirstPart -> - StaðarAtv - | "fram:ao" - | "inn:ao" - | "niður:ao" - | "upp:ao" - | "út:ao" - -QIoTLocationPrepositionSecondPart -> - "á" | "í" - -QIoTGroupName/fall -> - no/fall - -QIoTLightName/fall -> - no/fall - -QIoTColorName -> - {" | ".join(f"'{color}:lo'" for color in _COLORS.keys())} - -QIoTSceneName -> - no - | lo - -QIoTAnnadAndlag -> - QIoTNewSetting/nf - | QIoTSpyrjaHuldu/nf - -QIoTAdHverju -> - "að" QIoTNewSetting/þgf - -QIoTAHvad -> - "á" QIoTNewSetting/þf - -QIoTIHvad -> - "í" QIoTNewSetting/þf - -QIoTAHverju -> - "á" QIoTLight/þgf - | "á" QIoTNewSetting/þgf - -QIoTSomethingOrSomehow -> - QIoTAnnadAndlag - | QIoTAdHverju - -QIoTSomehow -> - QIoTAnnadAndlag - | QIoTThannigAd - -QIoTLight/fall -> - QIoTLightName/fall - | QIoTLightWord/fall - -# Should 'birta' be included -QIoTLightWord/fall -> - 'ljós'/fall - | 'lýsing'/fall - | 'birta'/fall - | 'Birta'/fall - -QIoTColorWord/fall -> - 'litur'/fall - | 'litblær'/fall - | 'blær'/fall - -QIoTBrightnessWords/fall -> - 'bjartur'/fall - | QIoTBrightnessWord/fall - -QIoTBrightnessWord/fall -> - 'birta'/fall - | 'Birta'/fall - | 'birtustig'/fall - -QIoTSceneWord/fall -> - 'sena'/fall - | 'stemning'/fall - | 'stemming'/fall - | 'stemmning'/fall - -# Need to ask Hulda how this works. -QIoTSpyrjaHuldu/fall -> - # QIoTHuldaColor/fall - QIoTHuldaBrightness/fall - # | QIoTHuldaScene/fall - -# Do I need a "new light state" non-terminal? -QIoTNewSetting/fall -> - QIoTNewColor/fall - | QIoTNewBrightness/fall - | QIoTNewScene/fall - -# Missing "meira dimmt" -QIoTHuldaBrightness/fall -> - QIoTMoreBrighterOrHigher/fall QIoTBrightnessWords/fall? - | QIoTLessDarkerOrLower/fall QIoTBrightnessWords/fall? - -#Unsure about whether to include /fall after QIoTColorName -QIoTNewColor/fall -> - QIoTColorWord/fall QIoTColorName - | QIoTColorName QIoTColorWord/fall? - -QIoTNewBrightness/fall -> - 'sá'/fall? QIoTBrightestOrDarkest/fall - | QIoTBrightestOrDarkest/fall QIoTBrightnessOrSettingWord/fall - -QIoTNewScene/fall -> - QIoTSceneWord/fall QIoTSceneName - | QIoTSceneName QIoTSceneWord/fall? - -QIoTMoreBrighterOrHigher/fall -> - 'mikill:lo'_mst/fall - | 'bjartur:lo'_mst/fall - | 'ljós:lo'_mst/fall - | 'hár:lo'_mst/fall - -QIoTLessDarkerOrLower/fall -> - 'lítill:lo'_mst/fall - | 'dökkur:lo'_mst/fall - | 'dimmur:lo'_mst/fall - | 'lágur:lo'_mst/fall - -QIoTBrightestOrDarkest/fall -> - QIoTBrightest/fall - | QIoTDarkest/fall - -QIoTBrightest/fall -> - 'bjartur:lo'_evb - | 'bjartur:lo'_esb - | 'ljós:lo'_evb - | 'ljós:lo'_esb - -QIoTDarkest/fall -> - 'dimmur:lo'_evb - | 'dimmur:lo'_esb - | 'dökkur:lo'_evb - | 'dökkur:lo'_esb - -QIoTBrightnessOrSettingWord/fall -> - QIoTBrightnessWord/fall - | QIoTSettingWord/fall - -QIoTSettingWord/fall -> - 'stilling'/fall - -""" +GRAMMAR = read_grammar_file( + "iot_hue", color_names=" | ".join(f"'{color}:lo'" for color in _COLORS.keys()) +) def QIoTColorWord(node: Node, params: QueryStateDict, result: Result) -> None: diff --git a/queries/iot_speakers.py b/queries/iot_speakers.py index 5a2c8885..499937fe 100644 --- a/queries/iot_speakers.py +++ b/queries/iot_speakers.py @@ -47,7 +47,7 @@ from query import Query, QueryStateDict, AnswerTuple from queries import gen_answer, read_jsfile, read_grammar_file -from queries.sonos import SonosClient, update_sonos_token +from queries.sonos import SonosClient from tree import Result, Node, TerminalNode from util import read_api_key @@ -86,349 +86,7 @@ def help_text(lemma: str) -> str: # The context-free grammar for the queries recognized by this plug-in module # GRAMMAR = read_grammar_file("iot_hue") -GRAMMAR = f""" - -/þgf = þgf -/ef = ef - -Query → - QIoTSpeaker '?'? - -QIoTSpeaker → - QIoTSpeakerQuery - -QIoTSpeakerQuery -> - QIoTSpeakerMakeVerb QIoTSpeakerMakeRest - | QIoTSpeakerSetVerb QIoTSpeakerSetRest - # | QIoTSpeakerChangeVerb QIoTSpeakerChangeRest - | QIoTSpeakerLetVerb QIoTSpeakerLetRest - | QIoTSpeakerTurnOnVerb QIoTSpeakerTurnOnRest - | QIoTSpeakerTurnOffVerb QIoTSpeakerTurnOffRest - | QIoTSpeakerPlayVerb QIoTSpeakerPlayRest - | QIoTSpeakerIncreaseOrDecreaseVerb QIoTSpeakerIncreaseOrDecreaseRest - -QIoTSpeakerMakeVerb -> - 'gera:so'_bh - -QIoTSpeakerSetVerb -> - 'setja:so'_bh - | 'stilla:so'_bh - -QIoTSpeakerChangeVerb -> - 'breyta:so'_bh - -QIoTSpeakerLetVerb -> - 'láta:so'_bh - -QIoTSpeakerTurnOnVerb -> - 'kveikja:so'_bh - -QIoTSpeakerTurnOffVerb -> - 'slökkva:so'_bh - -QIoTSpeakerPlayVerb -> - 'spila:so'_bh - | "spilaðu" - -QIoTSpeakerIncreaseOrDecreaseVerb -> - QIoTSpeakerIncreaseVerb - | QIoTSpeakerDecreaseVerb - -QIoTSpeakerIncreaseVerb -> - 'hækka:so'_bh - | 'auka:so'_bh - -QIoTSpeakerDecreaseVerb -> - 'lækka:so'_bh - | 'minnka:so'_bh - -QIoTSpeakerMakeRest -> - # QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigMake - # | QCHANGESubject/þf QCHANGEHvernigMake QCHANGEHvar? - # | QCHANGEHvar? QCHANGESubject/þf QCHANGEHvernigMake - # | QCHANGEHvar? QCHANGEHvernigMake QCHANGESubject/þf - # | QCHANGEHvernigMake QCHANGESubject/þf QCHANGEHvar? - # | QCHANGEHvernigMake QCHANGEHvar? QCHANGESubject/þf - QIoTSpeakerMusicWord QIoTSpeakerHvar? - -# TODO: Add support for "stilltu rauðan lit á ljósið í eldhúsinu" -QIoTSpeakerSetRest -> - # QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigSet - # | QCHANGESubject/þf QCHANGEHvernigSet QCHANGEHvar? - # | QCHANGEHvar? QCHANGESubject/þf QCHANGEHvernigSet - # | QCHANGEHvar? QCHANGEHvernigSet QCHANGESubject/þf - # | QCHANGEHvernigSet QCHANGESubject/þf QCHANGEHvar? - # | QCHANGEHvernigSet QCHANGEHvar? QCHANGESubject/þf - "á" QIoTSpeakerMusicWord QIoTSpeakerHvar? - -# QIoTSpeakerChangeRest -> - # QCHANGESubjectOne/þgf QCHANGEHvar? QCHANGEHvernigChange - # | QCHANGESubjectOne/þgf QCHANGEHvernigChange QCHANGEHvar? - # | QCHANGEHvar? QCHANGESubjectOne/þgf QCHANGEHvernigChange - # | QCHANGEHvar? QCHANGEHvernigChange QCHANGESubjectOne/þgf - # | QCHANGEHvernigChange QCHANGESubjectOne/þgf QCHANGEHvar? - # | QCHANGEHvernigChange QCHANGEHvar? QCHANGESubjectOne/þgf - -QIoTSpeakerLetRest -> - # QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigLet - # | QCHANGESubject/þf QCHANGEHvernigLet QCHANGEHvar? - # | QCHANGEHvar? QCHANGESubject/þf QCHANGEHvernigLet - # | QCHANGEHvar? QCHANGEHvernigLet QCHANGESubject/þf - # | QCHANGEHvernigLet QCHANGESubject/þf QCHANGEHvar? - # | QCHANGEHvernigLet QCHANGEHvar? QCHANGESubject/þf - QIoTSpeakerBe QIoTSpeakerMusicWord QIoTSpeakerHvar? - | "á" QIoTSpeakerMusicWord QIoTSpeakerHvar? - -QIoTSpeakerTurnOnRest -> - # QCHANGETurnOnLightsRest - # | QCHANGEAHverju QCHANGEHvar? - # | QCHANGEHvar? QCHANGEAHverju - "á" QIoTSpeakerMusicWord QIoTSpeakerHvar? - -# QCHANGETurnOnLightsRest -> -# QCHANGELightSubject/þf QCHANGEHvar? -# | QCHANGEHvar QCHANGELightSubject/þf? - -# Would be good to add "slökktu á rauða litnum" functionality -QIoTSpeakerTurnOffRest -> - # QCHANGETurnOffLightsRest - "á" QIoTSpeakerMusicWord QIoTSpeakerHvar? - -# QCHANGETurnOffLightsRest -> -# QCHANGELightSubject/þf QCHANGEHvar? -# | QCHANGEHvar QCHANGELightSubject/þf? - -QIoTSpeakerPlayRest -> - QIoTSpeakerMusicWord QIoTSpeakerHvar? - | "tónlist" - -# TODO: Make the subject categorization cleaner -QIoTSpeakerIncreaseOrDecreaseRest -> - # QCHANGELightSubject/þf QCHANGEHvar? - # | QCHANGEBrightnessSubject/þf QCHANGEHvar? - QIoTSpeakerMusicWord QIoTSpeakerHvar? - | "í" QIoTSpeakerMusicWord QIoTSpeakerHvar? - -# QCHANGESubject/fall -> -# QCHANGESubjectOne/fall -# | QCHANGESubjectTwo/fall - -# There is a bug when trying to -QIoTSpeakerMusicWord -> - "tónlist" - # | 'tónlist:no' - -# # TODO: Decide whether LightSubject/þgf should be accepted -# QCHANGESubjectOne/fall -> -# QCHANGELightSubject/fall -# | QCHANGEColorSubject/fall -# | QCHANGEBrightnessSubject/fall -# | QCHANGESceneSubject/fall - -# QCHANGESubjectTwo/fall -> -# QCHANGEGroupNameSubject/fall # á bara að styðja "gerðu eldhúsið rautt", "gerðu eldhúsið rómó" "gerðu eldhúsið bjartara", t.d. - -QIoTSpeakerHvar -> - QIoTSpeakerLocationPreposition QIoTSpeakerGroupName/þgf - -# QCHANGEHvernigMake -> -# QCHANGEAnnadAndlag # gerðu litinn rauðan í eldhúsinu EÐA gerðu birtuna meiri í eldhúsinu -# | QCHANGEAdHverju # gerðu litinn að rauðum í eldhúsinu -# | QCHANGEThannigAd - -# QCHANGEHvernigSet -> -# QCHANGEAHvad -# | QCHANGEThannigAd - -# QCHANGEHvernigChange -> -# QCHANGEIHvad -# | QCHANGEThannigAd - -# QCHANGEHvernigLet -> -# QCHANGEBecome QCHANGESomethingOrSomehow -# | QCHANGEBe QCHANGESomehow - -# QCHANGEThannigAd -> -# "þannig" "að"? pfn_nf QCHANGEBeOrBecomeSubjunctive QCHANGEAnnadAndlag - -# I think these verbs only appear in these forms. -# In which case these terminals should be deleted and a direct reference should be made in the relevant non-terminals. -QIoTSpeakerBe -> - 'vera:so'_nh - -# QCHANGEBecome -> -# "verða" - -# QCHANGEBeOrBecomeSubjunctive -> -# "verði" -# | "sé" - -# QCHANGELightSubject/fall -> -# QCHANGELight/fall - -# QCHANGEColorSubject/fall -> -# QCHANGEColorWord/fall QCHANGELight/ef? -# | QCHANGEColorWord/fall "á" QCHANGELight/þgf - -# QCHANGEBrightnessSubject/fall -> -# QCHANGEBrightnessWord/fall QCHANGELight/ef? -# | QCHANGEBrightnessWord/fall "á" QCHANGELight/þgf - -# QCHANGESceneSubject/fall -> -# QCHANGESceneWord/fall - -# QCHANGEGroupNameSubject/fall -> -# QCHANGEGroupName/fall - -QIoTSpeakerLocationPreposition -> - QIoTSpeakerLocationPrepositionFirstPart? QIoTSpeakerLocationPrepositionSecondPart - -# The latter proverbs are grammatically incorrect, but common errors, both in speech and transcription. -# The list provided is taken from StefnuAtv in Greynir.grammar. That includes "aftur:ao", which is not applicable here. -QIoTSpeakerLocationPrepositionFirstPart -> - StaðarAtv - | "fram:ao" - | "inn:ao" - | "niður:ao" - | "upp:ao" - | "út:ao" - -QIoTSpeakerLocationPrepositionSecondPart -> - "á" | "í" - -QIoTSpeakerGroupName/fall -> - no/fall - -# QCHANGELightName/fall -> -# no/fall - - -# QCHANGESceneName -> -# no -# | lo - -# QCHANGEAnnadAndlag -> -# QCHANGENewSetting/nf -# | QCHANGESpyrjaHuldu/nf - -# QCHANGEAdHverju -> -# "að" QCHANGENewSetting/þgf - -# QCHANGEAHvad -> -# "á" QCHANGENewSetting/þf - -# QCHANGEIHvad -> -# "í" QCHANGENewSetting/þf - -# QCHANGEAHverju -> -# "á" QCHANGELight/þgf -# | "á" QCHANGENewSetting/þgf - -# QCHANGESomethingOrSomehow -> -# QCHANGEAnnadAndlag -# | QCHANGEAdHverju - -# QCHANGESomehow -> -# QCHANGEAnnadAndlag -# | QCHANGEThannigAd - -# QCHANGELight/fall -> -# QCHANGELightName/fall -# | QCHANGELightWord/fall - -# # Should 'birta' be included -# QCHANGELightWord/fall -> -# 'ljós'/fall -# | 'lýsing'/fall -# | 'birta'/fall -# | 'Birta'/fall - -# QCHANGEColorWord/fall -> -# 'litur'/fall -# | 'litblær'/fall -# | 'blær'/fall - -# QCHANGEBrightnessWords/fall -> -# 'bjartur'/fall -# | QCHANGEBrightnessWord/fall - -# QCHANGEBrightnessWord/fall -> -# 'birta'/fall -# | 'Birta'/fall -# | 'birtustig'/fall - -# QCHANGESceneWord/fall -> -# 'sena'/fall -# | 'stemning'/fall -# | 'stemming'/fall -# | 'stemmning'/fall - -# # Need to ask Hulda how this works. -# QCHANGESpyrjaHuldu/fall -> -# # QCHANGEHuldaColor/fall -# QCHANGEHuldaBrightness/fall -# # | QCHANGEHuldaScene/fall - -# # Do I need a "new light state" non-terminal? -# QCHANGENewSetting/fall -> -# QCHANGENewColor/fall -# | QCHANGENewBrightness/fall -# | QCHANGENewScene/fall - -# # Missing "meira dimmt" -# QCHANGEHuldaBrightness/fall -> -# QCHANGEMoreBrighterOrHigher/fall QCHANGEBrightnessWords/fall? -# | QCHANGELessDarkerOrLower/fall QCHANGEBrightnessWords/fall? - -# #Unsure about whether to include /fall after QCHANGEColorName -# QCHANGENewColor/fall -> -# QCHANGEColorWord/fall QCHANGEColorName -# | QCHANGEColorName QCHANGEColorWord/fall? - -# QCHANGENewBrightness/fall -> -# 'sá'/fall? QCHANGEBrightestOrDarkest/fall -# | QCHANGEBrightestOrDarkest/fall QCHANGEBrightnessOrSettingWord/fall - -# QCHANGENewScene/fall -> -# QCHANGESceneWord/fall QCHANGESceneName -# | QCHANGESceneName QCHANGESceneWord/fall? - -# QCHANGEMoreBrighterOrHigher/fall -> -# 'mikill:lo'_mst/fall -# | 'bjartur:lo'_mst/fall -# | 'ljós:lo'_mst/fall -# | 'hár:lo'_mst/fall - -# QCHANGELessDarkerOrLower/fall -> -# 'lítill:lo'_mst/fall -# | 'dökkur:lo'_mst/fall -# | 'dimmur:lo'_mst/fall -# | 'lágur:lo'_mst/fall - -# QCHANGEBrightestOrDarkest/fall -> -# QCHANGEBrightest/fall -# | QCHANGEDarkest/fall - -# QCHANGEBrightest/fall -> -# 'bjartur:lo'_evb -# | 'bjartur:lo'_esb -# | 'ljós:lo'_evb -# | 'ljós:lo'_esb - -# QCHANGEDarkest/fall -> -# 'dimmur:lo'_evb -# | 'dimmur:lo'_esb -# | 'dökkur:lo'_evb -# | 'dökkur:lo'_esb - -# QCHANGEBrightnessOrSettingWord/fall -> -# QCHANGEBrightnessWord/fall -# | QCHANGESettingWord/fall - -# QCHANGESettingWord/fall -> -# 'stilling'/fall - -""" +GRAMMAR = read_grammar_file("iot_speakers") def QIoTSpeakerIncreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: From 6bba2434d05cab87f8e7939807f01708e235ec8f Mon Sep 17 00:00:00 2001 From: Logi E Date: Fri, 1 Jul 2022 15:11:31 +0000 Subject: [PATCH 148/371] oops, removed _DIALOGUE_INFO --- queries/fruitseller_module.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/queries/fruitseller_module.py b/queries/fruitseller_module.py index d407e6f9..7fe6ce76 100644 --- a/queries/fruitseller_module.py +++ b/queries/fruitseller_module.py @@ -352,7 +352,6 @@ def QNumOfFruit(node: Node, params: QueryStateDict, result: Result): result.queryfruits.append([1, result.fruit]) else: result.queryfruits.append([result.fruitnumber, result.fruit]) - _DIALOGUE_INFO["strings"] = result.queryfruits def QNum(node: Node, params: QueryStateDict, result: Result): fruitnumber = int(parse_num(node, result._nominative)) @@ -360,7 +359,6 @@ def QNum(node: Node, params: QueryStateDict, result: Result): result.fruitnumber = fruitnumber else: result.fruitnumber = 1 - _DIALOGUE_INFO["numbers"] = result.fruitnumber def QFruit(node: Node, params: QueryStateDict, result: Result): fruit = result._root @@ -490,7 +488,6 @@ def sentence(state: QueryStateDict, result: Result) -> None: # Successfully matched a query type try: - print("INFO:", _DIALOGUE_INFO) dsm.setup_dialogue(_ANSWERING_FUNCTIONS) if result.qtype == "QFruitInfo": # Example info handling functionality From 6c87f72836ade2db429755f98aee5a2e83fa1c35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Fri, 1 Jul 2022 16:38:38 +0000 Subject: [PATCH 149/371] added weekday pronounciation --- queries/theater/theater.toml | 6 +- queries/theater_module.py | 156 +++++++++++++++++++++++------------ 2 files changed, 106 insertions(+), 56 deletions(-) diff --git a/queries/theater/theater.toml b/queries/theater/theater.toml index 28554ba0..04479a1d 100644 --- a/queries/theater/theater.toml +++ b/queries/theater/theater.toml @@ -27,9 +27,9 @@ type = "ListResource" requires = ["ShowDate", "ShowTime"] prompts.initial = "Hvenær viltu fara á sýninguna {show}?\n{dates}" prompts.options = "{options}" -prompts.confirm = "Þú valdir dagsetninguna {date}, viltu halda áfram og velja fjölda sæta?" +prompts.confirm = "Þú valdir {date}, viltu halda áfram og velja fjölda sæta?" prompts.many_matching_times = "Margar dagsetningar pössuðu við gefna tímasetningu, vinsamlegast reyndu aftur." -prompts.multiple_times_for_date = "Fyrir dagsetninguna {date} eru nokkrar tímasetningar, hverja af þeim viltu bóka?\nValmöguleikarnir eru:\n{times}" +prompts.multiple_times_for_date = "Fyrir dagsetninguna sem þú valdir eru nokkrar tímasetningar, hverja af þeim viltu bóka?\nValmöguleikarnir eru:\n{times}" prompts.no_date_matched = "Engin sýning er í boði fyrir gefna dagsetningu, vinsamlegast reyndu aftur." prompts.no_time_matched = "Engin sýning er í boði fyrir gefna tímasetningu, vinsamlegast reyndu aftur." prompts.no_date_available = "{show} hefur engar dagsetningar í boði. Vinsamlegast veldu aðra sýningu." @@ -67,7 +67,7 @@ prompts.seats_unavailable = "Valin sæti eru ekki laus, vinsamlegast reyndu aftu name = "Final" type = "FinalResource" requires = ["ShowSeatNumber"] -prompts.final = "Þú bókaðir sæti {seats} í röð {row} fyrir sýninguna {show} þann {date_time}." +prompts.final = "Þú bókaðir sæti {seats} í röð {row} fyrir sýninguna {show} {date_time}." prompts.cancelled = "Móttekið, hætti við leikhús pöntunina." # TODO: Add a resource for the payment method diff --git a/queries/theater_module.py b/queries/theater_module.py index 2353f130..053769e1 100644 --- a/queries/theater_module.py +++ b/queries/theater_module.py @@ -28,11 +28,11 @@ import random import datetime - +from settings import changedlocale from query import Query, QueryStateDict from tree import Result, Node, TerminalNode from queries import AnswerTuple, gen_answer, natlang_seq, parse_num, query_json_api -from queries.num import number_to_text +from queries.num import number_to_text, numbers_to_ordinal from queries.dialogue import ( AnsweringFunctionMap, DateResource, @@ -240,10 +240,12 @@ def help_text(lemma: str) -> str: """ + class ShowType(TypedDict): title: str date: List[datetime.datetime] - location: List[Tuple[int,int]] + location: List[Tuple[int, int]] + _SHOWS: List[ShowType] = [ { @@ -286,9 +288,11 @@ def _generate_show_answer( if result.get("no_show_matched"): return gen_answer(resource.prompts["no_show_matched"]) if result.get("no_show_matched_data_exists"): - return gen_answer(resource.prompts["no_show_matched_data_exists"].format( - show=resource.data[0] - )) + return gen_answer( + resource.prompts["no_show_matched_data_exists"].format( + show=resource.data[0] + ) + ) if resource.is_unfulfilled: return gen_answer(resource.prompts["initial"]) if resource.is_fulfilled: @@ -310,7 +314,8 @@ def _generate_date_answer( for show in _SHOWS: if show["title"] == title: for date in show["date"]: - dates.append(date.strftime(" %d/%m/%Y klukkan %H:%M\n")) + with changedlocale(category="LC_TIME"): + dates.append(date.strftime(" %A %d. %B klukkan %H:%M\n")) date_number: int = 3 if len(dates) >= 3 else len(dates) options_string: str = ( "Eftirfarandi dagsetning er í boði:\n" @@ -319,9 +324,14 @@ def _generate_date_answer( if date_number == 2 else "Næstu þrjár dagsetningar eru:\n" ) - options_string += "".join(dates) + options_string += natlang_seq(dates) if len(dates) > 0: - return gen_answer(resource.prompts["options"].format(options=options_string)) + ans = gen_answer( + resource.prompts["options"] + .format(options=options_string) + .replace("dagur", "dagurinn") + ) + return (ans[0], ans[1], numbers_to_ordinal(ans[2])) else: return gen_answer(resource.prompts["no_date_available"].format(show=title)) if result.get("no_date_matched"): @@ -342,16 +352,20 @@ def _generate_date_answer( assert isinstance(date, datetime.datetime) if date.date() == show_date: show_times.append(date.strftime(" %H:%M\n")) - return gen_answer(resource.prompts["multiple_times_for_date"].format( - date=show_date, times="".join(show_times) - )) + ans = gen_answer( + resource.prompts["multiple_times_for_date"] + .format(times=natlang_seq(show_times)) + .replace("dagur", "dagurinn") + ) + return (ans[0], ans[1], numbers_to_ordinal(ans[2])) if resource.is_unfulfilled: title: str = dsm.get_resource("Show").data[0] dates: list[str] = [] for show in _SHOWS: if show["title"] == title: for date in show["date"]: - dates.append(date.strftime(" %d/%m/%Y klukkan %H:%M\n")) + with changedlocale(category="LC_TIME"): + dates.append(date.strftime(" %A %d. %B klukkan %H:%M\n")) date_number: int = 3 if len(dates) >= 3 else len(dates) start_string: str = ( "Eftirfarandi dagsetning er í boði:\n" @@ -361,21 +375,31 @@ def _generate_date_answer( else "Næstu þrjár dagsetningar eru:\n" ) if len(dates) > 0: - return gen_answer(resource.prompts["initial"].format( - show=title, - dates=start_string + "".join(dates), - )) + ans = gen_answer( + resource.prompts["initial"] + .format( + show=title, + dates=start_string + "".join(dates), + ) + .replace("dagur", "dagurinn") + ) + return (ans[0], ans[1], numbers_to_ordinal(ans[2])) else: return gen_answer(resource.prompts["no_date_available"].format(show=title)) if resource.is_fulfilled: - date_resource = dsm.get_resource("ShowDate") - time_resource = dsm.get_resource("ShowTime") - return gen_answer(resource.prompts["confirm"].format( - date=datetime.datetime.combine( - date_resource.data, - time_resource.data, - ).strftime("%Y/%m/%d %H:%M") - )) + date = dsm.get_resource("ShowDate").data + time = dsm.get_resource("ShowTime").data + with changedlocale(category="LC_TIME"): + date_time: str = datetime.datetime.combine( + date, + time, + ).strftime("%A %d. %B klukkan %H:%M") + ans = gen_answer( + resource.prompts["confirm"] + .format(date=date_time) + .replace("dagur", "daginn") + ) + return ans def _generate_seat_count_answer( @@ -387,9 +411,11 @@ def _generate_seat_count_answer( if resource.is_unfulfilled: return gen_answer(resource.prompts["initial"]) if resource.is_fulfilled: - return gen_answer(resource.prompts["confirm"].format( - seats=number_to_text(cast(int, resource.data)) - )) + return gen_answer( + resource.prompts["confirm"].format( + seats=number_to_text(cast(int, resource.data)) + ) + ) def _generate_row_answer( @@ -418,17 +444,23 @@ def _generate_row_answer( if (not resource.is_confirmed and result.get("options_info")) or result.get( "row_options" ): - return gen_answer(resource.prompts["options"].format( - rows=natlang_seq(available_rows), seats=number_to_text(seats) - )) + return gen_answer( + resource.prompts["options"].format( + rows=natlang_seq(available_rows), seats=number_to_text(seats) + ) + ) if result.get("no_row_matched"): - return gen_answer(resource.prompts["no_row_matched"].format(seats=number_to_text(seats))) + return gen_answer( + resource.prompts["no_row_matched"].format(seats=number_to_text(seats)) + ) if resource.is_unfulfilled: if len(available_rows) == 0: return gen_answer(resource.prompts["not_enough_seats"].format(seats=seats)) - return gen_answer(resource.prompts["initial"].format( - seats=number_to_text(seats), seat_rows=natlang_seq(available_rows) - )) + return gen_answer( + resource.prompts["initial"].format( + seats=number_to_text(seats), seat_rows=natlang_seq(available_rows) + ) + ) if resource.is_fulfilled: row = dsm.get_resource("ShowSeatRow").data[0] return gen_answer(resource.prompts["confirm"].format(row=number_to_text(row))) @@ -451,25 +483,31 @@ def _generate_seat_number_answer( if (not resource.is_confirmed and result.get("options_info")) or result.get( "seat_options" ): - return gen_answer(resource.prompts["options"].format( - row=number_to_text(chosen_row), options=natlang_seq(available_seats) - )) + return gen_answer( + resource.prompts["options"].format( + row=number_to_text(chosen_row), options=natlang_seq(available_seats) + ) + ) if result.get("wrong_number_seats_selected"): print("wrong_number_seats_selected prompt") chosen_seats = len( range(result.get("numbers")[0], result.get("numbers")[1] + 1) ) - return gen_answer(resource.prompts["wrong_number_seats_selected"].format( - chosen_seats=number_to_text(chosen_seats), seats=number_to_text(seats) - )) + return gen_answer( + resource.prompts["wrong_number_seats_selected"].format( + chosen_seats=number_to_text(chosen_seats), seats=number_to_text(seats) + ) + ) if result.get("seats_unavailable"): print("seats_unavailable prompt") return gen_answer(resource.prompts["seats_unavailable"]) if resource.is_unfulfilled: print("initial prompt") - return gen_answer(resource.prompts["initial"].format( - seats=natlang_seq(available_seats), row=number_to_text(chosen_row) - )) + return gen_answer( + resource.prompts["initial"].format( + seats=natlang_seq(available_seats), row=number_to_text(chosen_row) + ) + ) if resource.is_fulfilled: print("confirm prompt") chosen_seats_string: str = "" @@ -504,14 +542,20 @@ def _generate_final_answer( else: seat_string = number_to_text(seats[0]) row = dsm.get_resource("ShowSeatRow").data[0] - ans = resource.prompts["final"].format( - seats=seat_string, - row=number_to_text(row), - show=title, - date_time=datetime.datetime.combine( + with changedlocale(category="LC_TIME"): + date_time: str = datetime.datetime.combine( date, time, - ).strftime("%Y/%m/%d %H:%M"), + ).strftime("%A %d. %B klukkan %H:%M\n") + ans = ( + resource.prompts["final"] + .format( + seats=seat_string, + row=number_to_text(row), + show=title, + date_time=date_time, + ) + .replace("dagur", "daginn") ) return gen_answer(ans) @@ -580,7 +624,9 @@ def _date_callback( dsm.set_resource_state(datetime_resource.name, ResourceState.FULFILLED) else: result.multiple_times_for_date = True - dsm.set_resource_state(datetime_resource.name, ResourceState.PARTIALLY_FULFILLED) + dsm.set_resource_state( + datetime_resource.name, ResourceState.PARTIALLY_FULFILLED + ) def _time_callback( @@ -607,7 +653,9 @@ def _time_callback( first_matching_date = date print("Time callback, date there, setting time") resource.set_time(date.time()) - dsm.set_resource_state(resource.name, ResourceState.FULFILLED) + dsm.set_resource_state( + resource.name, ResourceState.FULFILLED + ) break if resource.is_fulfilled: dsm.set_resource_state(datetime_resource.name, ResourceState.FULFILLED) @@ -801,7 +849,9 @@ def _add_seats( else: print("Seat unavailable") resource.data = [] - dsm.set_resource_state(resource.name, ResourceState.UNFULFILLED) + dsm.set_resource_state( + resource.name, ResourceState.UNFULFILLED + ) result.seats_unavailable = True return resource.data = [] From bcf3964f3f1fe19ed4642f30193bd71f1654cab9 Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Mon, 4 Jul 2022 13:12:45 +0000 Subject: [PATCH 150/371] Sonos client updated Sonos client updated --- queries/iot_connect.py | 239 +++++++++++++++++++---------------------- queries/sonos.py | 147 ++++++++++++++++++++----- routes/api.py | 47 ++++---- 3 files changed, 258 insertions(+), 175 deletions(-) diff --git a/queries/iot_connect.py b/queries/iot_connect.py index 40083134..2d399df4 100644 --- a/queries/iot_connect.py +++ b/queries/iot_connect.py @@ -173,6 +173,7 @@ def sentence(state: QueryStateDict, result: Result) -> None: answer = "Skráðu þig inn hjá Sonos" voice_answer, response = answer, dict(answer=answer) q.set_answer(response, answer, voice_answer) + # Redirect the user to a Sonos login screen, which will then forward the neccessary credentials to the connect_sonos.api found in api.py q.set_url( f"https://api.sonos.com/login/v3/oauth?client_id={sonos_key}&response_type=code&state={client_id}&scope=playback-control-all&redirect_uri=http://{host}/connect_sonos.api" ) @@ -189,27 +190,7 @@ def sentence(state: QueryStateDict, result: Result) -> None: q.set_error("Missing sonos code") return sonos_client = SonosClient(device_data, q) - # code = device_data.get("sonos").get("credentials").get("code") - # if device_data is None or code is None: - # q.set_error("Missing sonos code") - # return - # sonos_encoded_credentials = read_api_key("SonosEncodedCredentials") - # response = create_token(code, sonos_encoded_credentials, host) - # response = sonos_client.create_token() - # access_token, refresh_token = ( - # response.get("access_token"), - # response.get("refresh_token"), - # ) - # if access_token is None or refresh_token is None: - # q.set_error("Missing sonos access token") - # return - # sonos_client.set_credentials(access_token, refresh_token) sonos_client.set_data() - # timestamp = str(datetime.now()) - # data_dict = sonos_client.create_sonos_data_dict() - # cred_dict = sonos_client.create_sonos_cred_dict() - # print("data dict for update", data_dict) - # print("cred dict for update", cred_dict) sonos_client.store_sonos_data_and_credentials() answer = "Ég bjó til tóka frá Sónos" @@ -224,143 +205,143 @@ def sentence(state: QueryStateDict, result: Result) -> None: return -def create_sonos_data_dict(access_token, q): - data_dict = {} - households = get_households(access_token).json() - data_dict.update(households) - groups_list = [] - players_list = [] - for i in range(len(households)): - groups_object = get_groups( - households["households"][i]["id"], access_token - ).json() - groups_raw = groups_object.get("groups") - players_raw = groups_object.get("players") - groups_list += create_grouplist_for_db(groups_raw) - players_list += create_playerlist_for_db(players_raw) - - data_dict["groups"] = groups_list - data_dict["players"] = players_list - return data_dict - - -def create_sonos_cred_dict(access_token, refresh_token, timestamp, q): - cred_dict = {} - cred_dict.update( - { - "access_token": access_token, - "refresh_token": refresh_token, - "timestamp": timestamp, - } - ) - return cred_dict +# def create_sonos_data_dict(access_token, q): +# data_dict = {} +# households = get_households(access_token).json() +# data_dict.update(households) +# groups_list = [] +# players_list = [] +# for i in range(len(households)): +# groups_object = get_groups( +# households["households"][i]["id"], access_token +# ).json() +# groups_raw = groups_object.get("groups") +# players_raw = groups_object.get("players") +# groups_list += create_grouplist_for_db(groups_raw) +# players_list += create_playerlist_for_db(players_raw) +# data_dict["groups"] = groups_list +# data_dict["players"] = players_list +# return data_dict -def store_sonos_data_and_credentials(data_dict, cred_dict, q): - sonos_dict = {} - sonos_dict["sonos"] = {"credentials": cred_dict, "data": data_dict} - print("final dict for db :", sonos_dict) - q.set_client_data("iot_speakers", sonos_dict, update_in_place=True) +# def create_sonos_cred_dict(access_token, refresh_token, timestamp, q): +# cred_dict = {} +# cred_dict.update( +# { +# "access_token": access_token, +# "refresh_token": refresh_token, +# "timestamp": timestamp, +# } +# ) +# return cred_dict -def create_grouplist_for_db(groups): - groups_list = [] - for i in range(len(groups)): - groups_list.append({groups[i]["name"]: groups[i]["id"]}) - return groups_list +# def store_sonos_data_and_credentials(data_dict, cred_dict, q): +# sonos_dict = {} +# sonos_dict["sonos"] = {"credentials": cred_dict, "data": data_dict} +# print("final dict for db :", sonos_dict) +# q.set_client_data("iot_speakers", sonos_dict, update_in_place=True) -def create_playerlist_for_db(players): - player_list = [] - for i in range(len(players)): - player_list.append({players[i]["name"]: players[i]["id"]}) - return player_list +# def create_grouplist_for_db(groups): +# groups_list = [] +# for i in range(len(groups)): +# groups_list.append({groups[i]["name"]: groups[i]["id"]}) +# return groups_list -# put this in a separate file -def get_households(token): - """ - Returns the list of households of the user - """ - url = f"https://api.ws.sonos.com/control/api/v1/households" - payload = {} - headers = {"Authorization": f"Bearer {token}"} +# def create_playerlist_for_db(players): +# player_list = [] +# for i in range(len(players)): +# player_list.append({players[i]["name"]: players[i]["id"]}) +# return player_list - response = requests.request("GET", url, headers=headers, data=payload) - return response +# # put this in a separate file +# def get_households(token): +# """ +# Returns the list of households of the user +# """ +# url = f"https://api.ws.sonos.com/control/api/v1/households" +# payload = {} +# headers = {"Authorization": f"Bearer {token}"} -def get_groups(household_id, token): - """ - Returns the list of groups of the user - """ - url = f"https://api.ws.sonos.com/control/api/v1/households/{household_id}/groups" +# response = requests.request("GET", url, headers=headers, data=payload) - payload = {} - headers = {"Authorization": f"Bearer {token}"} +# return response - response = requests.request("GET", url, headers=headers, data=payload) - return response +# def get_groups(household_id, token): +# """ +# Returns the list of groups of the user +# """ +# url = f"https://api.ws.sonos.com/control/api/v1/households/{household_id}/groups" +# payload = {} +# headers = {"Authorization": f"Bearer {token}"} -def create_token(code, sonos_encoded_credentials, host): - """ - Creates a token given a code - """ - url = f"https://api.sonos.com/login/v3/oauth/access?grant_type=authorization_code&code={code}&redirect_uri=http://{host}/connect_sonos.api" +# response = requests.request("GET", url, headers=headers, data=payload) - payload = {} - headers = { - "Authorization": f"Basic {sonos_encoded_credentials}", - "Cookie": "JSESSIONID=F710019AF0A3B7126A8702577C883B5F; AWSELB=69BFEFC914A689BF6DC8E4652748D7B501ED60290D5EA56F2E543ABD7CF357A5F65186AEBCFB059E28075D83A700FD504C030A53CC28683B515BE3DCA3CC587AFAF606E171; AWSELBCORS=69BFEFC914A689BF6DC8E4652748D7B501ED60290D5EA56F2E543ABD7CF357A5F65186AEBCFB059E28075D83A700FD504C030A53CC28683B515BE3DCA3CC587AFAF606E171", - } +# return response - response = requests.request("POST", url, headers=headers, data=payload) - return response.json() +# def create_token(code, sonos_encoded_credentials, host): +# """ +# Creates a token given a code +# """ +# url = f"https://api.sonos.com/login/v3/oauth/access?grant_type=authorization_code&code={code}&redirect_uri=http://{host}/connect_sonos.api" +# payload = {} +# headers = { +# "Authorization": f"Basic {sonos_encoded_credentials}", +# "Cookie": "JSESSIONID=F710019AF0A3B7126A8702577C883B5F; AWSELB=69BFEFC914A689BF6DC8E4652748D7B501ED60290D5EA56F2E543ABD7CF357A5F65186AEBCFB059E28075D83A700FD504C030A53CC28683B515BE3DCA3CC587AFAF606E171; AWSELBCORS=69BFEFC914A689BF6DC8E4652748D7B501ED60290D5EA56F2E543ABD7CF357A5F65186AEBCFB059E28075D83A700FD504C030A53CC28683B515BE3DCA3CC587AFAF606E171", +# } -def toggle_play_pause(group_id, token): - """ - Toggles the play/pause of a group - """ - url = ( - f"https://api.ws.sonos.com/control/api/v1/groups/{group_id}/playback/playPause" - ) +# response = requests.request("POST", url, headers=headers, data=payload) - payload = {} - headers = {"Content-Type": "application/json", "Authorization": f"Bearer {token}"} +# return response.json() - response = requests.request("POST", url, headers=headers, data=payload) - return response +# def toggle_play_pause(group_id, token): +# """ +# Toggles the play/pause of a group +# """ +# url = ( +# f"https://api.ws.sonos.com/control/api/v1/groups/{group_id}/playback/playPause" +# ) +# payload = {} +# headers = {"Content-Type": "application/json", "Authorization": f"Bearer {token}"} -def audio_clip(audioclip_url, player_id, token): - """ - Plays an audioclip from link to .mp3 file - """ - import requests - import json +# response = requests.request("POST", url, headers=headers, data=payload) - url = f"https://api.ws.sonos.com/control/api/v1/players/{player_id}/audioClip" +# return response - payload = json.dumps( - { - "name": "Embla", - "appId": "com.acme.app", - "streamUrl": f"{audioclip_url}", - "volume": 50, - "priority": "HIGH", - "clipType": "CUSTOM", - } - ) - headers = { - "Content-Type": "application/json", - "Authorization": f"Bearer {token}", - } - response = requests.request("POST", url, headers=headers, data=payload) +# def audio_clip(audioclip_url, player_id, token): +# """ +# Plays an audioclip from link to .mp3 file +# """ +# import requests +# import json + +# url = f"https://api.ws.sonos.com/control/api/v1/players/{player_id}/audioClip" + +# payload = json.dumps( +# { +# "name": "Embla", +# "appId": "com.acme.app", +# "streamUrl": f"{audioclip_url}", +# "volume": 50, +# "priority": "HIGH", +# "clipType": "CUSTOM", +# } +# ) +# headers = { +# "Content-Type": "application/json", +# "Authorization": f"Bearer {token}", +# } + +# response = requests.request("POST", url, headers=headers, data=payload) diff --git a/queries/sonos.py b/queries/sonos.py index df0387bc..2aecf23e 100644 --- a/queries/sonos.py +++ b/queries/sonos.py @@ -22,9 +22,81 @@ """ +_GROUPS = { + "fjölskylduherbergi": "Family Room", + "fjölskyldu herbergi": "Family Room", + "stofa": "Living Room", + "eldhús": "Kitchen", + "bað": "Bathroom", + "klósett": "Bathroom", + "svefnherbergi": "Bedroom", + "svefn herbergi": "Bedroom", + "herbergi": "Bedroom", + "skrifstofa": "Office", + "bílskúr": "Garage", + "skúr": "Garage", + "garður": "Garden", + "gangur": "hallway", + "borðstofa": "Dining Room", + "gestasvefnherbergi": "Guest Room", + "gesta svefnherbergi": "Guest Room", + "gestaherbergi": "Guest Room", + "gesta herbergi": "Guest Room", + "leikherbergi": "Playroom", + "leik herbergi": "Playroom", + "sundlaug": "Pool", + "sjónvarpsherbergi": "TV Room", + "sjóvarps herbergi": "TV Room", + "ferðahátalari": "Portable", + "ferða hátalari": "Portable", + "verönd": "Patio", + "pallur": "Patio", + "sjónvarpsherbergi": "Media Room", + "sjónvarps herbergi": "Media Room", + "hjónaherbergi": "Main Bedroom", + "hjóna herbergi": "Main Bedroom", + "anddyri": "Foyer", + "forstofa": "Foyer", + "inngangur": "Foyer", + "húsbóndaherbergi": "Den", + "húsbónda herbergi": "Den", + "hosiló": "Den", + "bókasafn": "Library", + "bókaherbergi": "Library", + "bóka herbergi": "Library", +} + +_RADIO_STEAMS = { + "rás 1": " http://netradio.ruv.is/ras1.mp3", + "rás 2": "http://netradio.ruv.is/ras2.mp3", + "rondo": "http://netradio.ruv.is/rondo.mp3", + "rondó": "http://netradio.ruv.is/rondo.mp3", + "bylgjan": "https://live.visir.is/hls-radio/bylgjan/playlist.m3u8", + "fm 957": "https://live.visir.is/hls-radio/fm957/playlist.m3u8", + "fm957": "https://live.visir.is/hls-radio/fm957/playlist.m3u8", + "fm-957": "https://live.visir.is/hls-radio/fm957/playlist.m3u8", + "Útvarp Saga": "https://stream.utvarpsaga.is/Hljodver", + "k-100": "https://k100streymi.mbl.is/beint/k100/tracks-v1a1/rewind-3600.m3u8", + "k 100": "https://k100streymi.mbl.is/beint/k100/tracks-v1a1/rewind-3600.m3u8", + "k hundrað": "https://k100streymi.mbl.is/beint/k100/tracks-v1a1/rewind-3600.m3u8", + "Gullbylgjan": "https://live.visir.is/hls-radio/gullbylgjan/playlist.m3u8", + "x977": "https://live.visir.is/hls-radio/x977/playlist.m3u8", + "x 977": "https://live.visir.is/hls-radio/x977/playlist.m3u8", + "x-977": "https://live.visir.is/hls-radio/x977/playlist.m3u8", + "x-ið": "https://live.visir.is/hls-radio/x977/playlist.m3u8", + "x-ið 977": "https://live.visir.is/hls-radio/x977/playlist.m3u8", + "léttbylgjan": "https://live.visir.is/hls-radio/lettbylgjan/playlist.m3u8", + "retro": "https://k100straumar.mbl.is/retromobile", + "kiss fm": "http://stream3.radio.is:443/kissfm", + "flassbakk": "http://stream.radio.is:443/flashback", + "flassbakk fm": "http://stream.radio.is:443/flashback", +} + + import requests from datetime import datetime, timedelta import flask +import random from util import read_api_key from queries import query_json_api, post_to_json_api @@ -34,10 +106,11 @@ class SonosClient: - def __init__(self, device_data, client_id, query=None): + def __init__(self, device_data, client_id, query=None, radio_name=None): self._client_id = client_id self._device_data = device_data self._query = query + self._radio_name = radio_name self._sonos_encoded_credentials = read_api_key("SonosEncodedCredentials") self._code = self._device_data["sonos"]["credentials"]["code"] print("code :", self._code) @@ -51,31 +124,31 @@ def __init__(self, device_data, client_id, query=None): "refresh_token" ] except KeyError: - print("Missing credentials for sonos") self._create_token() - print("continue") self._check_token_expiration() self._households = self._get_households() - print("households :", self._households) self._household_id = self._households[0]["id"] - # groups_and_players = self._get_groups_and_players() self._groups = self._get_groups() self._players = self._get_players() - print("exited get groups") - print(self._groups) self.store_sonos_data_and_credentials() def _check_token_expiration(self): + """ + Checks if access token is expired, and calls a function to refresh it if necessary. + """ try: timestamp = self._device_data["sonos"]["credentials"]["timestamp"] except KeyError: - print("Missing timestamp for sonos access token") + print("No timestamp found for Sonos token.") return timestamp = datetime.strptime(timestamp, "%Y-%m-%d %H:%M:%S.%f") if (datetime.now() - timestamp) > timedelta(hours=24): self._update_sonos_token() def _update_sonos_token(self): + """ + Updates the access token + """ print("update sonos token") self._sonos_encoded_credentials = read_api_key("SonosEncodedCredentials") self._access_token = self._refresh_expired_token() @@ -93,7 +166,7 @@ def _update_sonos_token(self): def _refresh_expired_token(self): """ - Refreshes token + Helper function for updating the access token. """ print("_refresh_expired_token") url = f"https://api.sonos.com/login/v3/oauth/access?grant_type=refresh_token&refresh_token={self._refresh_token}" @@ -123,7 +196,7 @@ def _create_token(self): def toggle_play_pause(self): """ - Toggles the play/pause of a group + Toggles play/pause of a group """ print("toggle playpause") group_id = self._get_group_id() @@ -144,10 +217,8 @@ def _get_households(self): """ print("get households") try: - print("households try") return self._device_data["sonos"]["data"]["households"] except KeyError: - print("key error get households") url = f"https://api.ws.sonos.com/control/api/v1/households" headers = {"Authorization": f"Bearer {self._access_token}"} @@ -174,18 +245,14 @@ def _get_groups(self): """ print("get groups") try: - print("try get groups") - print("device data get groups :", self._device_data) return self._device_data["sonos"]["data"]["groups"] except KeyError: - print("keyerror") for i in range(len(self._households)): url = f"https://api.ws.sonos.com/control/api/v1/households/{self._household_id}/groups" headers = {"Authorization": f"Bearer {self._access_token}"} response = query_json_api(url, headers=headers) cleaned_groups_list = self._create_grouplist_for_db(response["groups"]) - print("cleaned_groups_list :", cleaned_groups_list) return cleaned_groups_list def get_groups_and_players(self): @@ -210,7 +277,6 @@ def _get_group_id(self): group_id = self._groups[0]["id"] return group_id except KeyError: - print("_get_group_id keyerror") url = f"https://api.ws.sonos.com/control/api/v1/households/{self._household_id}/groups" headers = { "Content-Type": "application/json", @@ -227,8 +293,6 @@ def _get_players(self): """ print("get players") try: - print("try get players") - print("device data get players :", self._device_data) return self._device_data["sonos"]["data"]["players"] except KeyError: print("keyerror") @@ -240,7 +304,6 @@ def _get_players(self): cleaned_players_list = self._create_playerlist_for_db( response["players"] ) - print("cleaned_groups_list :", cleaned_players_list) return cleaned_players_list def _get_player_id(self): @@ -252,7 +315,6 @@ def _get_player_id(self): player_id = self._players[0]["id"] return player_id except KeyError: - print("_get_player_id keyerror") url = f"https://api.ws.sonos.com/control/api/v1/households/{self._household_id}/groups" headers = { "Content-Type": "application/json", @@ -294,12 +356,9 @@ def _create_sonos_cred_dict(self): def store_sonos_data_and_credentials(self): print("store_sonos_data_and_credentials") data_dict = self._create_sonos_data_dict() - print("data dict :", data_dict) cred_dict = self._create_sonos_cred_dict() - print("cred_dict :", cred_dict) sonos_dict = {} sonos_dict["sonos"] = {"credentials": cred_dict, "data": data_dict} - print("final dict for db :", sonos_dict) self._store_data(sonos_dict) def _store_data(self, data): @@ -364,6 +423,46 @@ def audio_clip(self, audioclip_url): response = post_to_json_api(url, payload, headers) return response + def create_or_join_session(self): + url = f"https://api.ws.sonos.com/control/api/v1/groups/{self._group_id}/playbackSession/joinOrCreate" + + payload = json.dumps({"appId": "com.mideind.embla", "appContext": "embla123"}) + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {self._access_token}", + } + + response = post_to_json_api(url, payload, headers) + print(response) + session_id = response.sessionId + return session_id + + def play_radio_stream(self, query=None): + session_id = self._create_or_join_session() + if query == None: + radio_name, radio_url = random.choice(list(_RADIO_STEAMS.items())) + else: + radio_name, radio_url = _RADIO_STEAMS.get(query) + + url = f"https://api.ws.sonos.com/control/api/v1//playbackSessions/{session_id}/playbackSession/loadStreamUrl?" + + payload = json.dumps( + { + "streamUrl": f"{radio_url}", + "playOnCompletion": True, + "stationMetadata": {"name": f"{radio_name}"}, + "itemId": "StreamItemId", + } + ) + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {self._access_token}", + } + + response = post_to_json_api(url, payload, headers) + + print(response.text) + # # TODO: Check whether this should return the ids themselves instead of the json response # def _get_households(token): diff --git a/routes/api.py b/routes/api.py index 0cdc6f8f..3c81dd08 100755 --- a/routes/api.py +++ b/routes/api.py @@ -722,40 +722,43 @@ def upload_speech_audio(version: int = 1) -> Response: @routes.route("/connect_sonos.api", methods=["GET"]) @routes.route("/connect_sonos.api/v", methods=["GET", "POST"]) def sonos_code(version: int = 1) -> Response: + """ + API endpoint to connect to Sonos speakers + """ print("sonos code") args = request.args client_id = args.get("state") code = args.get("code") - code = {"sonos": {"credentials": {"code": code}}} + code = { + "sonos": {"credentials": {"code": code}} + } # create a dictonary with the code if client_id and code: - print("if client_id and code") success = QueryObject.store_query_data( client_id, "iot_speakers", code, update_in_place=True ) - print("store querydata done") - print(success) if success: - print("success") device_data = code + # Create an instance of the SonosClient class. This will automatically create the rest of the credentials needed. sonos_client = SonosClient(device_data, client_id) - sonos_voice_clip = f"Hæ!, ég er búin að tengja þennan Sónos hátalara." - sonos_client.audio_clip(text_to_audio_url(sonos_voice_clip)) + sonos_voice_clip = ( + f"Hæ! Embla hérna. Ég er búin að tengja þennan Sónos hátalara." + ) + sonos_client.audio_clip(text_to_audio_url(sonos_voice_clip)) # Send the above message to the Sonos speaker return better_jsonify(valid=True, msg="Registered sonos code") - print("else") return better_jsonify(valid=False, errmsg="Error registering sonos code.") -def sonos_code2(version: int = 1) -> Response: - print("sonos code") - args = request.args - client_id = args.get("state") - code = args.get("code") - code = {"sonos": {"credentials": {"code": code}}} - if client_id and code: - success = QueryObject.store_query_data( - client_id, "iot_speakers", code, update_in_place=True - ) - if success: - return better_jsonify(valid=True, msg="Registered sonos code") - - return better_jsonify(valid=False, errmsg="Error registering sonos code.") +# def sonos_code2(version: int = 1) -> Response: +# print("sonos code") +# args = request.args +# client_id = args.get("state") +# code = args.get("code") +# code = {"sonos": {"credentials": {"code": code}}} +# if client_id and code: +# success = QueryObject.store_query_data( +# client_id, "iot_speakers", code, update_in_place=True +# ) +# if success: +# return better_jsonify(valid=True, msg="Registered sonos code") + +# return better_jsonify(valid=False, errmsg="Error registering sonos code.") From efa7a87328b3ecac3c7f5a0460d0678b7b1c0867 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Mon, 4 Jul 2022 13:25:14 +0000 Subject: [PATCH 151/371] Made show dates pageable --- queries/dialogue.py | 11 +++ queries/theater/theater.toml | 7 +- queries/theater_module.py | 136 +++++++++++++++++++++++++---------- 3 files changed, 114 insertions(+), 40 deletions(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index 1085d2c5..de4362c1 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -36,6 +36,7 @@ _DIALOGUE_NAME_KEY = "dialogue_name" _DIALOGUE_RESOURCES_KEY = "resources" _DIALOGUE_LAST_INTERACTED_WITH_KEY = "last_interacted_with" +_DIALOGUE_EXTRAS_KEY = "extras" _EMPTY_DIALOGUE_DATA = "{}" _FINAL_RESOURCE_NAME = "Final" @@ -295,6 +296,7 @@ class DialogueStructureType(TypedDict): dialogue_name: str resources: Dict[str, Resource] last_interacted_with: Optional[datetime.datetime] + extras: Dict[str, Any] def _load_dialogue_structure(filename: str) -> DialogueStructureType: @@ -336,6 +338,7 @@ def __init__( self._answering_functions: AnsweringFunctionMap = {} self._answer_tuple: Optional[AnswerTuple] = None self._error: bool = False + self._extras: Dict[str, Any] = {} # TODO: Delegate answering from a resource to another resource or to another dialogue # TODO: í ávaxtasamtali "ég vil panta flug" "viltu að ég geymi ávaxtapöntunina eða eyði henni?" ... @@ -360,6 +363,8 @@ def setup_dialogue(self, answering_functions: AnsweringFunctionMap) -> None: # Change from int to enum type resource.state = ResourceState(resource.state) self._resources[rname] = resource + if _DIALOGUE_EXTRAS_KEY in self._saved_state: + self._extras = self._saved_state[_DIALOGUE_EXTRAS_KEY] self._answering_functions = answering_functions if self._result.qtype == self._start_qtype: @@ -376,6 +381,7 @@ def _start_dialogue(self): _DIALOGUE_NAME_KEY: self._dialogue_name, _DIALOGUE_RESOURCES_KEY: {}, _DIALOGUE_LAST_INTERACTED_WITH_KEY: datetime.datetime.now(), + _DIALOGUE_EXTRAS_KEY: self._extras, } ) @@ -387,6 +393,7 @@ def update_dialogue_state(self): _DIALOGUE_NAME_KEY: self._dialogue_name, _DIALOGUE_RESOURCES_KEY: self._resources, _DIALOGUE_LAST_INTERACTED_WITH_KEY: datetime.datetime.now(), + _DIALOGUE_EXTRAS_KEY: self._extras, } ) @@ -396,6 +403,9 @@ def get_resource(self, name: str) -> Resource: def get_result(self) -> Result: return self._result + def get_extras(self) -> Dict[str, Any]: + return self._extras + def get_answer(self) -> Optional[AnswerTuple]: # Executing callbacks cbs: Optional[List[_CallbackTupleType]] = self._result.get("callbacks") @@ -461,6 +471,7 @@ def _get_saved_dialogue_state(self) -> DialogueStructureType: _DIALOGUE_NAME_KEY: "", _DIALOGUE_RESOURCES_KEY: {}, _DIALOGUE_LAST_INTERACTED_WITH_KEY: datetime.datetime.now(), + _DIALOGUE_EXTRAS_KEY: {}, } if cd: ds_str = cd.get(self._dialogue_name) diff --git a/queries/theater/theater.toml b/queries/theater/theater.toml index 04479a1d..f9e0017d 100644 --- a/queries/theater/theater.toml +++ b/queries/theater/theater.toml @@ -25,8 +25,9 @@ requires = ["Show"] name = "ShowDateTime" type = "ListResource" requires = ["ShowDate", "ShowTime"] -prompts.initial = "Hvenær viltu fara á sýninguna {show}?\n{dates}" -prompts.options = "{options}" +cascade_state = true +prompts.initial = "Hvenær viltu fara á sýninguna {show}? Í boði eru {date_number} dagsetningar.\n{dates}" +prompts.options = "Í boði eru {date_number} dagsetningar. {options}" prompts.confirm = "Þú valdir {date}, viltu halda áfram og velja fjölda sæta?" prompts.many_matching_times = "Margar dagsetningar pössuðu við gefna tímasetningu, vinsamlegast reyndu aftur." prompts.multiple_times_for_date = "Fyrir dagsetninguna sem þú valdir eru nokkrar tímasetningar, hverja af þeim viltu bóka?\nValmöguleikarnir eru:\n{times}" @@ -51,7 +52,7 @@ prompts.initial = "Að minnsta kosti {seats} sæti eru í boði í röðum {seat prompts.options = "Raðir {rows} eru með {seats} laus sæti." prompts.confirm = "Þú valdir röð {row}, viltu halda áfram?" prompts.no_row_matched = "Því miður er þessi röð ekki með {seats} laus sæti. Vinsamlegast reyndu aftur." -prompts.not_enough_seats = "Því miður er engin röð með {seats} sæti, vinsamlegast veldu færri sæti eða prófaðu aðra dagsetningu." +prompts.not_enough_seats = "Því miður er engin röð með {seats} laus sæti á þessari sýningu, vinsamlegast prófaðu aðra dagsetningu." [[resources]] name = "ShowSeatNumber" diff --git a/queries/theater_module.py b/queries/theater_module.py index 053769e1..33e72aab 100644 --- a/queries/theater_module.py +++ b/queries/theater_module.py @@ -21,7 +21,7 @@ This query module handles dialogue related to theater tickets. """ -from typing import Any, Callable, List, Optional, Tuple, cast +from typing import Any, Callable, Dict, List, Optional, Tuple, cast from typing_extensions import TypedDict import json import logging @@ -102,6 +102,8 @@ def help_text(lemma: str) -> str: QTheaterDialogue → QTheaterShowQuery | QTheaterShowDateQuery + | QTheaterMoreDates + | QTheaterPreviousDates | QTheaterShowSeatCountQuery | QTheaterShowLocationQuery | QTheaterOptions @@ -169,6 +171,24 @@ def help_text(lemma: str) -> str: QTheaterTime → "klukkan"? tími +QTheaterMoreDates → + "hverjar"? "eru"? "næstu" "þrjár"? QSyningarTimar + | "hverjir" "eru" "næstu" "þrír"? QSyningarTimar + | "get" "ég" "fengið" "að" "sjá" "næstu" "þrjá"? QSyningarTimar + | QTheaterEgVil? "sjá"? "fleiri" QSyningarTimar + | QTheaterEgVil? "sjá"? "næstu" "þrjá"? QSyningarTimar + +QTheaterPreviousDates → + QTheaterEgVil "sjá" "fyrri" QSyningarTimar + | "hvaða" QSyningarTimar "eru" "á" "undan" "þessum"? + | "get" "ég" "fengið" "að" "sjá" QSyningarTimar "á" "undan" "þessum"? + | QTheaterEgVil? "sjá"? QSyningarTimar "á" "undan" "þessum"? + +QSyningarTimar → + 'sýningartíma' + | "dagsetningar" + | "sýningartímana" + QTheaterShowSeatCountQuery → QTheaterEgVil? "fá"? QNum "sæti"? @@ -254,6 +274,10 @@ class ShowType(TypedDict): datetime.datetime(2022, 8, 27, 13, 0), datetime.datetime(2022, 8, 28, 13, 0), datetime.datetime(2022, 8, 28, 17, 0), + datetime.datetime(2022, 9, 3, 13, 0), + datetime.datetime(2022, 9, 3, 17, 0), + datetime.datetime(2022, 9, 4, 13, 0), + datetime.datetime(2022, 9, 10, 13, 0), ], "location": [ (1, 1), # (row, seat) @@ -307,28 +331,45 @@ def _generate_date_answer( result = dsm.get_result() title = dsm.get_resource("Show").data[0] + dates: list[str] = [] + index: int = 0 + extras: Dict[str, Any] = dsm.get_extras() + if "page_index" in extras: + index = extras["page_index"] + for show in _SHOWS: + if show["title"] == title: + for date in show["date"]: + with changedlocale(category="LC_TIME"): + dates.append(date.strftime(" %A %d. %B klukkan %H:%M\n")) + date_number: int = 3 if len(dates) >= 3 else len(dates) + start_string: str = ( + "Eftirfarandi dagsetning er í boði:\n" + if date_number == 1 + else "Næstu tvær dagsetningar eru:\n" + if date_number == 2 + else "Næstu þrjár dagsetningar eru:\n" + ) + if index == 0: + start_string = start_string.replace("Næstu", "Fyrstu") + if len(dates) < 3: + index = 0 + extras["page_index"] = 0 + if index > len(dates) - 3: + start_string = "Síðustu þrjár dagsetningarnar eru:\n" + index = max(0, len(dates) - 3) + extras["page_index"] = index + if (not resource.is_confirmed and result.get("options_info")) or result.get( "date_options" ): - dates: list[str] = [] - for show in _SHOWS: - if show["title"] == title: - for date in show["date"]: - with changedlocale(category="LC_TIME"): - dates.append(date.strftime(" %A %d. %B klukkan %H:%M\n")) - date_number: int = 3 if len(dates) >= 3 else len(dates) - options_string: str = ( - "Eftirfarandi dagsetning er í boði:\n" - if date_number == 1 - else "Næstu tvær dagsetningar eru:\n" - if date_number == 2 - else "Næstu þrjár dagsetningar eru:\n" - ) - options_string += natlang_seq(dates) + options_string = start_string + natlang_seq(dates[0:date_number]) if len(dates) > 0: ans = gen_answer( resource.prompts["options"] - .format(options=options_string) + .format( + options=options_string, + date_number=number_to_text(len(dates), gender="kvk"), + ) .replace("dagur", "dagurinn") ) return (ans[0], ans[1], numbers_to_ordinal(ans[2])) @@ -359,27 +400,13 @@ def _generate_date_answer( ) return (ans[0], ans[1], numbers_to_ordinal(ans[2])) if resource.is_unfulfilled: - title: str = dsm.get_resource("Show").data[0] - dates: list[str] = [] - for show in _SHOWS: - if show["title"] == title: - for date in show["date"]: - with changedlocale(category="LC_TIME"): - dates.append(date.strftime(" %A %d. %B klukkan %H:%M\n")) - date_number: int = 3 if len(dates) >= 3 else len(dates) - start_string: str = ( - "Eftirfarandi dagsetning er í boði:\n" - if date_number == 1 - else "Næstu tvær dagsetningar eru:\n" - if date_number == 2 - else "Næstu þrjár dagsetningar eru:\n" - ) if len(dates) > 0: ans = gen_answer( resource.prompts["initial"] .format( show=title, - dates=start_string + "".join(dates), + dates=start_string + "".join(dates[index : index + 3]), + date_number=number_to_text(len(dates), gender="kvk"), ) .replace("dagur", "dagurinn") ) @@ -455,6 +482,8 @@ def _generate_row_answer( ) if resource.is_unfulfilled: if len(available_rows) == 0: + dsm.set_resource_state("ShowDateTime", ResourceState.UNFULFILLED) + dsm.get_extras()["page_index"] = 0 return gen_answer(resource.prompts["not_enough_seats"].format(seats=seats)) return gen_answer( resource.prompts["initial"].format( @@ -754,6 +783,42 @@ def QTheaterTime(node: Node, params: QueryStateDict, result: Result) -> None: result.callbacks.append((filter_func, _time_callback)) +def QTheaterMoreDates(node: Node, params: QueryStateDict, result: Result) -> None: + def _next_dates( + resource: NumberResource, dsm: DialogueStateManager, result: Result + ) -> None: + print("In next dates") + extras: Dict[str, Any] = dsm.get_extras() + if "page_index" in extras: + extras["page_index"] += 3 + else: + extras["page_index"] = 3 + print("Next dates page index:", extras["page_index"]) + + if "callbacks" not in result: + result["callbacks"] = [] + print("In QTheaterMoreDates") + filter_func: Callable[[Resource], bool] = lambda r: r.name == "ShowDate" + result.callbacks.append((filter_func, _next_dates)) + + +def QTheaterPreviousDates(node: Node, params: QueryStateDict, result: Result) -> None: + def _prev_dates( + resource: NumberResource, dsm: DialogueStateManager, result: Result + ) -> None: + extras: Dict[str, Any] = dsm.get_extras() + if "page_index" in extras: + extras["page_index"] = max(extras["page_index"] - 3, 0) + else: + extras["page_index"] = 0 + print("Prev dates page index:", extras["page_index"]) + + if "callbacks" not in result: + result["callbacks"] = [] + filter_func: Callable[[Resource], bool] = lambda r: r.name == "ShowDate" + result.callbacks.append((filter_func, _prev_dates)) + + def QTheaterShowSeatCountQuery( node: Node, params: QueryStateDict, result: Result ) -> None: @@ -780,7 +845,7 @@ def _add_row( if dsm.get_resource("ShowSeatCount").is_confirmed: title: str = dsm.get_resource("Show").data[0] seats: int = dsm.get_resource("ShowSeatCount").data - available_rows: list[str] = [] + available_rows: list[int] = [] for show in _SHOWS: if show["title"] == title: checking_row: int = 1 @@ -797,12 +862,9 @@ def _add_row( print("Add row: ", result.number) print("Available rows: ", available_rows) if result.number in available_rows: - print("Appending row") resource.data = [result.number] dsm.set_resource_state(resource.name, ResourceState.FULFILLED) else: - print("Emptying row data") - resource.data = [] dsm.set_resource_state(resource.name, ResourceState.UNFULFILLED) result.no_row_matched = True From f4c759dd1cd8fc81ef80d96a6d09fed1dab4f594 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Mon, 4 Jul 2022 15:48:35 +0000 Subject: [PATCH 152/371] Added voice/text strings to all answers in theater --- queries/theater/theater.toml | 5 +- queries/theater_module.py | 271 +++++++++++++++++++++++++++-------- 2 files changed, 212 insertions(+), 64 deletions(-) diff --git a/queries/theater/theater.toml b/queries/theater/theater.toml index f9e0017d..10d4a5f4 100644 --- a/queries/theater/theater.toml +++ b/queries/theater/theater.toml @@ -30,7 +30,7 @@ prompts.initial = "Hvenær viltu fara á sýninguna {show}? Í boði eru {date_n prompts.options = "Í boði eru {date_number} dagsetningar. {options}" prompts.confirm = "Þú valdir {date}, viltu halda áfram og velja fjölda sæta?" prompts.many_matching_times = "Margar dagsetningar pössuðu við gefna tímasetningu, vinsamlegast reyndu aftur." -prompts.multiple_times_for_date = "Fyrir dagsetninguna sem þú valdir eru nokkrar tímasetningar, hverja af þeim viltu bóka?\nValmöguleikarnir eru:\n{times}" +prompts.multiple_times_for_date = "Fyrir dagsetninguna sem þú valdir eru nokkrar tímasetningar, hverja af þeim viltu bóka?\nValmöguleikarnir eru:{times}" prompts.no_date_matched = "Engin sýning er í boði fyrir gefna dagsetningu, vinsamlegast reyndu aftur." prompts.no_time_matched = "Engin sýning er í boði fyrir gefna tímasetningu, vinsamlegast reyndu aftur." prompts.no_date_available = "{show} hefur engar dagsetningar í boði. Vinsamlegast veldu aðra sýningu." @@ -40,6 +40,7 @@ prompts.no_date_chosen = "Vinsamlegast veldu dagsetningu til að fá mögulegar name = "ShowSeatCount" type = "NumberResource" requires = ["ShowDateTime"] +cascade_state = true prompts.initial = "Hversu mörg sæti viltu bóka?" prompts.confirm = "Þú valdir {seats} sæti, viltu halda áfram og velja staðsetningu sætanna?" prompts.invalid_seat_count = "Fjöldi sæta þarfa að vera hærri en einn. Vinsamlegast reyndu aftur." @@ -48,6 +49,7 @@ prompts.invalid_seat_count = "Fjöldi sæta þarfa að vera hærri en einn. Vins name = "ShowSeatRow" type = "ListResource" requires = ["ShowSeatCount"] +cascade_state = true prompts.initial = "Að minnsta kosti {seats} sæti eru í boði í röðum {seat_rows}. Í hvaða röð viltu sitja?" prompts.options = "Raðir {rows} eru með {seats} laus sæti." prompts.confirm = "Þú valdir röð {row}, viltu halda áfram?" @@ -58,6 +60,7 @@ prompts.not_enough_seats = "Því miður er engin röð með {seats} laus sæti name = "ShowSeatNumber" type = "ListResource" requires = ["ShowSeatRow"] +cascade_state = true prompts.initial = "Sæti {seats} eru í boði í röð {row}, hvaða sæti má bjóða þér?" prompts.options = "Sætin sem eru í boði í röð {row} eru {options}" prompts.confirm = "Þú valdir sæti {seats}, viltu halda áfram?" diff --git a/queries/theater_module.py b/queries/theater_module.py index 33e72aab..13b40a82 100644 --- a/queries/theater_module.py +++ b/queries/theater_module.py @@ -294,8 +294,37 @@ class ShowType(TypedDict): (6, 24), ], }, + { + "title": "Lína Langsokkur", + "date": [ + datetime.datetime(2022, 8, 27, 13, 0), + datetime.datetime(2022, 8, 28, 13, 0), + datetime.datetime(2022, 8, 28, 17, 0), + datetime.datetime(2022, 9, 3, 13, 0), + datetime.datetime(2022, 9, 3, 17, 0), + datetime.datetime(2022, 9, 4, 13, 0), + datetime.datetime(2022, 9, 10, 13, 0), + ], + "location": [ + (1, 11), # (row, seat) + (1, 12), + (1, 13), + (1, 14), + (2, 7), + (2, 18), + (2, 19), + (6, 20), + (6, 21), + (6, 22), + (6, 23), + (6, 24), + ], + }, ] +_BREAK_LENGTH = 0.3 # Seconds +_BREAK_SSML = ''.format(_BREAK_LENGTH) + def _generate_show_answer( resource: ListResource, dsm: DialogueStateManager @@ -307,8 +336,14 @@ def _generate_show_answer( ): shows: list[str] = [] for show in _SHOWS: - shows.append(show["title"]) - return gen_answer(resource.prompts["options"].format(options=", ".join(shows))) + shows.append("\n - " + show["title"]) + print("Prompts: ", resource.prompts) + ans = resource.prompts["options"] + if len(shows) == 1: + ans = ans.replace("Sýningarnar", "Sýningin").replace("eru", "er") + text_ans = ans.format(options="".join(shows)) + voice_ans = ans.format(options=natlang_seq(shows)).replace("-", "") + return (dict(answer=text_ans), text_ans, voice_ans) if result.get("no_show_matched"): return gen_answer(resource.prompts["no_show_matched"]) if result.get("no_show_matched_data_exists"): @@ -332,6 +367,7 @@ def _generate_date_answer( title = dsm.get_resource("Show").data[0] dates: list[str] = [] + text_dates: list[str] = [] index: int = 0 extras: Dict[str, Any] = dsm.get_extras() if "page_index" in extras: @@ -340,21 +376,22 @@ def _generate_date_answer( if show["title"] == title: for date in show["date"]: with changedlocale(category="LC_TIME"): - dates.append(date.strftime(" %A %d. %B klukkan %H:%M\n")) + text_dates.append(date.strftime("\n - %a %d. %b kl. %H:%M")) + dates.append(date.strftime("\n%A %d. %B klukkan %H:%M")) date_number: int = 3 if len(dates) >= 3 else len(dates) start_string: str = ( - "Eftirfarandi dagsetning er í boði:\n" + "Eftirfarandi dagsetning er í boði:" if date_number == 1 - else "Næstu tvær dagsetningar eru:\n" + else "Næstu tvær dagsetningarnar eru:" if date_number == 2 - else "Næstu þrjár dagsetningar eru:\n" + else "Næstu þrjár dagsetningarnar eru:" ) if index == 0: start_string = start_string.replace("Næstu", "Fyrstu") if len(dates) < 3: index = 0 extras["page_index"] = 0 - if index > len(dates) - 3: + if index > len(dates) - 3 and len(dates) > 3: start_string = "Síðustu þrjár dagsetningarnar eru:\n" index = max(0, len(dates) - 3) extras["page_index"] = index @@ -362,17 +399,26 @@ def _generate_date_answer( if (not resource.is_confirmed and result.get("options_info")) or result.get( "date_options" ): - options_string = start_string + natlang_seq(dates[0:date_number]) + options_string = ( + start_string + natlang_seq(dates[index : index + date_number]) + ).replace("dagur", "dagurinn") + text_options_string = start_string + "".join( + text_dates[index : index + date_number] + ) if len(dates) > 0: - ans = gen_answer( - resource.prompts["options"] - .format( - options=options_string, - date_number=number_to_text(len(dates), gender="kvk"), - ) - .replace("dagur", "dagurinn") + ans = resource.prompts["options"] + if date_number == 1: + ans = ans.replace("eru", "er").replace("dagsetningar", "dagsetning") + voice_ans = ans.format( + options=options_string, + date_number=number_to_text(len(dates), gender="kvk"), + ).replace("\n", _BREAK_SSML) + text_ans = ans.format( + options=text_options_string, + date_number=number_to_text(len(dates), gender="kvk"), ) - return (ans[0], ans[1], numbers_to_ordinal(ans[2])) + + return (dict(answer=text_ans), text_ans, numbers_to_ordinal(voice_ans)) else: return gen_answer(resource.prompts["no_date_available"].format(show=title)) if result.get("no_date_matched"): @@ -392,25 +438,41 @@ def _generate_date_answer( for date in show["date"]: assert isinstance(date, datetime.datetime) if date.date() == show_date: - show_times.append(date.strftime(" %H:%M\n")) + show_times.append(date.strftime("\n - %H:%M")) + ans = resource.prompts["multiple_times_for_date"] + voice_times = " klukkan " + natlang_seq(show_times) + voice_ans = ans.format( + times=voice_times.replace("\n -", "").replace("\n", _BREAK_SSML) + ) + text_ans = ans.format(times="".join((show_times))) ans = gen_answer( resource.prompts["multiple_times_for_date"] .format(times=natlang_seq(show_times)) .replace("dagur", "dagurinn") ) - return (ans[0], ans[1], numbers_to_ordinal(ans[2])) + return (dict(answer=text_ans), text_ans, numbers_to_ordinal(voice_ans)) if resource.is_unfulfilled: if len(dates) > 0: - ans = gen_answer( - resource.prompts["initial"] - .format( - show=title, - dates=start_string + "".join(dates[index : index + 3]), - date_number=number_to_text(len(dates), gender="kvk"), - ) - .replace("dagur", "dagurinn") + ans = resource.prompts["initial"] + if date_number == 1: + ans = ans.replace("eru", "er").replace("dagsetningar", "dagsetning") + voice_date_string = ( + start_string + natlang_seq(dates[index : index + date_number]) + ).replace("dagur", "dagurinn") + text_date_string = start_string + "".join( + text_dates[index : index + date_number] + ) + voice_ans = ans.format( + show=title, + dates=voice_date_string, + date_number=number_to_text(len(dates), gender="kvk"), + ).replace("\n", _BREAK_SSML) + text_ans = ans.format( + show=title, + dates=text_date_string, + date_number=len(dates), ) - return (ans[0], ans[1], numbers_to_ordinal(ans[2])) + return (dict(answer=text_ans), text_ans, numbers_to_ordinal(voice_ans)) else: return gen_answer(resource.prompts["no_date_available"].format(show=title)) if resource.is_fulfilled: @@ -430,7 +492,7 @@ def _generate_date_answer( def _generate_seat_count_answer( - resource: ListResource, dsm: DialogueStateManager + resource: NumberResource, dsm: DialogueStateManager ) -> Optional[AnswerTuple]: result = dsm.get_result() if result.get("invalid_seat_count"): @@ -438,11 +500,21 @@ def _generate_seat_count_answer( if resource.is_unfulfilled: return gen_answer(resource.prompts["initial"]) if resource.is_fulfilled: - return gen_answer( - resource.prompts["confirm"].format( - seats=number_to_text(cast(int, resource.data)) - ) - ) + ans = resource.prompts["confirm"] + nr_seats: int = resource.data + print("Seats: ", resource.data) + if nr_seats == 1: + print("if ans: ", ans) + ans = ans.replace("eru", "er") + print("after if ans: ", ans) + text_ans = ans.format(seats=resource.data) + voice_ans = ans.format(seats=number_to_text(resource.data)) + # return gen_answer( + # resource.prompts["confirm"].format( + # seats=number_to_text(cast(int, resource.data)) + # ) + # ) + return (dict(answer=text_ans), text_ans, voice_ans) def _generate_row_answer( @@ -453,6 +525,7 @@ def _generate_row_answer( title: str = dsm.get_resource("Show").data[0] seats: int = dsm.get_resource("ShowSeatCount").data available_rows: list[str] = [] + text_available_rows: list[str] = [] for show in _SHOWS: if show["title"] == title: checking_row: int = 1 @@ -463,6 +536,7 @@ def _generate_row_answer( seats_in_row += 1 if seats_in_row >= seats: available_rows.append(number_to_text(row)) + text_available_rows.append(str(row)) seats_in_row = 0 row_added = row else: @@ -471,28 +545,49 @@ def _generate_row_answer( if (not resource.is_confirmed and result.get("options_info")) or result.get( "row_options" ): - return gen_answer( - resource.prompts["options"].format( - rows=natlang_seq(available_rows), seats=number_to_text(seats) - ) + ans = resource.prompts["options"] + if len(available_rows) == 1: + ans = ans.replace("eru", "er").replace("Raðir", "Röð") + if seats == 1: + ans = ans.replace("laus", "laust") + text_ans = ans.format(rows=natlang_seq(text_available_rows), seats=seats) + voice_ans = ans.format( + rows=natlang_seq(available_rows), seats=number_to_text(seats) ) + return (dict(answer=text_ans), text_ans, voice_ans) if result.get("no_row_matched"): - return gen_answer( - resource.prompts["no_row_matched"].format(seats=number_to_text(seats)) - ) + ans = resource.prompts["no_row_matched"] + if seats == 1: + ans = ans.replace("laus", "laust") + text_ans = ans.format(seats=seats) + voice_ans = ans.format(seats=number_to_text(seats)) + return (dict(answer=text_ans), text_ans, voice_ans) if resource.is_unfulfilled: if len(available_rows) == 0: dsm.set_resource_state("ShowDateTime", ResourceState.UNFULFILLED) dsm.get_extras()["page_index"] = 0 - return gen_answer(resource.prompts["not_enough_seats"].format(seats=seats)) - return gen_answer( - resource.prompts["initial"].format( - seats=number_to_text(seats), seat_rows=natlang_seq(available_rows) - ) + ans = resource.prompts["not_enough_seats"] + if seats == 1: + ans = ans.replace("laus", "laust") + text_ans = ans.format(seats=seats) + voice_ans = ans.format(seats=number_to_text(seats)) + return (dict(answer=text_ans), text_ans, voice_ans) + ans = resource.prompts["initial"] + if len(available_rows) == 1: + ans = ans.replace("röðum", "röð") + if seats == 1: + ans = ans.replace("eru", "er") + text_ans = ans.format(seats=seats, seat_rows=natlang_seq(text_available_rows)) + voice_ans = ans.format( + seats=number_to_text(seats), seat_rows=natlang_seq(available_rows) ) + return (dict(answer=text_ans), text_ans, voice_ans) if resource.is_fulfilled: row = dsm.get_resource("ShowSeatRow").data[0] - return gen_answer(resource.prompts["confirm"].format(row=number_to_text(row))) + ans = resource.prompts["confirm"] + text_ans = ans.format(row=row) + voice_ans = ans.format(row=number_to_text(row)) + return (dict(answer=text_ans), text_ans, voice_ans) def _generate_seat_number_answer( @@ -504,34 +599,48 @@ def _generate_seat_number_answer( seats: int = dsm.get_resource("ShowSeatCount").data chosen_row: int = dsm.get_resource("ShowSeatRow").data[0] available_seats: list[str] = [] + text_available_seats: list[str] = [] for show in _SHOWS: if show["title"] == title: for (row, seat) in show["location"]: if chosen_row == row: + text_available_seats.append(str(seat)) available_seats.append(number_to_text(seat)) if (not resource.is_confirmed and result.get("options_info")) or result.get( "seat_options" ): - return gen_answer( - resource.prompts["options"].format( - row=number_to_text(chosen_row), options=natlang_seq(available_seats) - ) + ans = resource.prompts["options"] + if len(available_seats) == 1: + ans = ans.replace("Sætin", "Sætið").replace("eru", "er") + text_ans = ans.format(row=chosen_row, options=natlang_seq(text_available_seats)) + voice_ans = ans.format( + row=number_to_text(chosen_row), options=natlang_seq(available_seats) ) + return (dict(answer=text_ans), text_ans, voice_ans) if result.get("wrong_number_seats_selected"): print("wrong_number_seats_selected prompt") chosen_seats = len( range(result.get("numbers")[0], result.get("numbers")[1] + 1) ) - return gen_answer( - resource.prompts["wrong_number_seats_selected"].format( - chosen_seats=number_to_text(chosen_seats), seats=number_to_text(seats) - ) + ans = resource.prompts["wrong_number_seats_selected"] + text_ans = ans.format(chosen_seats=chosen_seats, seats=seats) + voice_ans = ans.format( + chosen_seats=number_to_text(chosen_seats), seats=number_to_text(seats) ) + return (dict(answer=text_ans), text_ans, voice_ans) if result.get("seats_unavailable"): print("seats_unavailable prompt") return gen_answer(resource.prompts["seats_unavailable"]) if resource.is_unfulfilled: print("initial prompt") + ans = resource.prompts["initial"] + if len(available_seats) == 1: + ans = ans.replace("eru", "er") + text_ans = ans.format(seats=natlang_seq(text_available_seats), row=chosen_row) + voice_ans = ans.format( + seats=natlang_seq(available_seats), row=number_to_text(chosen_row) + ) + return (dict(answer=text_ans), text_ans, voice_ans) return gen_answer( resource.prompts["initial"].format( seats=natlang_seq(available_seats), row=number_to_text(chosen_row) @@ -539,15 +648,25 @@ def _generate_seat_number_answer( ) if resource.is_fulfilled: print("confirm prompt") - chosen_seats_string: str = "" + chosen_seats_voice_string: str = "" + chosen_seats_text_string: str = "" + if seats > 1: - chosen_seats_string = "{first_seat} til {last_seat}".format( + chosen_seats_voice_string = "{first_seat} til {last_seat}".format( first_seat=number_to_text(result.get("numbers")[0]), last_seat=number_to_text(result.get("numbers")[1]), ) + chosen_seats_text_string = "{first_seat} til {last_seat}".format( + first_seat=result.get("numbers")[0], + last_seat=result.get("numbers")[1], + ) else: - chosen_seats_string = number_to_text(result.get("numbers")[0]) - return gen_answer(resource.prompts["confirm"].format(seats=chosen_seats_string)) + chosen_seats_voice_string = number_to_text(result.get("numbers")[0]) + chosen_seats_text_string = result.get("numbers")[0] + ans = resource.prompts["confirm"] + text_ans = ans.format(seats=chosen_seats_text_string) + voice_ans = ans.format(seats=chosen_seats_voice_string) + return (dict(answer=text_ans), text_ans, voice_ans) def _generate_final_answer( @@ -563,23 +682,49 @@ def _generate_final_answer( number_of_seats = cast(NumberResource, dsm.get_resource("ShowSeatCount")).data seats = dsm.get_resource("ShowSeatNumber").data seat_string: str = "" + seat_voice_string: str = "" + seats_text_string: str = "" if number_of_seats > 1: - seat_string = "{first_seat} til {last_seat}".format( + seat_voice_string = "{first_seat} til {last_seat}".format( first_seat=number_to_text(seats[0]), last_seat=number_to_text(seats[-1]), ) + seats_text_string = "{first_seat} til {last_seat}".format( + first_seat=seats[0], + last_seat=seats[-1], + ) else: - seat_string = number_to_text(seats[0]) + seat_voice_string = number_to_text(seats[0]) + seats_text_string = seats[0] row = dsm.get_resource("ShowSeatRow").data[0] with changedlocale(category="LC_TIME"): - date_time: str = datetime.datetime.combine( + date_time_voice: str = ( + datetime.datetime.combine( + date, + time, + ) + .strftime("%A %d. %B klukkan %H:%M\n") + .replace("dagur", "daginn") + ) + date_time_text: str = datetime.datetime.combine( date, time, - ).strftime("%A %d. %B klukkan %H:%M\n") + ).strftime("%a %d. %b kl. %H:%M") + ans = resource.prompts["final"] + text_ans = ans.format( + seats=seats_text_string, row=row, show=title, date_time=date_time_text + ) + voice_ans = ans.format( + seats=seat_voice_string, + row=number_to_text(row), + show=title, + date_time=date_time_voice, + ) + return (dict(answer=text_ans), text_ans, voice_ans) ans = ( resource.prompts["final"] .format( - seats=seat_string, + seats=seat_voice_string, row=number_to_text(row), show=title, date_time=date_time, From 7815fee22f42f8e645d8f5ea2000621ddf5528d2 Mon Sep 17 00:00:00 2001 From: Logi E Date: Mon, 4 Jul 2022 15:55:13 +0000 Subject: [PATCH 153/371] merged some stuff --- queries/dialogue.py | 20 +++++++++----------- queries/fruitseller_module.py | 7 +++---- queries/theater_module.py | 11 ----------- 3 files changed, 12 insertions(+), 26 deletions(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index de4362c1..80a57f8a 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -2,24 +2,21 @@ Any, Callable, Dict, - Generic, Mapping, - NewType, Set, Tuple, List, Optional, Type, TypeVar, - Union, cast, ) -from typing_extensions import TypedDict, reveal_type +from typing_extensions import TypedDict import os.path import json import datetime -from enum import IntEnum, auto +from enum import IntEnum, IntFlag, auto from dataclasses import dataclass, field try: @@ -40,20 +37,20 @@ _EMPTY_DIALOGUE_DATA = "{}" _FINAL_RESOURCE_NAME = "Final" -# Generic resource type (covariant, see https://peps.python.org/pep-0484/#covariance-and-contravariance) -ResourceType_co = TypeVar("ResourceType_co", covariant=True) +# Generic resource type +ResourceType_co = TypeVar("ResourceType_co", bound="Resource") # Types for use in callbacks _CallbackType = Callable[[ResourceType_co, "DialogueStateManager", Result], None] _FilterFuncType = Type[Callable[[ResourceType_co], bool]] _CallbackTupleType = Tuple[_FilterFuncType["Resource"], _CallbackType["Resource"]] - # Types for use in generating prompts/answers AnsweringFunctionType = Callable[ - ["Resource", "DialogueStateManager"], Optional[AnswerTuple] + [ResourceType_co, "DialogueStateManager"], Optional[AnswerTuple] ] -AnsweringFunctionMap = Mapping[str, AnsweringFunctionType] +# TODO: Fix 'Any' in type hint (Callable args are contravariant) +AnsweringFunctionMap = Mapping[str, AnsweringFunctionType[Any]] class ResourceState(IntEnum): @@ -68,7 +65,7 @@ class ResourceState(IntEnum): PAUSED = auto() SKIPPED = auto() CANCELLED = auto() - + # ALL = UNFULFILLED | PARTIALLY_FULFILLED | FULFILLED | CONFIRMED | PAUSED | SKIPPED | CANCELLED ########################## # RESOURCE CLASSES # @@ -511,6 +508,7 @@ def set_resource_state(self, resource_name: str, state: ResourceState): def _find_parent_resources(self, resource_name: str) -> Optional[Set[str]]: """Find all parent resources of a resource""" + # TODO: FIX ME, UGLY all_parents: Set[str] = set() ap_len: int i = 0 diff --git a/queries/fruitseller_module.py b/queries/fruitseller_module.py index 7fe6ce76..cf3699d2 100644 --- a/queries/fruitseller_module.py +++ b/queries/fruitseller_module.py @@ -353,6 +353,7 @@ def QNumOfFruit(node: Node, params: QueryStateDict, result: Result): else: result.queryfruits.append([result.fruitnumber, result.fruit]) + def QNum(node: Node, params: QueryStateDict, result: Result): fruitnumber = int(parse_num(node, result._nominative)) if fruitnumber is not None: @@ -360,6 +361,7 @@ def QNum(node: Node, params: QueryStateDict, result: Result): else: result.fruitnumber = 1 + def QFruit(node: Node, params: QueryStateDict, result: Result): fruit = result._root if fruit is not None: @@ -467,16 +469,13 @@ def QFruitInfoQuery(node: Node, params: QueryStateDict, result: Result): result.qtype = "QFruitInfo" -def my_test(r: Resource, dsm: DialogueStateManager) -> Optional[AnswerTuple]: - return gen_answer("hello") - _ANSWERING_FUNCTIONS: AnsweringFunctionMap = { "Fruits": _generate_fruit_answer, "DateTime": _generate_datetime_answer, "Final": _generate_final_answer, - "something": my_test, } + def sentence(state: QueryStateDict, result: Result) -> None: """Called when sentence processing is complete""" q: Query = state["query"] diff --git a/queries/theater_module.py b/queries/theater_module.py index 13b40a82..02b3fe0b 100644 --- a/queries/theater_module.py +++ b/queries/theater_module.py @@ -721,17 +721,6 @@ def _generate_final_answer( date_time=date_time_voice, ) return (dict(answer=text_ans), text_ans, voice_ans) - ans = ( - resource.prompts["final"] - .format( - seats=seat_voice_string, - row=number_to_text(row), - show=title, - date_time=date_time, - ) - .replace("dagur", "daginn") - ) - return gen_answer(ans) def QTheaterDialogue(node: Node, params: QueryStateDict, result: Result) -> None: From a711c1e4e9f0335e436eca19d421adecd239c19e Mon Sep 17 00:00:00 2001 From: Logi E Date: Mon, 4 Jul 2022 16:19:49 +0000 Subject: [PATCH 154/371] added replace count for safety (if show includes the replaced word) --- queries/theater_module.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/queries/theater_module.py b/queries/theater_module.py index 02b3fe0b..72f0cd4c 100644 --- a/queries/theater_module.py +++ b/queries/theater_module.py @@ -340,7 +340,7 @@ def _generate_show_answer( print("Prompts: ", resource.prompts) ans = resource.prompts["options"] if len(shows) == 1: - ans = ans.replace("Sýningarnar", "Sýningin").replace("eru", "er") + ans = ans.replace("Sýningarnar", "Sýningin", 1).replace("eru", "er", 2) text_ans = ans.format(options="".join(shows)) voice_ans = ans.format(options=natlang_seq(shows)).replace("-", "") return (dict(answer=text_ans), text_ans, voice_ans) @@ -387,7 +387,7 @@ def _generate_date_answer( else "Næstu þrjár dagsetningarnar eru:" ) if index == 0: - start_string = start_string.replace("Næstu", "Fyrstu") + start_string = start_string.replace("Næstu", "Fyrstu", 1) if len(dates) < 3: index = 0 extras["page_index"] = 0 @@ -408,7 +408,9 @@ def _generate_date_answer( if len(dates) > 0: ans = resource.prompts["options"] if date_number == 1: - ans = ans.replace("eru", "er").replace("dagsetningar", "dagsetning") + ans = ans.replace("eru", "er", 1).replace( + "dagsetningar", "dagsetning", 1 + ) voice_ans = ans.format( options=options_string, date_number=number_to_text(len(dates), gender="kvk"), @@ -455,7 +457,9 @@ def _generate_date_answer( if len(dates) > 0: ans = resource.prompts["initial"] if date_number == 1: - ans = ans.replace("eru", "er").replace("dagsetningar", "dagsetning") + ans = ans.replace("eru", "er", 1).replace( + "dagsetningar", "dagsetning", 1 + ) voice_date_string = ( start_string + natlang_seq(dates[index : index + date_number]) ).replace("dagur", "dagurinn") @@ -611,7 +615,7 @@ def _generate_seat_number_answer( ): ans = resource.prompts["options"] if len(available_seats) == 1: - ans = ans.replace("Sætin", "Sætið").replace("eru", "er") + ans = ans.replace("Sætin", "Sætið", 1).replace("eru", "er", 2) text_ans = ans.format(row=chosen_row, options=natlang_seq(text_available_seats)) voice_ans = ans.format( row=number_to_text(chosen_row), options=natlang_seq(available_seats) From 00d08eee5deb5336c7f78f1438c550c96653b058 Mon Sep 17 00:00:00 2001 From: Logi E Date: Mon, 4 Jul 2022 16:34:09 +0000 Subject: [PATCH 155/371] moved TOML files to queries/dialogues folder --- queries/dialogue.py | 3 +- .../fruitseller.toml | 0 queries/{theater => dialogues}/theater.toml | 0 queries/fruitseller/__init__.py | 0 queries/fruitseller/fruitseller.yaml | 14 ------- queries/fruitseller/resources.py | 39 ------------------- 6 files changed, 1 insertion(+), 55 deletions(-) rename queries/{fruitseller => dialogues}/fruitseller.toml (100%) rename queries/{theater => dialogues}/theater.toml (100%) delete mode 100644 queries/fruitseller/__init__.py delete mode 100644 queries/fruitseller/fruitseller.yaml delete mode 100644 queries/fruitseller/resources.py diff --git a/queries/dialogue.py b/queries/dialogue.py index 80a57f8a..63674943 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -299,8 +299,7 @@ class DialogueStructureType(TypedDict): def _load_dialogue_structure(filename: str) -> DialogueStructureType: """Loads dialogue structure from TOML file.""" basepath, _ = os.path.split(os.path.realpath(__file__)) - # TODO: Fix this, causes issues when folders have the same name as a module - fpath = os.path.join(basepath, filename, filename + ".toml") + fpath = os.path.join(basepath, "dialogues", filename + ".toml") with open(fpath, mode="r") as file: f = file.read() obj: Dict[str, Any] = tomllib.loads(f) # type: ignore diff --git a/queries/fruitseller/fruitseller.toml b/queries/dialogues/fruitseller.toml similarity index 100% rename from queries/fruitseller/fruitseller.toml rename to queries/dialogues/fruitseller.toml diff --git a/queries/theater/theater.toml b/queries/dialogues/theater.toml similarity index 100% rename from queries/theater/theater.toml rename to queries/dialogues/theater.toml diff --git a/queries/fruitseller/__init__.py b/queries/fruitseller/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/queries/fruitseller/fruitseller.yaml b/queries/fruitseller/fruitseller.yaml deleted file mode 100644 index 44de7e6a..00000000 --- a/queries/fruitseller/fruitseller.yaml +++ /dev/null @@ -1,14 +0,0 @@ -dialogue_name: "fruitseller" -resources: - - name: "Fruits" - prompt: "Hvaða ávexti má bjóða þér?" - type: "ListResource" - repeatable: true - confirm_prompt: "Viltu staðfesta ávextina {list_items}?" - repeat_prompt: "Pöntunin samanstendur af {list_items}. Verður það eitthvað fleira?" - - name: "Date" - type: "DatetimeResource" - prompt: "Hvenær viltu fá ávextina?" - time_fulfilled_prompt: "Afhendingin verður klukkan {time}. Hvaða dagsetningu viltu fá ávextina?" - date_fulfilled_prompt: "Afhendingin verður {date}. Klukkan hvað viltu fá ávextina?" - confirm_prompt: "Afhending pöntunar er {date_time}. Viltu staðfesta afhendinguna?" diff --git a/queries/fruitseller/resources.py b/queries/fruitseller/resources.py deleted file mode 100644 index df3ceb8b..00000000 --- a/queries/fruitseller/resources.py +++ /dev/null @@ -1,39 +0,0 @@ -from queries.dialogue import ( - ListResource, -) - -class FruitResource(ListResource): - def generate_answer(self) -> str: - ans = super().generate_answer() - """ans: str = "" - if type == "QFruitStartQuery": - ans = "Hvaða ávexti má bjóða þér?" - elif type == "ListFruit": - if len(self.data) != 0: - ans = "Komið! Pöntunin samanstendur af " - ans += list_items(self.data) - ans += ". Var það eitthvað fleira?" - else: - ans = "Komið! Karfan er núna tóm. Hvaða ávexti má bjóða þér?" - - elif type == "FruitOrderNotFinished": - ans = "Hverju viltu að bæta við pöntunina?" - elif type == "FruitsFulfilled": - ans = "Frábært! Á ég að staðfesta pöntunina?" - elif type == "FruitMethodNotFound": - self.ans = "Ég get ekki tekið við þessari beiðni strax." - elif type == "OrderComplete": - ans = "Frábært, pöntunin er staðfest!" - elif type == "OrderWrong": - ans = "Leitt að heyra, viltu hætta við pöntunina eða breyta henni?" - elif type == "CancelOrder": - ans = "Móttekið. Hætti við pöntunina." - elif type == "FruitOptions": - ans = "Hægt er að panta appelsínur, banana, epli og perur." - elif type == "FruitRemoved": - ans = "Karfan hefur verið uppfærð. Var það eitthvað fleira?" - elif type == "NoFruitMatched": - ans = "Enginn ávöxtur í körfunni passaði við beiðnina á undan." - elif type == "NoFruitToRemove": - ans = "Engir ávextir eru í körfunni til að fjarlægja." """ - return ans From f96a2729b9534da35f903b310bef854beecfbc04 Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Mon, 4 Jul 2022 17:10:45 +0000 Subject: [PATCH 156/371] IoT speakers & sonos Client updated --- queries/iot_speakers.py | 399 +++++++++++++++------------------------- queries/sonos.py | 32 ++++ 2 files changed, 184 insertions(+), 247 deletions(-) diff --git a/queries/iot_speakers.py b/queries/iot_speakers.py index 2221a6d1..854861b9 100644 --- a/queries/iot_speakers.py +++ b/queries/iot_speakers.py @@ -50,7 +50,7 @@ from tree import Result, Node -_IoT_QTYPE = "IoT" +_IoT_QTYPE = "IoTSpeakers" TOPIC_LEMMAS = [ "tónlist", @@ -84,7 +84,9 @@ def help_text(lemma: str) -> str: # The context-free grammar for the queries recognized by this plug-in module # GRAMMAR = read_grammar_file("iot_hue") +# TODO: Fix music hardcoding GRAMMAR = f""" +# TODO: Fix music hardcoding /þgf = þgf /ef = ef @@ -102,7 +104,7 @@ def help_text(lemma: str) -> str: | QIoTSpeakerLetVerb QIoTSpeakerLetRest | QIoTSpeakerTurnOnVerb QIoTSpeakerTurnOnRest | QIoTSpeakerTurnOffVerb QIoTSpeakerTurnOffRest - | QIoTSpeakerPlayVerb QIoTSpeakerPlayRest + | QIoTSpeakerPlayOrPauseVerb QIoTSpeakerPlayRest | QIoTSpeakerIncreaseOrDecreaseVerb QIoTSpeakerIncreaseOrDecreaseRest QIoTSpeakerMakeVerb -> @@ -124,9 +126,17 @@ def help_text(lemma: str) -> str: QIoTSpeakerTurnOffVerb -> 'slökkva:so'_bh +QIoTSpeakerPlayOrPauseVerb -> + QIoTSpeakerPlayVerb + | QIoTSpeakerPauseVerb + QIoTSpeakerPlayVerb -> 'spila:so'_bh - | "spilaðu" + +QIoTSpeakerPauseVerb -> + 'stöðva:so'_bh + | 'stoppa:so'_bh + | 'pása:so'_bh QIoTSpeakerIncreaseOrDecreaseVerb -> QIoTSpeakerIncreaseVerb @@ -147,7 +157,7 @@ def help_text(lemma: str) -> str: # | QCHANGEHvar? QCHANGEHvernigMake QCHANGESubject/þf # | QCHANGEHvernigMake QCHANGESubject/þf QCHANGEHvar? # | QCHANGEHvernigMake QCHANGEHvar? QCHANGESubject/þf - QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? + QIoTSpeakerMusicWordÞf QIoTSpeakerHvar? # TODO: Add support for "stilltu rauðan lit á ljósið í eldhúsinu" QIoTSpeakerSetRest -> @@ -157,7 +167,7 @@ def help_text(lemma: str) -> str: # | QCHANGEHvar? QCHANGEHvernigSet QCHANGESubject/þf # | QCHANGEHvernigSet QCHANGESubject/þf QCHANGEHvar? # | QCHANGEHvernigSet QCHANGEHvar? QCHANGESubject/þf - "á" QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? + "á" QIoTSpeakerMusicWordÞf QIoTSpeakerHvar? # QIoTSpeakerChangeRest -> # QCHANGESubjectOne/þgf QCHANGEHvar? QCHANGEHvernigChange @@ -174,107 +184,66 @@ def help_text(lemma: str) -> str: # | QCHANGEHvar? QCHANGEHvernigLet QCHANGESubject/þf # | QCHANGEHvernigLet QCHANGESubject/þf QCHANGEHvar? # | QCHANGEHvernigLet QCHANGEHvar? QCHANGESubject/þf - "vera" QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? - | "á" QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? + QIoTSpeakerBeOrBecome QIoTSpeakerMusicWordNf QIoTSpeakerHvar? + | "á" QIoTSpeakerMusicWordNf QIoTSpeakerHvar? +# TODO: Find out why they conjugate this incorrectly "tónlist" is in þgf here, not þf QIoTSpeakerTurnOnRest -> - # QCHANGETurnOnLightsRest - # | QCHANGEAHverju QCHANGEHvar? + QIoTSpeakerAHverju QIoTSpeakerHvar? # | QCHANGEHvar? QCHANGEAHverju - "á" QIoTSpeakerMusicWord/þgf QIoTSpeakerHvar? - -# QCHANGETurnOnLightsRest -> -# QCHANGELightSubject/þf QCHANGEHvar? -# | QCHANGEHvar QCHANGELightSubject/þf? # Would be good to add "slökktu á rauða litnum" functionality QIoTSpeakerTurnOffRest -> # QCHANGETurnOffLightsRest - "á" QIoTSpeakerMusicWord/þgf QIoTSpeakerHvar? - -# QCHANGETurnOffLightsRest -> -# QCHANGELightSubject/þf QCHANGEHvar? -# | QCHANGEHvar QCHANGELightSubject/þf? + "á" QIoTSpeakerMusicWordÞgf QIoTSpeakerHvar? QIoTSpeakerPlayRest -> - QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? - | "tónlist" + QIoTSpeakerMusicWordÞf QIoTSpeakerHvar? # TODO: Make the subject categorization cleaner QIoTSpeakerIncreaseOrDecreaseRest -> # QCHANGELightSubject/þf QCHANGEHvar? # | QCHANGEBrightnessSubject/þf QCHANGEHvar? - QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? - | "í" QIoTSpeakerMusicWord/þgf QIoTSpeakerHvar? - -# QCHANGESubject/fall -> -# QCHANGESubjectOne/fall -# | QCHANGESubjectTwo/fall - -QIoTSpeakerMusicWord/fall -> - 'tónlist:no'/fall - -# # TODO: Decide whether LightSubject/þgf should be accepted -# QCHANGESubjectOne/fall -> -# QCHANGELightSubject/fall -# | QCHANGEColorSubject/fall -# | QCHANGEBrightnessSubject/fall -# | QCHANGESceneSubject/fall - -# QCHANGESubjectTwo/fall -> -# QCHANGEGroupNameSubject/fall # á bara að styðja "gerðu eldhúsið rautt", "gerðu eldhúsið rómó" "gerðu eldhúsið bjartara", t.d. - -QIoTSpeakerHvar -> - QIoTSpeakerLocationPreposition QIoTSpeakerGroupName/þgf - -# QCHANGEHvernigMake -> -# QCHANGEAnnadAndlag # gerðu litinn rauðan í eldhúsinu EÐA gerðu birtuna meiri í eldhúsinu -# | QCHANGEAdHverju # gerðu litinn að rauðum í eldhúsinu -# | QCHANGEThannigAd + QIoTSpeakerMusicWordÞf QIoTSpeakerHvar? + | "í" QIoTSpeakerMusicWordÞgf QIoTSpeakerHvar? -# QCHANGEHvernigSet -> -# QCHANGEAHvad -# | QCHANGEThannigAd +# Increase specificity +# QIoTSpeakerMusicWord -> +# 'tónlist' -# QCHANGEHvernigChange -> -# QCHANGEIHvad -# | QCHANGEThannigAd +QIoTSpeakerAHverju -> + "á" QIoTSpeakerMusicWordÞf +# | "á" QIoTSpeakerNewSetting/þgf -# QCHANGEHvernigLet -> -# QCHANGEBecome QCHANGESomethingOrSomehow -# | QCHANGEBe QCHANGESomehow +# QIoTSpeakerNewSetting/fall -> +# QIoTSpeakerNewRadio/fall -# QCHANGEThannigAd -> -# "þannig" "að"? pfn_nf QCHANGEBeOrBecomeSubjunctive QCHANGEAnnadAndlag +# QIoTSpeakerNewRadio/fall -> +# QIoTSpeakerRadioStationWord/fall? QIoTSpeakerRadioStationName/fall +# | QIoTSpeakerRadioStationWord/fall? QIoTSpeakerRadioStationNameIndeclinable -# I think these verbs only appear in these forms. -# In which case these terminals should be deleted and a direct reference should be made in the relevant non-terminals. -# QCHANGEBe -> -# "vera" -# QCHANGEBecome -> -# "verða" +# QIoTSpeakerRadioStationWord/fall -> +# 'útvarpsstöð:no'/fall -# QCHANGEBeOrBecomeSubjunctive -> -# "verði" -# | "sé" +QIoTSpeakerMusicWordNf -> + "tónlist" + | "tónlistin" -# QCHANGELightSubject/fall -> -# QCHANGELight/fall +QIoTSpeakerMusicWordÞf -> + "tónlist" + | "tónlistina" -# QCHANGEColorSubject/fall -> -# QCHANGEColorWord/fall QCHANGELight/ef? -# | QCHANGEColorWord/fall "á" QCHANGELight/þgf +QIoTSpeakerMusicWordÞgf -> + "tónlist" + | "tónlistinni" -# QCHANGEBrightnessSubject/fall -> -# QCHANGEBrightnessWord/fall QCHANGELight/ef? -# | QCHANGEBrightnessWord/fall "á" QCHANGELight/þgf +QIoTSpeakerMusicWordEf -> + "tónlistar" + | "tónlistarinnar" -# QCHANGESceneSubject/fall -> -# QCHANGESceneWord/fall - -# QCHANGEGroupNameSubject/fall -> -# QCHANGEGroupName/fall +QIoTSpeakerHvar -> + QIoTSpeakerLocationPreposition QIoTSpeakerGroupName/þgf QIoTSpeakerLocationPreposition -> QIoTSpeakerLocationPrepositionFirstPart? QIoTSpeakerLocationPrepositionSecondPart @@ -295,144 +264,29 @@ def help_text(lemma: str) -> str: QIoTSpeakerGroupName/fall -> no/fall -# QCHANGELightName/fall -> -# no/fall - - -# QCHANGESceneName -> -# no -# | lo - -# QCHANGEAnnadAndlag -> -# QCHANGENewSetting/nf -# | QCHANGESpyrjaHuldu/nf - -# QCHANGEAdHverju -> -# "að" QCHANGENewSetting/þgf - -# QCHANGEAHvad -> -# "á" QCHANGENewSetting/þf - -# QCHANGEIHvad -> -# "í" QCHANGENewSetting/þf - -# QCHANGEAHverju -> -# "á" QCHANGELight/þgf -# | "á" QCHANGENewSetting/þgf - -# QCHANGESomethingOrSomehow -> -# QCHANGEAnnadAndlag -# | QCHANGEAdHverju - -# QCHANGESomehow -> -# QCHANGEAnnadAndlag -# | QCHANGEThannigAd - -# QCHANGELight/fall -> -# QCHANGELightName/fall -# | QCHANGELightWord/fall - -# # Should 'birta' be included -# QCHANGELightWord/fall -> -# 'ljós'/fall -# | 'lýsing'/fall -# | 'birta'/fall -# | 'Birta'/fall - -# QCHANGEColorWord/fall -> -# 'litur'/fall -# | 'litblær'/fall -# | 'blær'/fall - -# QCHANGEBrightnessWords/fall -> -# 'bjartur'/fall -# | QCHANGEBrightnessWord/fall - -# QCHANGEBrightnessWord/fall -> -# 'birta'/fall -# | 'Birta'/fall -# | 'birtustig'/fall - -# QCHANGESceneWord/fall -> -# 'sena'/fall -# | 'stemning'/fall -# | 'stemming'/fall -# | 'stemmning'/fall - -# # Need to ask Hulda how this works. -# QCHANGESpyrjaHuldu/fall -> -# # QCHANGEHuldaColor/fall -# QCHANGEHuldaBrightness/fall -# # | QCHANGEHuldaScene/fall - -# # Do I need a "new light state" non-terminal? -# QCHANGENewSetting/fall -> -# QCHANGENewColor/fall -# | QCHANGENewBrightness/fall -# | QCHANGENewScene/fall - -# # Missing "meira dimmt" -# QCHANGEHuldaBrightness/fall -> -# QCHANGEMoreBrighterOrHigher/fall QCHANGEBrightnessWords/fall? -# | QCHANGELessDarkerOrLower/fall QCHANGEBrightnessWords/fall? - -# #Unsure about whether to include /fall after QCHANGEColorName -# QCHANGENewColor/fall -> -# QCHANGEColorWord/fall QCHANGEColorName -# | QCHANGEColorName QCHANGEColorWord/fall? - -# QCHANGENewBrightness/fall -> -# 'sá'/fall? QCHANGEBrightestOrDarkest/fall -# | QCHANGEBrightestOrDarkest/fall QCHANGEBrightnessOrSettingWord/fall - -# QCHANGENewScene/fall -> -# QCHANGESceneWord/fall QCHANGESceneName -# | QCHANGESceneName QCHANGESceneWord/fall? - -# QCHANGEMoreBrighterOrHigher/fall -> -# 'mikill:lo'_mst/fall -# | 'bjartur:lo'_mst/fall -# | 'ljós:lo'_mst/fall -# | 'hár:lo'_mst/fall - -# QCHANGELessDarkerOrLower/fall -> -# 'lítill:lo'_mst/fall -# | 'dökkur:lo'_mst/fall -# | 'dimmur:lo'_mst/fall -# | 'lágur:lo'_mst/fall - -# QCHANGEBrightestOrDarkest/fall -> -# QCHANGEBrightest/fall -# | QCHANGEDarkest/fall - -# QCHANGEBrightest/fall -> -# 'bjartur:lo'_evb -# | 'bjartur:lo'_esb -# | 'ljós:lo'_evb -# | 'ljós:lo'_esb - -# QCHANGEDarkest/fall -> -# 'dimmur:lo'_evb -# | 'dimmur:lo'_esb -# | 'dökkur:lo'_evb -# | 'dökkur:lo'_esb - -# QCHANGEBrightnessOrSettingWord/fall -> -# QCHANGEBrightnessWord/fall -# | QCHANGESettingWord/fall - -# QCHANGESettingWord/fall -> -# 'stilling'/fall +QIoTSpeakerBeOrBecome -> + QIoTSpeakerBe + | QIoTSpeakerBecome + +QIoTSpeakerBe -> + 'vera:so'_nh +QIoTSpeakerBecome -> + 'verða:so'_nh """ +def QIoTSpeaker(node: Node, params: QueryStateDict, result: Result) -> None: + print("QTYPE") + result.qtype = _IoT_QTYPE + + def QIoTSpeakerIncreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: - result.action = "increase_volume" + result["qkey"] = "increase_volume" def QIoTSpeakerDecreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: - result.action = "decrease_volume" + result["qkey"] = "decrease_volume" def QIoTSpeakerGroupName(node: Node, params: QueryStateDict, result: Result) -> None: @@ -444,47 +298,98 @@ def QIoTMusicWord(node: Node, params: QueryStateDict, result: Result) -> None: print("music") -def sentence(state: QueryStateDict, result: Result) -> None: - # try: - print("sentence") - """Called when sentence processing is complete""" - q: Query = state["query"] +def QIoTSpeakerTurnOnVerb(node: Node, params: QueryStateDict, result: Result) -> None: + result["qkey"] = "play_music" + - q.set_qtype(result.get("qtype")) +def QIoTSpeakerPauseVerb(node: Node, params: QueryStateDict, result: Result) -> None: + print("PAUSE") + result["qkey"] = "pause_music" - # TODO: Find way to only catch playing commands - if result.get("action") == None: - result.action = "play_music" - smartdevice_type = "smart_speaker" +def QIoTSpeakerPlayVerb(node: Node, params: QueryStateDict, result: Result) -> None: + result["qkey"] = "play_music" - # Fetch relevant data from the device_data table to perform an action on the lights - device_data = q.client_data("iot_speakers") - print(device_data) - # TODO: Need to add check for if there are no registered devices to an account, probably when initilazing the querydata - if device_data is not None: - sonos_client = SonosClient(device_data, q.client_id) +# def QIoTSpeakerRadioStationName( +# node: Node, params: QueryStateDict, result: Result +# ) -> None: +# result.target = "radio" +# print("radio") + + +# def toggle_play_pause(q): +# device_data = q.client_data("iot_speakers") +# print(device_data) +# if device_data is not None: +# sonos_client = SonosClient(device_data, q.client_id) +# else: +# print("No device data found for this account") +# return +# sonos_client.toggle_play_pause() +# answer = "Ég kveikti á tónlist." +# answer_list = gen_answer(answer) +# answer_list[1].replace("Sonos", "Sónos") +# q.set_answer(*answer_list) + + +# def get_device_data(q): +# device_data = q.client_data("iot_speakers") +# if device_data is not None: +# return device_data +# else: +# print("No device data found for this account") +# return + + +_HANDLER_MAP = { + "play_music": ["toggle_play_pause", "Ég kveikti á tónlist"], + "pause_music": ["toggle_play_pause", "Ég slökkti á tónlist"], + "increase_volume": ["increase_volume", "Ég hækkaði í tónlistinni"], + "decrease_volume": ["decrease_volume", "Ég lækkaði í tónlistinni"], +} + + +def sentence(state: QueryStateDict, result: Result) -> None: + # try: + print("sentence") + """Called when sentence processing is complete""" + if "qtype" in result and "qkey" in result: + try: + q: Query = state["query"] + q.set_qtype(result.qtype) + device_data = q.client_data("iot_speakers") + if device_data is not None: + sonos_client = SonosClient(device_data, q.client_id) + handler_func = _HANDLER_MAP[result.qkey][0] + handler_answer = _HANDLER_MAP[result.qkey][1] + getattr(sonos_client, handler_func)() + answer = handler_answer + answer_list = gen_answer(answer) + answer_list[1].replace("Sonos", "Sónos") + q.set_answer(*answer_list) + else: + print("No device data found for this account") + return + except Exception as e: + logging.warning("Exception answering iot_speakers query: {0}".format(e)) + q.set_error("E_EXCEPTION: {0}".format(e)) + return else: - print("No device data found for this account") + q.set_error("E_QUERY_NOT_UNDERSTOOD") return - # Perform the action on the Sonos device - if result.action == "play_music": - sonos_client.toggle_play_pause() - answer = "Ég kveikti á tónlist." - # elif result.action == "increase_volume": - # sonos_client.increase_volume() - # elif result.action == "decrease_volume": - # sonos_client.decrease_volume() - # elif result.action == "set_volume": - # sonos_client.set_volume(result.get["volume"]) - - answer_list = gen_answer(answer) - answer_list[1].replace("Sonos", "Sónos") - q.set_answer(*answer_list) - - # f"var BRIDGE_IP = '192.168.1.68';var USERNAME = 'p3obluiXT13IbHMpp4X63ZvZnpNRdbqqMt723gy2';" - # except Exception as e: - # print(e) - # print("Error in sentence") + # # TODO: Need to add check for if there are no registered devices to an account, probably when initilazing the querydata + # if device_data is not None: + # sonos_client = SonosClient(device_data, q.client_id) + # else: + # print("No device data found for this account") + # return + + # # Perform the action on the Sonos device + # if result.action == "play_music": + # sonos_client.toggle_play_pause(q) + # answer = "Ég kveikti á tónlist." + # answer_list = gen_answer(answer) + # answer_list[1].replace("Sonos", "Sónos") + # q.set_answer(*answer_list) diff --git a/queries/sonos.py b/queries/sonos.py index 2aecf23e..6d61a9c1 100644 --- a/queries/sonos.py +++ b/queries/sonos.py @@ -90,6 +90,8 @@ "kiss fm": "http://stream3.radio.is:443/kissfm", "flassbakk": "http://stream.radio.is:443/flashback", "flassbakk fm": "http://stream.radio.is:443/flashback", + "útvarp 101": "https://stream.101.live/audio/101/chunklist.m3u8", + "útvarp hundrað og einn": "https://stream.101.live/audio/101/chunklist.m3u8", } @@ -200,6 +202,7 @@ def toggle_play_pause(self): """ print("toggle playpause") group_id = self._get_group_id() + print("exited group_id") url = f"https://api.ws.sonos.com/control/api/v1/groups/{group_id}/playback/togglePlayPause" headers = { "Content-Type": "application/json", @@ -208,6 +211,7 @@ def toggle_play_pause(self): # response = requests.request("POST", url, headers=headers, data=payload) response = post_to_json_api(url, headers=headers) + print("response :", response) return response @@ -463,6 +467,34 @@ def play_radio_stream(self, query=None): print(response.text) + def increase_volume(self): + group_id = self._get_group_id() + url = f"https://api.ws.sonos.com/control/api/v1/groups/{group_id}/groupVolume/relative" + + payload = json.dumps({"volumeDelta": 10}) + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {self._access_token}", + } + + response = post_to_json_api(url, payload, headers) + + print(response.text) + + def decrease_volume(self): + group_id = self._get_group_id() + url = f"https://api.ws.sonos.com/control/api/v1/groups/{group_id}/groupVolume/relative" + + payload = json.dumps({"volumeDelta": -10}) + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {self._access_token}", + } + + response = post_to_json_api(url, payload, headers) + + print(response.text) + # # TODO: Check whether this should return the ids themselves instead of the json response # def _get_households(token): From 89d7756e6c20ae6a00f4d698009e2d4640d42d7a Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Mon, 4 Jul 2022 17:16:47 +0000 Subject: [PATCH 157/371] added radio stations to grammar --- queries/grammars/iot_speakers.grammar | 199 +++++++++++++++++++++++--- queries/iot_hue.py | 186 +++++++++++++----------- queries/iot_speakers.py | 142 ++++++++++++------ 3 files changed, 381 insertions(+), 146 deletions(-) diff --git a/queries/grammars/iot_speakers.grammar b/queries/grammars/iot_speakers.grammar index 4ba79481..284a40ab 100644 --- a/queries/grammars/iot_speakers.grammar +++ b/queries/grammars/iot_speakers.grammar @@ -1,3 +1,5 @@ +# TODO: Fix music hardcoding + /þgf = þgf /ef = ef @@ -14,7 +16,7 @@ QIoTSpeakerQuery -> | QIoTSpeakerLetVerb QIoTSpeakerLetRest | QIoTSpeakerTurnOnVerb QIoTSpeakerTurnOnRest | QIoTSpeakerTurnOffVerb QIoTSpeakerTurnOffRest - | QIoTSpeakerPlayVerb QIoTSpeakerPlayRest + | QIoTSpeakerPlayOrPauseVerb QIoTSpeakerPlayRest | QIoTSpeakerIncreaseOrDecreaseVerb QIoTSpeakerIncreaseOrDecreaseRest QIoTSpeakerMakeVerb -> @@ -36,9 +38,17 @@ QIoTSpeakerTurnOnVerb -> QIoTSpeakerTurnOffVerb -> 'slökkva:so'_bh +QIoTSpeakerPlayOrPauseVerb -> + QIoTSpeakerPlayVerb + | QIoTSpeakerPauseVerb + QIoTSpeakerPlayVerb -> 'spila:so'_bh - | "spilaðu" + +QIoTSpeakerPauseVerb -> + 'stöðva:so'_bh + | 'stoppa:so'_bh + | 'pása:so'_bh QIoTSpeakerIncreaseOrDecreaseVerb -> QIoTSpeakerIncreaseVerb @@ -59,17 +69,17 @@ QIoTSpeakerMakeRest -> # | QCHANGEHvar? QCHANGEHvernigMake QCHANGESubject/þf # | QCHANGEHvernigMake QCHANGESubject/þf QCHANGEHvar? # | QCHANGEHvernigMake QCHANGEHvar? QCHANGESubject/þf - QIoTSpeakerMusicWord QIoTSpeakerHvar? + QIoTSpeakerMusicWordÞf QIoTSpeakerHvar? # TODO: Add support for "stilltu rauðan lit á ljósið í eldhúsinu" QIoTSpeakerSetRest -> + QIoTSpeakerAHvad QIoTSpeakerHvar? # QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigSet # | QCHANGESubject/þf QCHANGEHvernigSet QCHANGEHvar? # | QCHANGEHvar? QCHANGESubject/þf QCHANGEHvernigSet # | QCHANGEHvar? QCHANGEHvernigSet QCHANGESubject/þf # | QCHANGEHvernigSet QCHANGESubject/þf QCHANGEHvar? # | QCHANGEHvernigSet QCHANGEHvar? QCHANGESubject/þf - "á" QIoTSpeakerMusicWord QIoTSpeakerHvar? # QIoTSpeakerChangeRest -> # QCHANGESubjectOne/þgf QCHANGEHvar? QCHANGEHvernigChange @@ -86,35 +96,82 @@ QIoTSpeakerLetRest -> # | QCHANGEHvar? QCHANGEHvernigLet QCHANGESubject/þf # | QCHANGEHvernigLet QCHANGESubject/þf QCHANGEHvar? # | QCHANGEHvernigLet QCHANGEHvar? QCHANGESubject/þf - QIoTSpeakerBe QIoTSpeakerMusicWord QIoTSpeakerHvar? - | "á" QIoTSpeakerMusicWord QIoTSpeakerHvar? + QIoTSpeakerBeOrBecome QIoTSpeakerMusicWordNf QIoTSpeakerHvar? + | "á" QIoTSpeakerMusicWordNf QIoTSpeakerHvar? +# TODO: Find out why they conjugate this incorrectly "tónlist" is in þgf here, not þf QIoTSpeakerTurnOnRest -> - # QCHANGETurnOnLightsRest - # | QCHANGEAHverju QCHANGEHvar? + QIoTSpeakerAHverju QIoTSpeakerHvar? # | QCHANGEHvar? QCHANGEAHverju - "á" QIoTSpeakerMusicWord QIoTSpeakerHvar? # Would be good to add "slökktu á rauða litnum" functionality QIoTSpeakerTurnOffRest -> # QCHANGETurnOffLightsRest - "á" QIoTSpeakerMusicWord QIoTSpeakerHvar? + "á" QIoTSpeakerMusicWordÞgf QIoTSpeakerHvar? QIoTSpeakerPlayRest -> - QIoTSpeakerMusicWord QIoTSpeakerHvar? - | "tónlist" + QIoTSpeakerMusicWordÞf QIoTSpeakerHvar? # TODO: Make the subject categorization cleaner QIoTSpeakerIncreaseOrDecreaseRest -> # QCHANGELightSubject/þf QCHANGEHvar? # | QCHANGEBrightnessSubject/þf QCHANGEHvar? - QIoTSpeakerMusicWord QIoTSpeakerHvar? - | "í" QIoTSpeakerMusicWord QIoTSpeakerHvar? + QIoTSpeakerMusicWordÞf QIoTSpeakerHvar? + | "í" QIoTSpeakerMusicWordÞgf QIoTSpeakerHvar? + +# Increase specificity +# QIoTSpeakerMusicWord -> +# 'tónlist' + +QIoTSpeakerAHvad -> + "á" QIoTSpeakerMusicWordÞf + | "á" QIoTSpeakerNewSetting/þf + +QIoTSpeakerAHverju -> + "á" QIoTSpeakerMusicWordÞf + | "á" QIoTSpeakerNewSetting/þgf + +QIoTSpeakerNewSetting/fall -> + QIoTSpeakerNewRadio/fall + +QIoTSpeakerNewRadio/fall -> + QIoTSpeakerRadioStationWord/fall? QIoTSpeakerRadioStationName + # | QIoTSpeakerRadioStationWord/fall? "bylgjunni" + # | QIoTSpeakerRadioStationWord/fall? QIoTSpeakerRadioStationNameIndeclinable -# There is a bug when trying to -QIoTSpeakerMusicWord -> +QIoTSpeakerRadioStationName -> + QIoTSpeakerBylgjan + | QIoTSpeakerUtvarpSaga + | QIoTSpeakerGullbylgjan + | QIoTSpeakerXId + | QIoTSpeakerLettbylgjan + | QIoTSpeakerRas1 + | QIoTSpeakerRas2 + | QIoTSpeakerRondo + | QIoTSpeakerFm957 + | QIoTSpeakerK100 + | QIoTSpeakerRetro + | QIoTSpeakerKissFm + | QIoTSpeakerFlashback + +QIoTSpeakerRadioStationWord/fall -> + 'útvarpsstöð:no'/fall + +QIoTSpeakerMusicWordNf -> + "tónlist" + | "tónlistin" + +QIoTSpeakerMusicWordÞf -> "tónlist" - # | 'tónlist:no' + | "tónlistina" + +QIoTSpeakerMusicWordÞgf -> + "tónlist" + | "tónlistinni" + +QIoTSpeakerMusicWordEf -> + "tónlistar" + | "tónlistarinnar" QIoTSpeakerHvar -> QIoTSpeakerLocationPreposition QIoTSpeakerGroupName/þgf @@ -138,5 +195,111 @@ QIoTSpeakerLocationPrepositionSecondPart -> QIoTSpeakerGroupName/fall -> no/fall +QIoTSpeakerBeOrBecome -> + QIoTSpeakerBe + | QIoTSpeakerBecome + QIoTSpeakerBe -> - 'vera:so'_nh \ No newline at end of file + 'vera:so'_nh + +QIoTSpeakerBecome -> + 'verða:so'_nh + +QIoTSpeakerBylgjan -> + "Bylgjan" + | "Bylgjuna" + | "Bylgjunni" + | "Bylgjunnar" + | "bylgjan" + | "bylgjuna" + | "bylgjunni" + | "bylgjunnar" + +QIoTSpeakerUtvarpSaga -> + "Útvarp" "Saga" + | "Útvarp" "Sögu" + | "Útvarpi" "Sögu" + | "Útvarp" "Sögu" + | "Útvarps" "Sögu" + | "útvarp" "saga" + | "útvarp" "sögu" + | "útvarpi" "sögu" + | "útvarp" "sögu" + | "útvarps" "sögu" + | "útvarpssaga" + | "útvarpssögu" + +QIoTSpeakerGullbylgjan -> + "Gullbylgjan" + | "Gullbylgjuna" + | "Gullbylgjunni" + | "Gullbylgjunnar" + | "gullbylgjan" + | "gullbylgjuna" + | "gullbylgjunni" + | "gullbylgjunnar" + | "gull" "bylgjan" + | "gull" "bylgjuna" + | "gull" "bylgjunni" + | "gull" "bylgjunnar" + +QIoTSpeakerLettbylgjan -> + "Léttbylgjan" + | "Léttbylgjuna" + | "Léttbylgjunni" + | "Léttbylgjunnar" + | "léttbylgjan" + | "léttbylgjuna" + | "léttbylgjunni" + | "léttbylgjunnar" + | "létt" "bylgjan" + | "létt" "bylgjuna" + | "létt" "bylgjunni" + | "létt" "bylgjunnar" + +QIoTSpeakerXId -> + "X-ið" "977"? + | "X-inu" "977"? + | "X-ins" "977"? + | "x-ið" "977"? + | "x-inu" "977"? + | "x-ins" "977"? + | "x" "977" + | "x977" + | "x" "níu" "sjö" "sjö" + | "x-977" + +QIoTSpeakerRas1 -> + "rás" "1" + | "rás" "eitt" + +QIoTSpeakerRas2 -> + "rás" "2" + | "rás" "tvö" + +QIoTSpeakerRondo -> + "rondo" "fm"? + | "rondó" "fm"? + +QIoTSpeakerFm957 -> + "fm" "957" + | "fm957" + +QIoTSpeakerK100 -> + "k" "100" + | "k" "hundrað" + | "k100" + | "k-hundrað" + | "k-100" + +QIoTSpeakerRetro -> + "retro" "fm"? + | "retró" "fm"? + +QIoTSpeakerKissFm -> + "kiss" "fm"? + +QIoTSpeakerFlashback -> + "flassbakk" "fm"? + | "flass" "bakk" "fm"? + diff --git a/queries/iot_hue.py b/queries/iot_hue.py index cc95dc69..5698e9f7 100755 --- a/queries/iot_hue.py +++ b/queries/iot_hue.py @@ -216,7 +216,7 @@ def QIoTDarkest(node: Node, params: QueryStateDict, result: Result) -> None: def QIoTNewScene(node: Node, params: QueryStateDict, result: Result) -> None: result.action = "set_scene" scene_name = result.get("scene_name", None) - print(scene_name) + print("scene: " + scene_name) if scene_name is not None: if "hue_obj" not in result: result["hue_obj"] = {"on": True, "scene": scene_name} @@ -233,7 +233,7 @@ def QIoTColorName(node: Node, params: QueryStateDict, result: Result) -> None: def QIoTSceneName(node: Node, params: QueryStateDict, result: Result) -> None: result["scene_name"] = result._indefinite - print(result.get("scene_name", None)) + print("scene: " + result.get("scene_name", None)) def QIoTGroupName(node: Node, params: QueryStateDict, result: Result) -> None: @@ -260,6 +260,29 @@ def QIoTLightName(node: Node, params: QueryStateDict, result: Result) -> None: ( "tónlist", "hátalari", + "bylgja", + "útvarp saga", + "gullbylgja", + "x-ið", + "léttbylgjan", + "rás 1", + "rás 2", + "rondo", + "rondó", + "fm 957", + "fm957", + "fm-957", + "k-100", + "k 100", + "k hundrað", + "x977", + "x 977", + "x-977", + "x-ið 977", + "retro", + "kiss fm", + "flassbakk", + "flassbakk fm", ) ) @@ -271,83 +294,84 @@ def sentence(state: QueryStateDict, result: Result) -> None: if not _SPEAKER_WORDS.isdisjoint(lemmas): print("matched with music word list") q.set_error("E_QUERY_NOT_UNDERSTOOD") - else: - changing_color = result.get("changing_color", False) - changing_scene = result.get("changing_scene", False) - changing_brightness = result.get("changing_brightness", False) - print("error?", sum((changing_color, changing_scene, changing_brightness)) > 1) - if ( - sum((changing_color, changing_scene, changing_brightness)) > 1 - or "qtype" not in result - ): - q.set_error("E_QUERY_NOT_UNDERSTOOD") - return - - q.set_qtype(result.qtype) - - smartdevice_type = "iot_lights" - client_id = str(q.client_id) - print("client_id:", client_id) - - # Fetch relevant data from the device_data table to perform an action on the lights - device_data = cast(Optional[DeviceData], q.client_data(smartdevice_type)) - print("location :", q.location) - print("device data :", device_data) - - selected_light: Optional[str] = None - print("selected light:", selected_light) - hue_credentials: Optional[Dict[str, str]] = None - - if device_data is not None: - dev = device_data - assert dev is not None - light = dev.get("philips_hue") - hue_credentials = light.get("credentials") - bridge_ip = hue_credentials.get("ip_address") - username = hue_credentials.get("username") - - if not device_data or not hue_credentials: - answer = "Það vantar að tengja Philips Hub-inn." - q.set_answer(*gen_answer(answer)) - return - - # Successfully matched a query type - print("bridge_ip: ", bridge_ip) - print("username: ", username) - print("selected light :", selected_light) - print("hue credentials :", hue_credentials) - - try: - # kalla í javascripts stuff - light_or_group_name = result.get("light_name", result.get("group_name", "")) - color_name = result.get("color_name", "") - print("GROUP NAME:", light_or_group_name) - print("COLOR NAME:", color_name) - print(result.hue_obj) - q.set_answer( - *gen_answer( - "ég var að kveikja ljósin! " - # + group_name - # + " " - # + color_name - # + " " - # + result.action - # + " " - # + str(result.hue_obj.get("hue", "enginn litur")) - ) - ) - js = ( - read_jsfile("IoT_Embla/fuse.js") - + f"var BRIDGE_IP = '{bridge_ip}';var USERNAME = '{username}';" - + read_jsfile("IoT_Embla/Philips_Hue/fuse_search.js") - + read_jsfile("IoT_Embla/Philips_Hue/lights.js") - + read_jsfile("IoT_Embla/Philips_Hue/set_lights.js") + return + changing_color = result.get("changing_color", False) + changing_scene = result.get("changing_scene", False) + changing_brightness = result.get("changing_brightness", False) + print("error?", sum((changing_color, changing_scene, changing_brightness)) > 1) + if ( + sum((changing_color, changing_scene, changing_brightness)) > 1 + or "qtype" not in result + ): + q.set_error("E_QUERY_NOT_UNDERSTOOD") + return + + q.set_qtype(result.qtype) + + smartdevice_type = "iot_lights" + client_id = str(q.client_id) + print("client_id:", client_id) + + # Fetch relevant data from the device_data table to perform an action on the lights + device_data = cast(Optional[DeviceData], q.client_data(smartdevice_type)) + print("location :", q.location) + print("device data :", device_data) + + selected_light: Optional[str] = None + print("selected light:", selected_light) + hue_credentials: Optional[Dict[str, str]] = None + + if device_data is not None: + dev = device_data + assert dev is not None + light = dev.get("philips_hue") + hue_credentials = light.get("credentials") + bridge_ip = hue_credentials.get("ip_address") + username = hue_credentials.get("username") + + if not device_data or not hue_credentials: + answer = "Það vantar að tengja Philips Hub-inn." + q.set_answer(*gen_answer(answer)) + return + + # Successfully matched a query type + print("bridge_ip: ", bridge_ip) + print("username: ", username) + print("selected light :", selected_light) + print("hue credentials :", hue_credentials) + + try: + # kalla í javascripts stuff + light_or_group_name = result.get("light_name", result.get("group_name", "")) + color_name = result.get("color_name", "") + print("GROUP NAME:", light_or_group_name) + print("COLOR NAME:", color_name) + print(result.hue_obj) + q.set_answer( + *gen_answer( + "ég var að kveikja ljósin! " + # + group_name + # + " " + # + color_name + # + " " + # + result.action + # + " " + # + str(result.hue_obj.get("hue", "enginn litur")) ) - js += f"setLights('{light_or_group_name}', '{json.dumps(result.hue_obj)}');" - q.set_command(js) - except Exception as e: - logging.warning("Exception while processing random query: {0}".format(e)) - q.set_error("E_EXCEPTION: {0}".format(e)) - raise - - # f"var BRIDGE_IP = '192.168.1.68';var USERNAME = 'p3obluiXT13IbHMpp4X63ZvZnpNRdbqqMt723gy2';" + ) + js = ( + read_jsfile("IoT_Embla/fuse.js") + + f"var BRIDGE_IP = '{bridge_ip}';var USERNAME = '{username}';" + + read_jsfile("IoT_Embla/Philips_Hue/fuse_search.js") + + read_jsfile("IoT_Embla/Philips_Hue/lights.js") + + read_jsfile("IoT_Embla/Philips_Hue/set_lights.js") + ) + js += f"setLights('{light_or_group_name}', '{json.dumps(result.hue_obj)}');" + q.set_command(js) + except Exception as e: + logging.warning("Exception while processing random query: {0}".format(e)) + q.set_error("E_EXCEPTION: {0}".format(e)) + raise + + +# f"var BRIDGE_IP = '192.168.1.68';var USERNAME = 'p3obluiXT13IbHMpp4X63ZvZnpNRdbqqMt723gy2';" diff --git a/queries/iot_speakers.py b/queries/iot_speakers.py index 499937fe..8fb22941 100644 --- a/queries/iot_speakers.py +++ b/queries/iot_speakers.py @@ -56,6 +56,7 @@ TOPIC_LEMMAS = [ "tónlist", + "spila", ] # def QIoTSpeakerIncreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: @@ -81,12 +82,19 @@ def help_text(lemma: str) -> str: HANDLE_TREE = True # The grammar nonterminals this module wants to handle -QUERY_NONTERMINALS = {"QIoTSpeaker"} +QUERY_NONTERMINALS = {"QIoTSpeaker", "QIoTSpeakerQuery"} # The context-free grammar for the queries recognized by this plug-in module # GRAMMAR = read_grammar_file("iot_hue") -GRAMMAR = read_grammar_file("iot_speakers") +GRAMMAR = read_grammar_file( + "iot_speakers", +) + + +def QIoTSpeaker(node: Node, params: QueryStateDict, result: Result) -> None: + print("QTYPE") + result.qtype = _IoT_QTYPE def QIoTSpeakerIncreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: @@ -106,56 +114,96 @@ def QIoTMusicWord(node: Node, params: QueryStateDict, result: Result) -> None: print("music") -def sentence(state: QueryStateDict, result: Result) -> None: - # try: - print("sentence") - print( - "DESCENDANTS:", - list( - i[0].root(state, result.params) - for i in result.enum_descendants(lambda x: isinstance(x, TerminalNode)) - ), - ) - """Called when sentence processing is complete""" - q: Query = state["query"] +def QIoTSpeakerTurnOnVerb(node: Node, params: QueryStateDict, result: Result) -> None: + result["qkey"] = "play_music" + + +def QIoTSpeakerPauseVerb(node: Node, params: QueryStateDict, result: Result) -> None: + print("PAUSE") + result["qkey"] = "pause_music" + - q.set_qtype(result.get("qtype")) +def QIoTSpeakerPlayVerb(node: Node, params: QueryStateDict, result: Result) -> None: + result["qkey"] = "play_music" - # TODO: Find way to only catch playing commands - if result.get("action") == None: - result.action = "play_music" - smartdevice_type = "smart_speaker" +# def QIoTSpeakerRadioStationName( +# node: Node, params: QueryStateDict, result: Result +# ) -> None: +# result.target = "radio" +# print("radio") - # Fetch relevant data from the device_data table to perform an action on the lights - device_data = q.client_data("iot_speakers") - print(device_data) - # TODO: Need to add check for if there are no registered devices to an account, probably when initilazing the querydata - if device_data is not None: - sonos_client = SonosClient(device_data, q.client_id) +# def toggle_play_pause(q): +# device_data = q.client_data("iot_speakers") +# print(device_data) +# if device_data is not None: +# sonos_client = SonosClient(device_data, q.client_id) +# else: +# print("No device data found for this account") +# return +# sonos_client.toggle_play_pause() +# answer = "Ég kveikti á tónlist." +# answer_list = gen_answer(answer) +# answer_list[1].replace("Sonos", "Sónos") +# q.set_answer(*answer_list) + + +# def get_device_data(q): +# device_data = q.client_data("iot_speakers") +# if device_data is not None: +# return device_data +# else: +# print("No device data found for this account") +# return + + +_HANDLER_MAP = { + "play_music": ["toggle_play_pause", "Ég kveikti á tónlist"], + "pause_music": ["toggle_play_pause", "Ég slökkti á tónlist"], +} + + +def sentence(state: QueryStateDict, result: Result) -> None: + # try: + print("sentence") + """Called when sentence processing is complete""" + if "qtype" in result and "qkey" in result: + try: + q: Query = state["query"] + q.set_qtype(result.qtype) + device_data = q.client_data("iot_speakers") + if device_data is not None: + sonos_client = SonosClient(device_data, q.client_id) + handler_func = _HANDLER_MAP[result.qkey][0] + handler_answer = _HANDLER_MAP[result.qkey][1] + getattr(sonos_client, handler_func)() + answer = handler_answer + answer_list = gen_answer(answer) + answer_list[1].replace("Sonos", "Sónos") + q.set_answer(*answer_list) + else: + print("No device data found for this account") + return + except Exception as e: + logging.warning("Exception answering iot_speakers query: {0}".format(e)) + q.set_error("E_EXCEPTION: {0}".format(e)) + return else: - print("No device data found for this account") + q.set_error("E_QUERY_NOT_UNDERSTOOD") return - # Perform the action on the Sonos device - if result.action == "play_music": - sonos_client.toggle_play_pause() - answer = "Ég kveikti á tónlist." - # elif result.action == "increase_volume": - # sonos_client.increase_volume() - # elif result.action == "decrease_volume": - # sonos_client.decrease_volume() - # elif result.action == "set_volume": - # sonos_client.set_volume(result.get["volume"]) - - answer_list = gen_answer(answer) - answer_list[1].replace( - "Sonos", "Sónos" - ) # Accounting for Embla's Icelandic pronunciation - q.set_answer(*answer_list) - - # f"var BRIDGE_IP = '192.168.1.68';var USERNAME = 'p3obluiXT13IbHMpp4X63ZvZnpNRdbqqMt723gy2';" - # except Exception as e: - # print(e) - # print("Error in sentence") + # # TODO: Need to add check for if there are no registered devices to an account, probably when initilazing the querydata + # if device_data is not None: + # sonos_client = SonosClient(device_data, q.client_id) + # else: + # print("No device data found for this account") + # return + + # # Perform the action on the Sonos device + # if result.action == "play_music": + # sonos_client.toggle_play_pause(q) + # answer = "Ég kveikti á tónlist." + # answer_list = gen_answer(answer) + # answer_list[1].replace("Sonos", "Sónos") + # q.set_answer(*answer_list) From c1286d72f8d17e611c8409f27308ea8437d25120 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Tue, 5 Jul 2022 09:13:10 +0000 Subject: [PATCH 158/371] removed print lines and commented out code --- queries/theater_module.py | 45 --------------------------------------- 1 file changed, 45 deletions(-) diff --git a/queries/theater_module.py b/queries/theater_module.py index 72f0cd4c..1a7016b5 100644 --- a/queries/theater_module.py +++ b/queries/theater_module.py @@ -111,7 +111,6 @@ def help_text(lemma: str) -> str: | QNo | QCancel | QStatus - # TODO: Hvað er í boði, ég vil sýningu X, dagsetningu X, X mörg sæti, staðsetningu X QTheaterOptions → QTheaterGeneralOptions @@ -330,14 +329,12 @@ def _generate_show_answer( resource: ListResource, dsm: DialogueStateManager ) -> Optional[AnswerTuple]: result = dsm.get_result() - print("Generate show answer") if (not resource.is_confirmed and result.get("options_info")) or result.get( "show_options" ): shows: list[str] = [] for show in _SHOWS: shows.append("\n - " + show["title"]) - print("Prompts: ", resource.prompts) ans = resource.prompts["options"] if len(shows) == 1: ans = ans.replace("Sýningarnar", "Sýningin", 1).replace("eru", "er", 2) @@ -362,7 +359,6 @@ def _generate_show_answer( def _generate_date_answer( resource: ListResource, dsm: DialogueStateManager ) -> Optional[AnswerTuple]: - print("Generating date answer") result = dsm.get_result() title = dsm.get_resource("Show").data[0] @@ -506,25 +502,16 @@ def _generate_seat_count_answer( if resource.is_fulfilled: ans = resource.prompts["confirm"] nr_seats: int = resource.data - print("Seats: ", resource.data) if nr_seats == 1: - print("if ans: ", ans) ans = ans.replace("eru", "er") - print("after if ans: ", ans) text_ans = ans.format(seats=resource.data) voice_ans = ans.format(seats=number_to_text(resource.data)) - # return gen_answer( - # resource.prompts["confirm"].format( - # seats=number_to_text(cast(int, resource.data)) - # ) - # ) return (dict(answer=text_ans), text_ans, voice_ans) def _generate_row_answer( resource: ListResource, dsm: DialogueStateManager ) -> Optional[AnswerTuple]: - print("Generating row answer") result = dsm.get_result() title: str = dsm.get_resource("Show").data[0] seats: int = dsm.get_resource("ShowSeatCount").data @@ -597,7 +584,6 @@ def _generate_row_answer( def _generate_seat_number_answer( resource: ListResource, dsm: DialogueStateManager ) -> Optional[AnswerTuple]: - print("_generate_seat_number_answer", resource.state) result = dsm.get_result() title: str = dsm.get_resource("Show").data[0] seats: int = dsm.get_resource("ShowSeatCount").data @@ -622,7 +608,6 @@ def _generate_seat_number_answer( ) return (dict(answer=text_ans), text_ans, voice_ans) if result.get("wrong_number_seats_selected"): - print("wrong_number_seats_selected prompt") chosen_seats = len( range(result.get("numbers")[0], result.get("numbers")[1] + 1) ) @@ -633,10 +618,8 @@ def _generate_seat_number_answer( ) return (dict(answer=text_ans), text_ans, voice_ans) if result.get("seats_unavailable"): - print("seats_unavailable prompt") return gen_answer(resource.prompts["seats_unavailable"]) if resource.is_unfulfilled: - print("initial prompt") ans = resource.prompts["initial"] if len(available_seats) == 1: ans = ans.replace("eru", "er") @@ -645,13 +628,7 @@ def _generate_seat_number_answer( seats=natlang_seq(available_seats), row=number_to_text(chosen_row) ) return (dict(answer=text_ans), text_ans, voice_ans) - return gen_answer( - resource.prompts["initial"].format( - seats=natlang_seq(available_seats), row=number_to_text(chosen_row) - ) - ) if resource.is_fulfilled: - print("confirm prompt") chosen_seats_voice_string: str = "" chosen_seats_text_string: str = "" @@ -818,7 +795,6 @@ def _time_callback( and result["show_time"] == date.time() ): first_matching_date = date - print("Time callback, date there, setting time") resource.set_time(date.time()) dsm.set_resource_state( resource.name, ResourceState.FULFILLED @@ -925,17 +901,14 @@ def QTheaterMoreDates(node: Node, params: QueryStateDict, result: Result) -> Non def _next_dates( resource: NumberResource, dsm: DialogueStateManager, result: Result ) -> None: - print("In next dates") extras: Dict[str, Any] = dsm.get_extras() if "page_index" in extras: extras["page_index"] += 3 else: extras["page_index"] = 3 - print("Next dates page index:", extras["page_index"]) if "callbacks" not in result: result["callbacks"] = [] - print("In QTheaterMoreDates") filter_func: Callable[[Resource], bool] = lambda r: r.name == "ShowDate" result.callbacks.append((filter_func, _next_dates)) @@ -949,7 +922,6 @@ def _prev_dates( extras["page_index"] = max(extras["page_index"] - 3, 0) else: extras["page_index"] = 0 - print("Prev dates page index:", extras["page_index"]) if "callbacks" not in result: result["callbacks"] = [] @@ -997,8 +969,6 @@ def _add_row( else: checking_row = row seats_in_row = 1 - print("Add row: ", result.number) - print("Available rows: ", available_rows) if result.number in available_rows: resource.data = [result.number] dsm.set_resource_state(resource.name, ResourceState.FULFILLED) @@ -1017,25 +987,17 @@ def _add_seats( resource: ListResource, dsm: DialogueStateManager, result: Result ) -> None: if dsm.get_resource("ShowSeatRow").is_confirmed: - print("Add seats callback, state: ", resource.state) title: str = dsm.get_resource("Show").data[0] - print("Row data: ", dsm.get_resource("ShowSeatRow").data) row: int = dsm.get_resource("ShowSeatRow").data[0] number_of_seats: int = dsm.get_resource("ShowSeatCount").data - print("Result.numbers: ", len(result.numbers)) selected_seats: list[int] = [] if number_of_seats > 1: selected_seats = [ seat for seat in range(result.numbers[0], result.numbers[1] + 1) ] else: - print("Result.numbers: ", result.numbers) - print("Result.number: ", result.number) selected_seats = [result.numbers[0]] - print("Selected seats: ", selected_seats) if len(selected_seats) != number_of_seats: - print("Selected seats does not match number of seats") - print("Resource name that is being emptied: ", resource.name) resource.data = [] dsm.set_resource_state(resource.name, ResourceState.UNFULFILLED) result.wrong_number_seats_selected = True @@ -1047,7 +1009,6 @@ def _add_seats( if (row, seat) in show["location"]: seats.append(seat) else: - print("Seat unavailable") resource.data = [] dsm.set_resource_state( resource.name, ResourceState.UNFULFILLED @@ -1057,9 +1018,7 @@ def _add_seats( resource.data = [] for seat in seats: resource.data.append(seat) - print("Length of data: ", len(resource.data)) if len(resource.data) > 0: - print("Setting state to fulfilled") dsm.set_resource_state(resource.name, ResourceState.FULFILLED) if "callbacks" not in result: @@ -1069,17 +1028,14 @@ def _add_seats( def QTheaterGeneralOptions(node: Node, params: QueryStateDict, result: Result) -> None: - print("QTheaterGeneralOptions") result.options_info = True def QTheaterShowOptions(node: Node, params: QueryStateDict, result: Result) -> None: - print("QTheaterShowOptions") result.show_options = True def QTheaterDateOptions(node: Node, params: QueryStateDict, result: Result) -> None: - print("QTheaterDateOptions") result.date_options = True @@ -1126,7 +1082,6 @@ def _parse_yes( resource: Resource, dsm: DialogueStateManager, result: Result ) -> None: if "yes_used" not in result and resource.is_fulfilled: - print("YES USED", resource.name, " confirming") dsm.set_resource_state(resource.name, ResourceState.CONFIRMED) result.yes_used = True if resource.name == "ShowDateTime": From 6880d1873512b34f1be8c7a8d0c63e46cabbad7c Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Tue, 5 Jul 2022 10:12:50 +0000 Subject: [PATCH 159/371] added 101 radio --- queries/grammars/iot_speakers.grammar | 5 +++++ queries/iot_hue.py | 1 + 2 files changed, 6 insertions(+) diff --git a/queries/grammars/iot_speakers.grammar b/queries/grammars/iot_speakers.grammar index 284a40ab..db830a6a 100644 --- a/queries/grammars/iot_speakers.grammar +++ b/queries/grammars/iot_speakers.grammar @@ -303,3 +303,8 @@ QIoTSpeakerFlashback -> "flassbakk" "fm"? | "flass" "bakk" "fm"? +QIoTUtvarp101 -> + "útvarp"? "101" + | "útvarp"? "hundrað" "og" "einn" + | "útvarp"? "hundrað" "og" "eitt" + diff --git a/queries/iot_hue.py b/queries/iot_hue.py index 5698e9f7..a1a46dcb 100755 --- a/queries/iot_hue.py +++ b/queries/iot_hue.py @@ -32,6 +32,7 @@ # TODO: Two specified groups or lights. # TODO: No specified location # TODO: Fix scene issues +# TODO: Turning on lights without using "turn on" from typing import Dict, Mapping, Optional, cast, FrozenSet from typing_extensions import TypedDict From 7a66d026d5c2dca0c100d784a96c2a54bd9df1fb Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Tue, 5 Jul 2022 10:28:29 +0000 Subject: [PATCH 160/371] =?UTF-8?q?added=20"spila=C3=B0u=20UTVARPSST=C3=96?= =?UTF-8?q?=C3=90"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- queries/grammars/iot_speakers.grammar | 1 + 1 file changed, 1 insertion(+) diff --git a/queries/grammars/iot_speakers.grammar b/queries/grammars/iot_speakers.grammar index db830a6a..3b3bf88d 100644 --- a/queries/grammars/iot_speakers.grammar +++ b/queries/grammars/iot_speakers.grammar @@ -111,6 +111,7 @@ QIoTSpeakerTurnOffRest -> QIoTSpeakerPlayRest -> QIoTSpeakerMusicWordÞf QIoTSpeakerHvar? + | QIoTSpeakerRadioStationWord? QIoTSpeakerRadioStationName QIoTSpeakerHvar? # TODO: Make the subject categorization cleaner QIoTSpeakerIncreaseOrDecreaseRest -> From 577a5dbb588db3a5e7f4aadf77ba6d54fdfa3f72 Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Tue, 5 Jul 2022 10:29:53 +0000 Subject: [PATCH 161/371] conjugation fix --- queries/grammars/iot_speakers.grammar | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/queries/grammars/iot_speakers.grammar b/queries/grammars/iot_speakers.grammar index 3b3bf88d..647d7d69 100644 --- a/queries/grammars/iot_speakers.grammar +++ b/queries/grammars/iot_speakers.grammar @@ -111,7 +111,7 @@ QIoTSpeakerTurnOffRest -> QIoTSpeakerPlayRest -> QIoTSpeakerMusicWordÞf QIoTSpeakerHvar? - | QIoTSpeakerRadioStationWord? QIoTSpeakerRadioStationName QIoTSpeakerHvar? + | QIoTSpeakerRadioStationWord/þf? QIoTSpeakerRadioStationName QIoTSpeakerHvar? # TODO: Make the subject categorization cleaner QIoTSpeakerIncreaseOrDecreaseRest -> From 2acc00ae1cbba1e27009364a3afe6d7229c6fc62 Mon Sep 17 00:00:00 2001 From: Logi E Date: Tue, 5 Jul 2022 10:33:47 +0000 Subject: [PATCH 162/371] changed ResourceState to IntFlag enum type --- queries/dialogue.py | 33 +++++++++++++++++++++------------ queries/dialogues/theater.toml | 2 +- queries/theater_module.py | 7 +------ 3 files changed, 23 insertions(+), 19 deletions(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index 63674943..472d3272 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -16,7 +16,7 @@ import os.path import json import datetime -from enum import IntEnum, IntFlag, auto +from enum import IntFlag, auto from dataclasses import dataclass, field try: @@ -53,7 +53,7 @@ AnsweringFunctionMap = Mapping[str, AnsweringFunctionType[Any]] -class ResourceState(IntEnum): +class ResourceState(IntFlag): """Enum representing the different states a dialogue resource can be in.""" # Main states (order matters, lower state should equal a lower number) @@ -65,7 +65,16 @@ class ResourceState(IntEnum): PAUSED = auto() SKIPPED = auto() CANCELLED = auto() - # ALL = UNFULFILLED | PARTIALLY_FULFILLED | FULFILLED | CONFIRMED | PAUSED | SKIPPED | CANCELLED + ALL = ( + UNFULFILLED + | PARTIALLY_FULFILLED + | FULFILLED + | CONFIRMED + | PAUSED + | SKIPPED + | CANCELLED + ) + ########################## # RESOURCE CLASSES # @@ -96,31 +105,31 @@ class Resource: @property def is_unfulfilled(self) -> bool: - return self.state is ResourceState.UNFULFILLED + return ResourceState.UNFULFILLED in self.state @property def is_partially_fulfilled(self) -> bool: - return self.state is ResourceState.PARTIALLY_FULFILLED + return ResourceState.PARTIALLY_FULFILLED in self.state @property def is_fulfilled(self) -> bool: - return self.state is ResourceState.FULFILLED + return ResourceState.FULFILLED in self.state @property def is_confirmed(self) -> bool: - return self.state is ResourceState.CONFIRMED + return ResourceState.CONFIRMED in self.state @property def is_paused(self) -> bool: - return self.state is ResourceState.PAUSED + return ResourceState.PAUSED in self.state @property def is_skipped(self) -> bool: - return self.state is ResourceState.SKIPPED + return ResourceState.SKIPPED in self.state @property def is_cancelled(self) -> bool: - return self.state is ResourceState.CANCELLED + return ResourceState.CANCELLED in self.state def update(self, new_data: Optional["Resource"]) -> None: """Update resource with attributes from another resource.""" @@ -293,7 +302,7 @@ class DialogueStructureType(TypedDict): dialogue_name: str resources: Dict[str, Resource] last_interacted_with: Optional[datetime.datetime] - extras: Dict[str, Any] + extras: Optional[Dict[str, Any]] def _load_dialogue_structure(filename: str) -> DialogueStructureType: @@ -360,7 +369,7 @@ def setup_dialogue(self, answering_functions: AnsweringFunctionMap) -> None: resource.state = ResourceState(resource.state) self._resources[rname] = resource if _DIALOGUE_EXTRAS_KEY in self._saved_state: - self._extras = self._saved_state[_DIALOGUE_EXTRAS_KEY] + self._extras = self._saved_state[_DIALOGUE_EXTRAS_KEY] or self._extras self._answering_functions = answering_functions if self._result.qtype == self._start_qtype: diff --git a/queries/dialogues/theater.toml b/queries/dialogues/theater.toml index 10d4a5f4..a2801532 100644 --- a/queries/dialogues/theater.toml +++ b/queries/dialogues/theater.toml @@ -64,7 +64,7 @@ cascade_state = true prompts.initial = "Sæti {seats} eru í boði í röð {row}, hvaða sæti má bjóða þér?" prompts.options = "Sætin sem eru í boði í röð {row} eru {options}" prompts.confirm = "Þú valdir sæti {seats}, viltu halda áfram?" -prompts.wrong_number_seats_selected = "Þú valdir {chosen_seats} sæti, en þú baðst um {seats}. Vinsamlegast reyndur aftur." +prompts.wrong_number_seats_selected = "Þú valdir {chosen_seats} sæti, en þú baðst um {seats}. Vinsamlegast reyndu aftur." prompts.seats_unavailable = "Valin sæti eru ekki laus, vinsamlegast reyndu aftur." [[resources]] diff --git a/queries/theater_module.py b/queries/theater_module.py index 72f0cd4c..d4b71d0d 100644 --- a/queries/theater_module.py +++ b/queries/theater_module.py @@ -236,7 +236,7 @@ def help_text(lemma: str) -> str: QNo → "nei" "takk"? | "nei" "nei"* | "neitakk" | "ómögulega" QCancel → "ég" "hætti" "við" - | QTheaterEgVil "hætta" "við" QTheaterPontun + | QTheaterEgVil "hætta" "við" QTheaterPontun? QStatus → "staðan" @@ -645,11 +645,6 @@ def _generate_seat_number_answer( seats=natlang_seq(available_seats), row=number_to_text(chosen_row) ) return (dict(answer=text_ans), text_ans, voice_ans) - return gen_answer( - resource.prompts["initial"].format( - seats=natlang_seq(available_seats), row=number_to_text(chosen_row) - ) - ) if resource.is_fulfilled: print("confirm prompt") chosen_seats_voice_string: str = "" From 39b8e07ef5287e6f9e1c57bdf096740c66cdb889 Mon Sep 17 00:00:00 2001 From: Logi E Date: Tue, 5 Jul 2022 11:02:01 +0000 Subject: [PATCH 163/371] added add_callback class method to DSM --- queries/dialogue.py | 15 +++++++- queries/fruitseller_module.py | 65 +++++++++++++++-------------------- queries/theater_module.py | 60 +++++++------------------------- 3 files changed, 55 insertions(+), 85 deletions(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index 472d3272..ff9d7acc 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -36,6 +36,7 @@ _DIALOGUE_EXTRAS_KEY = "extras" _EMPTY_DIALOGUE_DATA = "{}" _FINAL_RESOURCE_NAME = "Final" +_CALLBACK_LOCATION = "callbacks" # Generic resource type ResourceType_co = TypeVar("ResourceType_co", bound="Resource") @@ -413,7 +414,7 @@ def get_extras(self) -> Dict[str, Any]: def get_answer(self) -> Optional[AnswerTuple]: # Executing callbacks - cbs: Optional[List[_CallbackTupleType]] = self._result.get("callbacks") + cbs: Optional[List[_CallbackTupleType]] = self._result.get(_CALLBACK_LOCATION) curr_resource = self._resources[_FINAL_RESOURCE_NAME] if cbs: self._execute_callbacks_postorder(curr_resource, cbs, set()) @@ -546,6 +547,18 @@ def end_dialogue(self) -> None: def set_error(self) -> None: self._error = True + @classmethod # TODO: Fix type hints? + def add_callback( + cls, + result: Result, + filter_func: _FilterFuncType[Resource], + cb: _CallbackType[Resource], + ): + """Add a callback to the callback list""" + if _CALLBACK_LOCATION not in result: + result[_CALLBACK_LOCATION] = [] + result.callbacks.append((filter_func, cb)) + ################################### # ENCODING/DECODING CLASSES # diff --git a/queries/fruitseller_module.py b/queries/fruitseller_module.py index cf3699d2..57d6a4f3 100644 --- a/queries/fruitseller_module.py +++ b/queries/fruitseller_module.py @@ -120,7 +120,7 @@ QNo → "nei" "takk"? | "nei" "nei"* | "neitakk" | "ómögulega" QCancelOrder → "ég" "hætti" "við" - | "ég" "vil" "hætta" "við" "pöntunina" + | "ég" "vil" "hætta" "við" "pöntunina"? | "ég" "vill" "hætta" "við" "pöntunina" QFruitDateQuery → @@ -253,11 +253,8 @@ def _add_fruit( query_fruit_index += 1 resource.state = ResourceState.PARTIALLY_FULFILLED - if "callbacks" not in result: - result["callbacks"] = [] - filter_func: Callable[[Resource], bool] = lambda r: r.name == "Fruits" - result.callbacks.append((filter_func, _add_fruit)) result.qtype = "QAddFruitQuery" + DialogueStateManager.add_callback(result, lambda r: r.name == "Fruits", _add_fruit) def QRemoveFruitQuery(node: Node, params: QueryStateDict, result: Result): @@ -278,11 +275,10 @@ def _remove_fruit( else: resource.state = ResourceState.PARTIALLY_FULFILLED - if "callbacks" not in result: - result["callbacks"] = [] - filter_func: Callable[[Resource], bool] = lambda r: r.name == "Fruits" - result.callbacks.append((filter_func, _remove_fruit)) result.qtype = "QRemoveFruitQuery" + DialogueStateManager.add_callback( + result, lambda r: r.name == "Fruits", _remove_fruit + ) def QCancelOrder(node: Node, params: QueryStateDict, result: Result): @@ -294,10 +290,9 @@ def _cancel_order( result.qtype = "QCancelOrder" result.answer_key = ("Final", "cancelled") - if "callbacks" not in result: - result["callbacks"] = [] - filter_func: Callable[[Resource], bool] = lambda r: r.name == "Final" - result.callbacks.append((filter_func, _cancel_order)) + DialogueStateManager.add_callback( + result, lambda r: r.name == "Final", _cancel_order + ) def QFruitOptionsQuery(node: Node, params: QueryStateDict, result: Result): @@ -317,13 +312,12 @@ def _parse_yes( for rname in resource.requires: dsm.get_resource(rname).state = ResourceState.CONFIRMED - if "callbacks" not in result: - result["callbacks"] = [] - filter_func: Callable[[Resource], bool] = ( - lambda r: r.name in ("Fruits", "DateTime") and not r.is_confirmed - ) - result.callbacks.append((filter_func, _parse_yes)) result.qtype = "QYes" + DialogueStateManager.add_callback( + result, + lambda r: r.name in ("Fruits", "DateTime") and not r.is_confirmed, + _parse_yes, + ) def QNo(node: Node, params: QueryStateDict, result: Result): @@ -336,13 +330,10 @@ def _parse_no( elif resource.is_fulfilled: resource.state = ResourceState.PARTIALLY_FULFILLED - if "callbacks" not in result: - result["callbacks"] = [] - filter_func: Callable[[Resource], bool] = ( - lambda r: r.name == "Fruits" and not r.is_confirmed - ) - result.callbacks.append((filter_func, _parse_no)) result.qtype = "QNo" + DialogueStateManager.add_callback( + result, lambda r: r.name == "Fruits" and not r.is_confirmed, _parse_no + ) def QNumOfFruit(node: Node, params: QueryStateDict, result: Result): @@ -402,10 +393,9 @@ def QFruitDate(node: Node, params: QueryStateDict, result: Result) -> None: y += 1 result["delivery_date"] = datetime.date(day=d, month=m, year=y) - if "callbacks" not in result: - result["callbacks"] = [] - filter_func: Callable[[Resource], bool] = lambda r: r.name == "Date" - result.callbacks.append((filter_func, _date_callback)) + DialogueStateManager.add_callback( + result, lambda r: r.name == "Date", _date_callback + ) return raise ValueError("No date in {0}".format(str(datenode))) @@ -436,10 +426,9 @@ def QFruitTime(node: Node, params: QueryStateDict, result: Result): if hour in range(0, 24) and minute in range(0, 60): result["delivery_time"] = datetime.time(hour, minute) - if "callbacks" not in result: - result["callbacks"] = [] - filter_func: Callable[[Resource], bool] = lambda r: r.name == "Time" - result.callbacks.append((filter_func, _time_callback)) + DialogueStateManager.add_callback( + result, lambda r: r.name == "Time", _time_callback + ) else: result["parse_error"] = True @@ -449,8 +438,6 @@ def QFruitDateTime(node: Node, params: QueryStateDict, result: Result) -> None: datetimenode = node.first_child(lambda n: True) assert isinstance(datetimenode, TerminalNode) now = datetime.datetime.now() - if "callbacks" not in result: - result["callbacks"] = [] y, m, d, h, min, _ = (i if i != 0 else None for i in json.loads(datetimenode.aux)) if y is None: y = now.year @@ -458,11 +445,15 @@ def QFruitDateTime(node: Node, params: QueryStateDict, result: Result) -> None: result["delivery_date"] = datetime.date(y, m, d) if result["delivery_date"] < now.date(): result["delivery_date"].year += 1 - result.callbacks.append((lambda r: r.name == "Date", _date_callback)) + DialogueStateManager.add_callback( + result, lambda r: r.name == "Date", _date_callback + ) if h is not None and min is not None: result["delivery_time"] = datetime.time(h, min) - result.callbacks.append((lambda r: r.name == "Time", _time_callback)) + DialogueStateManager.add_callback( + result, lambda r: r.name == "Time", _time_callback + ) def QFruitInfoQuery(node: Node, params: QueryStateDict, result: Result): diff --git a/queries/theater_module.py b/queries/theater_module.py index a9f11ef9..63526acc 100644 --- a/queries/theater_module.py +++ b/queries/theater_module.py @@ -731,10 +731,7 @@ def _add_show( if resource.is_fulfilled: result.no_show_matched_data_exists = True - if "callbacks" not in result: - result["callbacks"] = [] - filter_func: Callable[[Resource], bool] = lambda r: r.name == "Show" - result.callbacks.append((filter_func, _add_show)) + DialogueStateManager.add_callback(result, lambda r: r.name == "Show", _add_show) def _date_callback( @@ -848,10 +845,8 @@ def QTheaterDateTime(node: Node, params: QueryStateDict, result: Result) -> None result["show_time"] = datetime.time(h, min) result["show_date"] = datetime.date(y, m, d) - if "callbacks" not in result: - result["callbacks"] = [] - result.callbacks.append((lambda r: r.name == "ShowDate", _date_callback)) - result.callbacks.append((lambda r: r.name == "ShowTime", _time_callback)) + DialogueStateManager.add_callback(result, lambda r: r.name == "ShowDate", _date_callback) + DialogueStateManager.add_callback(result, lambda r: r.name == "ShowTime", _time_callback) def QTheaterDate(node: Node, params: QueryStateDict, result: Result) -> None: @@ -871,10 +866,7 @@ def QTheaterDate(node: Node, params: QueryStateDict, result: Result) -> None: y += 1 result["show_date"] = datetime.date(day=d, month=m, year=y) - if "callbacks" not in result: - result["callbacks"] = [] - filter_func: Callable[[Resource], bool] = lambda r: r.name == "ShowDate" - result.callbacks.append((filter_func, _date_callback)) + DialogueStateManager.add_callback(result, lambda r: r.name == "ShowDate", _date_callback) return raise ValueError("No date in {0}".format(str(datenode))) @@ -891,10 +883,7 @@ def QTheaterTime(node: Node, params: QueryStateDict, result: Result) -> None: result["show_time"] = datetime.time(hour, minute) - if "callbacks" not in result: - result["callbacks"] = [] - filter_func: Callable[[Resource], bool] = lambda r: r.name == "ShowTime" - result.callbacks.append((filter_func, _time_callback)) + DialogueStateManager.add_callback(result, lambda r: r.name == "ShowTime", _time_callback) def QTheaterMoreDates(node: Node, params: QueryStateDict, result: Result) -> None: @@ -907,10 +896,7 @@ def _next_dates( else: extras["page_index"] = 3 - if "callbacks" not in result: - result["callbacks"] = [] - filter_func: Callable[[Resource], bool] = lambda r: r.name == "ShowDate" - result.callbacks.append((filter_func, _next_dates)) + DialogueStateManager.add_callback(result, lambda r: r.name == "ShowDate", _next_dates) def QTheaterPreviousDates(node: Node, params: QueryStateDict, result: Result) -> None: @@ -923,10 +909,7 @@ def _prev_dates( else: extras["page_index"] = 0 - if "callbacks" not in result: - result["callbacks"] = [] - filter_func: Callable[[Resource], bool] = lambda r: r.name == "ShowDate" - result.callbacks.append((filter_func, _prev_dates)) + DialogueStateManager.add_callback(result, lambda r: r.name == "ShowDate", _prev_dates) def QTheaterShowSeatCountQuery( @@ -942,10 +925,7 @@ def _add_seat_number( else: result.invalid_seat_count = True - if "callbacks" not in result: - result["callbacks"] = [] - filter_func: Callable[[Resource], bool] = lambda r: r.name == "ShowSeatCount" - result.callbacks.append((filter_func, _add_seat_number)) + DialogueStateManager.add_callback(result, lambda r: r.name == "ShowSeatCount", _add_seat_number) def QTheaterShowRow(node: Node, params: QueryStateDict, result: Result) -> None: @@ -976,11 +956,7 @@ def _add_row( dsm.set_resource_state(resource.name, ResourceState.UNFULFILLED) result.no_row_matched = True - if "callbacks" not in result: - result["callbacks"] = [] - filter_func: Callable[[Resource], bool] = lambda r: r.name == "ShowSeatRow" - result.callbacks.append((filter_func, _add_row)) - + DialogueStateManager.add_callback(result, lambda r: r.name == "ShowSeatRow", _add_row) def QTheaterShowSeats(node: Node, params: QueryStateDict, result: Result) -> None: def _add_seats( @@ -1021,10 +997,7 @@ def _add_seats( if len(resource.data) > 0: dsm.set_resource_state(resource.name, ResourceState.FULFILLED) - if "callbacks" not in result: - result["callbacks"] = [] - filter_func: Callable[[Resource], bool] = lambda r: r.name == "ShowSeatNumber" - result.callbacks.append((filter_func, _add_seats)) + DialogueStateManager.add_callback(result, lambda r: r.name == "ShowSeatNumber", _add_seats) def QTheaterGeneralOptions(node: Node, params: QueryStateDict, result: Result) -> None: @@ -1071,10 +1044,7 @@ def _cancel_order( dsm.end_dialogue() result.qtype = "QCancel" - if "callbacks" not in result: - result["callbacks"] = [] - filter_func: Callable[[Resource], bool] = lambda r: r.name == "Final" - result.callbacks.append((filter_func, _cancel_order)) + DialogueStateManager.add_callback(result, lambda r: r.name == "Final", _cancel_order) def QYes(node: Node, params: QueryStateDict, result: Result): @@ -1088,14 +1058,12 @@ def _parse_yes( for rname in resource.requires: dsm.get_resource(rname).state = ResourceState.CONFIRMED - if "callbacks" not in result: - result["callbacks"] = [] filter_func: Callable[[Resource], bool] = ( lambda r: r.name in ("Show", "ShowDateTime", "ShowSeatCount", "ShowSeatRow", "ShowSeatNumber") and not r.is_confirmed ) - result.callbacks.append((filter_func, _parse_yes)) + DialogueStateManager.add_callback(result, filter_func, _parse_yes) def QNo(node: Node, params: QueryStateDict, result: Result): @@ -1109,14 +1077,12 @@ def _parse_no( dsm.get_resource("ShowDate").state = ResourceState.UNFULFILLED dsm.get_resource("ShowTime").state = ResourceState.UNFULFILLED - if "callbacks" not in result: - result["callbacks"] = [] filter_func: Callable[[Resource], bool] = ( lambda r: r.name in ("Show", "ShowDateTime", "ShowSeatCount", "ShowSeatRow", "ShowSeatNumber") and not r.is_confirmed ) - result.callbacks.append((filter_func, _parse_no)) + DialogueStateManager.add_callback(result, filter_func, _parse_no) def QStatus(node: Node, params: QueryStateDict, result: Result): From 0f60ef17bb43402522f2e99a8ee1e7b7971d065f Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Tue, 5 Jul 2022 11:45:16 +0000 Subject: [PATCH 164/371] Sonos radio functionality v1 --- queries/grammars/iot_speakers.grammar | 7 +- queries/iot_hue.py | 15 +++- queries/iot_speakers.py | 107 ++++++++++++++++++++++++-- queries/sonos.py | 81 +++++++++---------- routes/api.py | 6 +- 5 files changed, 167 insertions(+), 49 deletions(-) diff --git a/queries/grammars/iot_speakers.grammar b/queries/grammars/iot_speakers.grammar index 647d7d69..e2e9abe6 100644 --- a/queries/grammars/iot_speakers.grammar +++ b/queries/grammars/iot_speakers.grammar @@ -154,6 +154,7 @@ QIoTSpeakerRadioStationName -> | QIoTSpeakerRetro | QIoTSpeakerKissFm | QIoTSpeakerFlashback + | QIoTSpeakerUtvarp101 QIoTSpeakerRadioStationWord/fall -> 'útvarpsstöð:no'/fall @@ -292,6 +293,8 @@ QIoTSpeakerK100 -> | "k100" | "k-hundrað" | "k-100" + | "kk" "hundrað" + | "kk" "100" QIoTSpeakerRetro -> "retro" "fm"? @@ -304,8 +307,10 @@ QIoTSpeakerFlashback -> "flassbakk" "fm"? | "flass" "bakk" "fm"? -QIoTUtvarp101 -> +QIoTSpeakerUtvarp101 -> "útvarp"? "101" | "útvarp"? "hundrað" "og" "einn" | "útvarp"? "hundrað" "og" "eitt" + | "útvarp"? "hundrað" "einn" + | "útvarp"? "hundrað" "1" diff --git a/queries/iot_hue.py b/queries/iot_hue.py index a1a46dcb..865adcec 100755 --- a/queries/iot_hue.py +++ b/queries/iot_hue.py @@ -46,7 +46,7 @@ from query import Query, QueryStateDict, AnswerTuple from queries import gen_answer, read_jsfile, read_grammar_file -from tree import Result, Node +from tree import Result, Node, TerminalNode class SmartLights(TypedDict): @@ -275,7 +275,9 @@ def QIoTLightName(node: Node, params: QueryStateDict, result: Result) -> None: "fm-957", "k-100", "k 100", + "kk 100", "k hundrað", + "kk hundrað", "x977", "x 977", "x-977", @@ -284,6 +286,12 @@ def QIoTLightName(node: Node, params: QueryStateDict, result: Result) -> None: "kiss fm", "flassbakk", "flassbakk fm", + "útvarp hundraðið", + "útvarp 101", + "útvarp hundraðogeinn", + "útvarp hundrað og einn", + "útvarp hundrað einn", + "útvarp hundrað 1", ) ) @@ -291,7 +299,10 @@ def QIoTLightName(node: Node, params: QueryStateDict, result: Result) -> None: def sentence(state: QueryStateDict, result: Result) -> None: """Called when sentence processing is complete""" q: Query = state["query"] - lemmas = set(i[0] for i in simple_lemmatize(q.query.lower().split())) + lemmas = set( + i[0].root(state, result.params) + for i in result.enum_descendants(lambda x: isinstance(x, TerminalNode)) + ) if not _SPEAKER_WORDS.isdisjoint(lemmas): print("matched with music word list") q.set_error("E_QUERY_NOT_UNDERSTOOD") diff --git a/queries/iot_speakers.py b/queries/iot_speakers.py index 1e8c3f2c..06ed75a7 100644 --- a/queries/iot_speakers.py +++ b/queries/iot_speakers.py @@ -22,6 +22,23 @@ of random numbers, e.g. "Kastaðu tengingi", "Nefndu tölu milli 5 og 10", etc. """ +_RADIO_STREAMS = { + "Rás 1": "http://netradio.ruv.is/ras1.mp3", + "Rás 2": "http://netradio.ruv.is/ras2.mp3", + "Rondó": "http://netradio.ruv.is/rondo.mp3", + "Bylgjan": "https://live.visir.is/hls-radio/bylgjan/playlist.m3u8", + "FM957": "https://live.visir.is/hls-radio/fm957/playlist.m3u8", + "Útvarp Saga": "https://stream.utvarpsaga.is/Hljodver", + "K100": "https://k100streymi.mbl.is/beint/k100/tracks-v1a1/rewind-3600.m3u8", + "Gullbylgjan": "https://live.visir.is/hls-radio/gullbylgjan/playlist.m3u8", + "X977": "https://live.visir.is/hls-radio/x977/playlist.m3u8", + "Léttbylgjan": "https://live.visir.is/hls-radio/lettbylgjan/playlist.m3u8", + "Retro": "https://k100straumar.mbl.is/retromobile", + "KissFM": "http://stream3.radio.is:443/kissfm", + "Flashback": "http://stream.radio.is:443/flashback", + "Útvarp 101": "https://stream.101.live/audio/101/chunklist.m3u8", +} + # TODO: add "láttu", "hafðu", "litaðu", "kveiktu" functionality. # TODO: make the objects of sentences more modular, so that the same structure doesn't need to be written for each action @@ -127,6 +144,76 @@ def QIoTSpeakerPlayVerb(node: Node, params: QueryStateDict, result: Result) -> N result["qkey"] = "play_music" +def QIoTSpeakerRas1(node: Node, params: QueryStateDict, result: Result) -> None: + result["qkey"] = "radio" + result["station"] = "Rás 1" + + +def QIoTSpeakerRas2(node: Node, params: QueryStateDict, result: Result) -> None: + result["qkey"] = "radio" + result["station"] = "Rás 2" + + +def QIoTSpeakerRondo(node: Node, params: QueryStateDict, result: Result) -> None: + result["qkey"] = "radio" + result["station"] = "Rondó" + + +def QIoTSpeakerBylgjan(node: Node, params: QueryStateDict, result: Result) -> None: + result["qkey"] = "radio" + result["station"] = "Bylgjan" + + +def QIoTSpeakerFm957(node: Node, params: QueryStateDict, result: Result) -> None: + result["qkey"] = "radio" + result["station"] = "FM957" + + +def QIoTSpeakerUtvarpSaga(node: Node, params: QueryStateDict, result: Result) -> None: + result["qkey"] = "radio" + result["station"] = "Útvarp Saga" + + +def QIoTSpeakerGullbylgjan(node: Node, params: QueryStateDict, result: Result) -> None: + result["qkey"] = "radio" + result["station"] = "Gullbylgjan" + + +def QIoTSpeakerLettbylgjan(node: Node, params: QueryStateDict, result: Result) -> None: + result["qkey"] = "radio" + result["station"] = "Léttbylgjan" + + +def QIoTSpeakerXid(node: Node, params: QueryStateDict, result: Result) -> None: + result["qkey"] = "radio" + result["station"] = "X977" + + +def QIoTSpeakerKissfm(node: Node, params: QueryStateDict, result: Result) -> None: + result["qkey"] = "radio" + result["station"] = "KissFM" + + +def QIoTSpeakerFlassback(node: Node, params: QueryStateDict, result: Result) -> None: + result["qkey"] = "radio" + result["station"] = "Flashback" + + +def QIoTSpeakerRetro(node: Node, params: QueryStateDict, result: Result) -> None: + result["qkey"] = "radio" + result["station"] = "Retro" + + +def QIoTSpeakerUtvarp101(node: Node, params: QueryStateDict, result: Result) -> None: + result["qkey"] = "radio" + result["station"] = "Útvarp 101" + + +def QIoTSpeakerK100(node: Node, params: QueryStateDict, result: Result) -> None: + result["qkey"] = "radio" + result["station"] = "K100" + + # def QIoTSpeakerRadioStationName( # node: Node, params: QueryStateDict, result: Result # ) -> None: @@ -158,32 +245,42 @@ def QIoTSpeakerPlayVerb(node: Node, params: QueryStateDict, result: Result) -> N # return +def call_sonos_client(sonos_client, result): + handler_func = _HANDLER_MAP[result.qkey][0] + if result.get("station") is not None: + radio_url = _RADIO_STREAMS.get(f"{result.station}") + getattr(sonos_client, handler_func)(radio_url) + else: + getattr(sonos_client, handler_func)() + return + + _HANDLER_MAP = { "play_music": ["toggle_play_pause", "Ég kveikti á tónlist"], "pause_music": ["toggle_play_pause", "Ég slökkti á tónlist"], "increase_volume": ["increase_volume", "Ég hækkaði í tónlistinni"], "decrease_volume": ["decrease_volume", "Ég lækkaði í tónlistinni"], + "radio": ["play_radio_stream", "Ég setti á útvarpstöðina"], } def sentence(state: QueryStateDict, result: Result) -> None: - # try: - print("sentence") """Called when sentence processing is complete""" + print("sentence") + q: Query = state["query"] if "qtype" in result and "qkey" in result: try: - q: Query = state["query"] q.set_qtype(result.qtype) device_data = q.client_data("iot_speakers") if device_data is not None: sonos_client = SonosClient(device_data, q.client_id) - handler_func = _HANDLER_MAP[result.qkey][0] + call_sonos_client(sonos_client, result) handler_answer = _HANDLER_MAP[result.qkey][1] - getattr(sonos_client, handler_func)() answer = handler_answer answer_list = gen_answer(answer) answer_list[1].replace("Sonos", "Sónos") q.set_answer(*answer_list) + return else: print("No device data found for this account") return diff --git a/queries/sonos.py b/queries/sonos.py index 6d61a9c1..95de574b 100644 --- a/queries/sonos.py +++ b/queries/sonos.py @@ -66,33 +66,33 @@ "bóka herbergi": "Library", } -_RADIO_STEAMS = { - "rás 1": " http://netradio.ruv.is/ras1.mp3", - "rás 2": "http://netradio.ruv.is/ras2.mp3", - "rondo": "http://netradio.ruv.is/rondo.mp3", - "rondó": "http://netradio.ruv.is/rondo.mp3", - "bylgjan": "https://live.visir.is/hls-radio/bylgjan/playlist.m3u8", - "fm 957": "https://live.visir.is/hls-radio/fm957/playlist.m3u8", - "fm957": "https://live.visir.is/hls-radio/fm957/playlist.m3u8", - "fm-957": "https://live.visir.is/hls-radio/fm957/playlist.m3u8", - "Útvarp Saga": "https://stream.utvarpsaga.is/Hljodver", - "k-100": "https://k100streymi.mbl.is/beint/k100/tracks-v1a1/rewind-3600.m3u8", - "k 100": "https://k100streymi.mbl.is/beint/k100/tracks-v1a1/rewind-3600.m3u8", - "k hundrað": "https://k100streymi.mbl.is/beint/k100/tracks-v1a1/rewind-3600.m3u8", - "Gullbylgjan": "https://live.visir.is/hls-radio/gullbylgjan/playlist.m3u8", - "x977": "https://live.visir.is/hls-radio/x977/playlist.m3u8", - "x 977": "https://live.visir.is/hls-radio/x977/playlist.m3u8", - "x-977": "https://live.visir.is/hls-radio/x977/playlist.m3u8", - "x-ið": "https://live.visir.is/hls-radio/x977/playlist.m3u8", - "x-ið 977": "https://live.visir.is/hls-radio/x977/playlist.m3u8", - "léttbylgjan": "https://live.visir.is/hls-radio/lettbylgjan/playlist.m3u8", - "retro": "https://k100straumar.mbl.is/retromobile", - "kiss fm": "http://stream3.radio.is:443/kissfm", - "flassbakk": "http://stream.radio.is:443/flashback", - "flassbakk fm": "http://stream.radio.is:443/flashback", - "útvarp 101": "https://stream.101.live/audio/101/chunklist.m3u8", - "útvarp hundrað og einn": "https://stream.101.live/audio/101/chunklist.m3u8", -} +# _RADIO_STEAMS = { +# "rás 1": " http://netradio.ruv.is/ras1.mp3", +# "rás 2": "http://netradio.ruv.is/ras2.mp3", +# "rondo": "http://netradio.ruv.is/rondo.mp3", +# "rondó": "http://netradio.ruv.is/rondo.mp3", +# "bylgjan": "https://live.visir.is/hls-radio/bylgjan/playlist.m3u8", +# "fm 957": "https://live.visir.is/hls-radio/fm957/playlist.m3u8", +# "fm957": "https://live.visir.is/hls-radio/fm957/playlist.m3u8", +# "fm-957": "https://live.visir.is/hls-radio/fm957/playlist.m3u8", +# "Útvarp Saga": "https://stream.utvarpsaga.is/Hljodver", +# "k-100": "https://k100streymi.mbl.is/beint/k100/tracks-v1a1/rewind-3600.m3u8", +# "k 100": "https://k100streymi.mbl.is/beint/k100/tracks-v1a1/rewind-3600.m3u8", +# "k hundrað": "https://k100streymi.mbl.is/beint/k100/tracks-v1a1/rewind-3600.m3u8", +# "Gullbylgjan": "https://live.visir.is/hls-radio/gullbylgjan/playlist.m3u8", +# "x977": "https://live.visir.is/hls-radio/x977/playlist.m3u8", +# "x 977": "https://live.visir.is/hls-radio/x977/playlist.m3u8", +# "x-977": "https://live.visir.is/hls-radio/x977/playlist.m3u8", +# "x-ið": "https://live.visir.is/hls-radio/x977/playlist.m3u8", +# "x-ið 977": "https://live.visir.is/hls-radio/x977/playlist.m3u8", +# "léttbylgjan": "https://live.visir.is/hls-radio/lettbylgjan/playlist.m3u8", +# "retro": "https://k100straumar.mbl.is/retromobile", +# "kiss fm": "http://stream3.radio.is:443/kissfm", +# "flassbakk": "http://stream.radio.is:443/flashback", +# "flassbakk fm": "http://stream.radio.is:443/flashback", +# "útvarp 101": "https://stream.101.live/audio/101/chunklist.m3u8", +# "útvarp hundrað og einn": "https://stream.101.live/audio/101/chunklist.m3u8", +# } import requests @@ -233,6 +233,7 @@ def _get_household_id(self): """ Returns the household id for the given query """ + print("get household id") url = f"https://api.ws.sonos.com/control/api/v1/households" headers = { "Content-Type": "application/json", @@ -406,6 +407,7 @@ def audio_clip(self, audioclip_url): """ Plays an audioclip from link to .mp3 file """ + print("audio_clip") player_id = self._get_player_id() url = f"https://api.ws.sonos.com/control/api/v1/players/{player_id}/audioClip" @@ -427,8 +429,10 @@ def audio_clip(self, audioclip_url): response = post_to_json_api(url, payload, headers) return response - def create_or_join_session(self): - url = f"https://api.ws.sonos.com/control/api/v1/groups/{self._group_id}/playbackSession/joinOrCreate" + def _create_or_join_session(self): + print("_create_or_join_session") + group_id = self._get_group_id() + url = f"https://api.ws.sonos.com/control/api/v1/groups/{group_id}/playbackSession/joinOrCreate" payload = json.dumps({"appId": "com.mideind.embla", "appContext": "embla123"}) headers = { @@ -438,15 +442,12 @@ def create_or_join_session(self): response = post_to_json_api(url, payload, headers) print(response) - session_id = response.sessionId + session_id = response["sessionId"] return session_id - def play_radio_stream(self, query=None): + def play_radio_stream(self, radio_url): + print("play radio stream") session_id = self._create_or_join_session() - if query == None: - radio_name, radio_url = random.choice(list(_RADIO_STEAMS.items())) - else: - radio_name, radio_url = _RADIO_STEAMS.get(query) url = f"https://api.ws.sonos.com/control/api/v1//playbackSessions/{session_id}/playbackSession/loadStreamUrl?" @@ -454,7 +455,7 @@ def play_radio_stream(self, query=None): { "streamUrl": f"{radio_url}", "playOnCompletion": True, - "stationMetadata": {"name": f"{radio_name}"}, + # "stationMetadata": {"name": f"{radio_name}"}, "itemId": "StreamItemId", } ) @@ -465,9 +466,10 @@ def play_radio_stream(self, query=None): response = post_to_json_api(url, payload, headers) - print(response.text) + print(response.get("text")) def increase_volume(self): + print("increase_volume") group_id = self._get_group_id() url = f"https://api.ws.sonos.com/control/api/v1/groups/{group_id}/groupVolume/relative" @@ -479,9 +481,10 @@ def increase_volume(self): response = post_to_json_api(url, payload, headers) - print(response.text) + print(response.get("text")) def decrease_volume(self): + print("decrease volume") group_id = self._get_group_id() url = f"https://api.ws.sonos.com/control/api/v1/groups/{group_id}/groupVolume/relative" @@ -493,7 +496,7 @@ def decrease_volume(self): response = post_to_json_api(url, payload, headers) - print(response.text) + print(response.get("text")) # # TODO: Check whether this should return the ids themselves instead of the json response diff --git a/routes/api.py b/routes/api.py index 3c81dd08..4e73a895 100755 --- a/routes/api.py +++ b/routes/api.py @@ -741,9 +741,11 @@ def sonos_code(version: int = 1) -> Response: # Create an instance of the SonosClient class. This will automatically create the rest of the credentials needed. sonos_client = SonosClient(device_data, client_id) sonos_voice_clip = ( - f"Hæ! Embla hérna. Ég er búin að tengja þennan Sónos hátalara." + f"Hæ! Embla hérna! Ég er búin að tengja þennan Sónos hátalara." ) - sonos_client.audio_clip(text_to_audio_url(sonos_voice_clip)) # Send the above message to the Sonos speaker + sonos_client.audio_clip( + text_to_audio_url(sonos_voice_clip) + ) # Send the above message to the Sonos speaker return better_jsonify(valid=True, msg="Registered sonos code") return better_jsonify(valid=False, errmsg="Error registering sonos code.") From a63266538d670d105758f290d32bcbd6e423c424 Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Tue, 5 Jul 2022 14:05:59 +0000 Subject: [PATCH 165/371] Sonos code cleanup --- queries/iot_speakers.py | 56 +--------------- queries/sonos.py | 143 +--------------------------------------- 2 files changed, 4 insertions(+), 195 deletions(-) diff --git a/queries/iot_speakers.py b/queries/iot_speakers.py index 06ed75a7..fd351649 100644 --- a/queries/iot_speakers.py +++ b/queries/iot_speakers.py @@ -22,6 +22,7 @@ of random numbers, e.g. "Kastaðu tengingi", "Nefndu tölu milli 5 og 10", etc. """ +# Dictionary of radio stations and their stream urls _RADIO_STREAMS = { "Rás 1": "http://netradio.ruv.is/ras1.mp3", "Rás 2": "http://netradio.ruv.is/ras2.mp3", @@ -76,14 +77,6 @@ "spila", ] -# def QIoTSpeakerIncreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: -# result.action = "increase_volume" -# if "hue_obj" not in result: -# result["hue_obj"] = {"on": True, "bri_inc": 64} -# else: -# result["hue_obj"]["bri_inc"] = 64 -# result["hue_obj"]["on"] = True - def help_text(lemma: str) -> str: """Help text to return when query.py is unable to parse a query but @@ -102,7 +95,6 @@ def help_text(lemma: str) -> str: QUERY_NONTERMINALS = {"QIoTSpeaker", "QIoTSpeakerQuery"} # The context-free grammar for the queries recognized by this plug-in module -# GRAMMAR = read_grammar_file("iot_hue") GRAMMAR = read_grammar_file( "iot_speakers", @@ -214,38 +206,8 @@ def QIoTSpeakerK100(node: Node, params: QueryStateDict, result: Result) -> None: result["station"] = "K100" -# def QIoTSpeakerRadioStationName( -# node: Node, params: QueryStateDict, result: Result -# ) -> None: -# result.target = "radio" -# print("radio") - - -# def toggle_play_pause(q): -# device_data = q.client_data("iot_speakers") -# print(device_data) -# if device_data is not None: -# sonos_client = SonosClient(device_data, q.client_id) -# else: -# print("No device data found for this account") -# return -# sonos_client.toggle_play_pause() -# answer = "Ég kveikti á tónlist." -# answer_list = gen_answer(answer) -# answer_list[1].replace("Sonos", "Sónos") -# q.set_answer(*answer_list) - - -# def get_device_data(q): -# device_data = q.client_data("iot_speakers") -# if device_data is not None: -# return device_data -# else: -# print("No device data found for this account") -# return - - def call_sonos_client(sonos_client, result): + """Call the appropriate function in the SonosClient based on the result""" handler_func = _HANDLER_MAP[result.qkey][0] if result.get("station") is not None: radio_url = _RADIO_STREAMS.get(f"{result.station}") @@ -255,6 +217,7 @@ def call_sonos_client(sonos_client, result): return +# Map of query keys to handler functions and the corresponding answer string for Embla _HANDLER_MAP = { "play_music": ["toggle_play_pause", "Ég kveikti á tónlist"], "pause_music": ["toggle_play_pause", "Ég slökkti á tónlist"], @@ -293,16 +256,3 @@ def sentence(state: QueryStateDict, result: Result) -> None: return # # TODO: Need to add check for if there are no registered devices to an account, probably when initilazing the querydata - # if device_data is not None: - # sonos_client = SonosClient(device_data, q.client_id) - # else: - # print("No device data found for this account") - # return - - # # Perform the action on the Sonos device - # if result.action == "play_music": - # sonos_client.toggle_play_pause(q) - # answer = "Ég kveikti á tónlist." - # answer_list = gen_answer(answer) - # answer_list[1].replace("Sonos", "Sónos") - # q.set_answer(*answer_list) diff --git a/queries/sonos.py b/queries/sonos.py index 95de574b..7eb2f175 100644 --- a/queries/sonos.py +++ b/queries/sonos.py @@ -66,34 +66,6 @@ "bóka herbergi": "Library", } -# _RADIO_STEAMS = { -# "rás 1": " http://netradio.ruv.is/ras1.mp3", -# "rás 2": "http://netradio.ruv.is/ras2.mp3", -# "rondo": "http://netradio.ruv.is/rondo.mp3", -# "rondó": "http://netradio.ruv.is/rondo.mp3", -# "bylgjan": "https://live.visir.is/hls-radio/bylgjan/playlist.m3u8", -# "fm 957": "https://live.visir.is/hls-radio/fm957/playlist.m3u8", -# "fm957": "https://live.visir.is/hls-radio/fm957/playlist.m3u8", -# "fm-957": "https://live.visir.is/hls-radio/fm957/playlist.m3u8", -# "Útvarp Saga": "https://stream.utvarpsaga.is/Hljodver", -# "k-100": "https://k100streymi.mbl.is/beint/k100/tracks-v1a1/rewind-3600.m3u8", -# "k 100": "https://k100streymi.mbl.is/beint/k100/tracks-v1a1/rewind-3600.m3u8", -# "k hundrað": "https://k100streymi.mbl.is/beint/k100/tracks-v1a1/rewind-3600.m3u8", -# "Gullbylgjan": "https://live.visir.is/hls-radio/gullbylgjan/playlist.m3u8", -# "x977": "https://live.visir.is/hls-radio/x977/playlist.m3u8", -# "x 977": "https://live.visir.is/hls-radio/x977/playlist.m3u8", -# "x-977": "https://live.visir.is/hls-radio/x977/playlist.m3u8", -# "x-ið": "https://live.visir.is/hls-radio/x977/playlist.m3u8", -# "x-ið 977": "https://live.visir.is/hls-radio/x977/playlist.m3u8", -# "léttbylgjan": "https://live.visir.is/hls-radio/lettbylgjan/playlist.m3u8", -# "retro": "https://k100straumar.mbl.is/retromobile", -# "kiss fm": "http://stream3.radio.is:443/kissfm", -# "flassbakk": "http://stream.radio.is:443/flashback", -# "flassbakk fm": "http://stream.radio.is:443/flashback", -# "útvarp 101": "https://stream.101.live/audio/101/chunklist.m3u8", -# "útvarp hundrað og einn": "https://stream.101.live/audio/101/chunklist.m3u8", -# } - import requests from datetime import datetime, timedelta @@ -241,7 +213,6 @@ def _get_household_id(self): } response = query_json_api(url, headers) - return response["households"][0]["id"] def _get_groups(self): @@ -270,8 +241,7 @@ def get_groups_and_players(self): headers = {"Authorization": f"Bearer {self._access_token}"} response = query_json_api(url, headers) - return response.json() - # return response.json()["groups"] + return response def _get_group_id(self): """ @@ -289,7 +259,6 @@ def _get_group_id(self): } response = query_json_api(url, headers) - return response["groups"][0]["id"] def _get_players(self): @@ -333,12 +302,9 @@ def _get_player_id(self): def _create_sonos_data_dict(self): print("_create_sonos_data_dict") data_dict = {"households": self._households} - # groups_list = [] - # players_list = [] for i in range(len(self._households)): groups_raw = self._groups players_raw = self._players - # groups_list += self._create_grouplist_for_db(groups_raw) groups_list = self._groups players_list = self._players @@ -465,7 +431,6 @@ def play_radio_stream(self, radio_url): } response = post_to_json_api(url, payload, headers) - print(response.get("text")) def increase_volume(self): @@ -480,7 +445,6 @@ def increase_volume(self): } response = post_to_json_api(url, payload, headers) - print(response.get("text")) def decrease_volume(self): @@ -495,109 +459,4 @@ def decrease_volume(self): } response = post_to_json_api(url, payload, headers) - print(response.get("text")) - - -# # TODO: Check whether this should return the ids themselves instead of the json response -# def _get_households(token): -# """ -# Returns the list of households of the user -# """ -# url = f"https://api.ws.sonos.com/control/api/v1/households" - -# payload = {} -# headers = {"Authorization": f"Bearer {token}"} - -# response = requests.request("GET", url, headers=headers, data=payload) - -# return response.json() - - -# def _get_groups(household_id, token): -# """ -# Returns the list of groups of the user -# """ -# url = f"https://api.ws.sonos.com/control/api/v1/households/{household_id}/groups" - -# payload = {} -# headers = {"Authorization": f"Bearer {token}"} - -# response = requests.request("GET", url, headers=headers, data=payload) - -# return response - - -# def _create_token(code, sonos_encoded_credentials, host): -# """ -# Creates a token given a code -# """ -# url = f"https://api.sonos.com/login/v3/oauth/access?grant_type=authorization_code&code={code}&redirect_uri=http://{host}/connect_sonos.api" - -# payload = {} -# headers = { -# "Authorization": f"Basic {sonos_encoded_credentials}", -# "Cookie": "JSESSIONID=F710019AF0A3B7126A8702577C883B5F; AWSELB=69BFEFC914A689BF6DC8E4652748D7B501ED60290D5EA56F2E543ABD7CF357A5F65186AEBCFB059E28075D83A700FD504C030A53CC28683B515BE3DCA3CC587AFAF606E171; AWSELBCORS=69BFEFC914A689BF6DC8E4652748D7B501ED60290D5EA56F2E543ABD7CF357A5F65186AEBCFB059E28075D83A700FD504C030A53CC28683B515BE3DCA3CC587AFAF606E171", -# } - -# response = requests.request("POST", url, headers=headers, data=payload) - -# return response - - -# def refresh_token(sonos_encoded_credentials, refresh_token): -# """ -# Refreshes token -# """ -# url = f"https://api.sonos.com/login/v3/oauth/access?grant_type=refresh_token&refresh_token={refresh_token}" - -# payload = {} -# headers = {"Authorization": f"Basic {sonos_encoded_credentials}"} - -# response = requests.request("POST", url, headers=headers, data=payload) - -# return response - - -# def audio_clip(audioclip_url, player_id, token): -# """ -# Plays an audioclip from link to .mp3 file -# """ -# import requests -# import json - -# url = f"https://api.ws.sonos.com/control/api/v1/players/{player_id}/audioClip" - -# payload = json.dumps( -# { -# "name": "Embla", -# "appId": "com.acme.app", -# "streamUrl": f"{audioclip_url}", -# "volume": 50, -# "priority": "HIGH", -# "clipType": "CUSTOM", -# } -# ) -# headers = { -# "Content-Type": "application/json", -# "Authorization": f"Bearer {token}", -# } - -# response = requests.request("POST", url, headers=headers, data=payload) - - -# def _update_sonos_token(q, device_data): -# print("update sonos token") -# sonos_encoded_credentials = read_api_key("SonosEncodedCredentials") -# refresh_token_str = device_data["sonos"]["credentials"]["refresh_token"] -# access_token = refresh_token(sonos_encoded_credentials, refresh_token_str).json() -# access_token = access_token["access_token"] -# sonos_dict = { -# "sonos": { -# "credentials": { -# "access_token": access_token, -# "timestamp": str(datetime.now()), -# } -# } -# } -# q.set_client_data("iot_speakers", sonos_dict, update_in_place=True) From c3da87bbda17b917575080be1a27d689d05e776b Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Tue, 5 Jul 2022 14:07:48 +0000 Subject: [PATCH 166/371] adding robot-like commands --- queries/grammars/iot_speakers.grammar | 2 ++ queries/iot_hue.py | 1 + 2 files changed, 3 insertions(+) diff --git a/queries/grammars/iot_speakers.grammar b/queries/grammars/iot_speakers.grammar index 647d7d69..d1bbadcf 100644 --- a/queries/grammars/iot_speakers.grammar +++ b/queries/grammars/iot_speakers.grammar @@ -18,6 +18,7 @@ QIoTSpeakerQuery -> | QIoTSpeakerTurnOffVerb QIoTSpeakerTurnOffRest | QIoTSpeakerPlayOrPauseVerb QIoTSpeakerPlayRest | QIoTSpeakerIncreaseOrDecreaseVerb QIoTSpeakerIncreaseOrDecreaseRest + | QIoTSpeakerRadioStationName QIoTSpeakerMakeVerb -> 'gera:so'_bh @@ -74,6 +75,7 @@ QIoTSpeakerMakeRest -> # TODO: Add support for "stilltu rauðan lit á ljósið í eldhúsinu" QIoTSpeakerSetRest -> QIoTSpeakerAHvad QIoTSpeakerHvar? + | "á" "tónlist" # QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigSet # | QCHANGESubject/þf QCHANGEHvernigSet QCHANGEHvar? # | QCHANGEHvar? QCHANGESubject/þf QCHANGEHvernigSet diff --git a/queries/iot_hue.py b/queries/iot_hue.py index a1a46dcb..caf2b9a4 100755 --- a/queries/iot_hue.py +++ b/queries/iot_hue.py @@ -33,6 +33,7 @@ # TODO: No specified location # TODO: Fix scene issues # TODO: Turning on lights without using "turn on" +# TODO: Add functionality for robot-like commands "ljós í eldhúsinu", "rauður í eldhúsinu" from typing import Dict, Mapping, Optional, cast, FrozenSet from typing_extensions import TypedDict From e067da53eb66991a137ffd94afcae32543dddf9b Mon Sep 17 00:00:00 2001 From: Logi E Date: Tue, 5 Jul 2022 15:15:38 +0000 Subject: [PATCH 167/371] added arithmetic and time modules, added update_in_place to client data funcs --- queries/{disabled => }/arithmetic.py | 0 queries/dialogue.py | 70 ++++++++++++++++++---------- queries/{disabled => }/time.py | 0 query.py | 34 ++++++++++++-- 4 files changed, 77 insertions(+), 27 deletions(-) rename queries/{disabled => }/arithmetic.py (100%) rename queries/{disabled => }/time.py (100%) diff --git a/queries/disabled/arithmetic.py b/queries/arithmetic.py similarity index 100% rename from queries/disabled/arithmetic.py rename to queries/arithmetic.py diff --git a/queries/dialogue.py b/queries/dialogue.py index ff9d7acc..af043027 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -28,6 +28,8 @@ from query import Query, ClientDataDict from tree import Result +# TODO: Add timezone info to json encoding/decoding? + # Keys for accessing saved client data for dialogues _DIALOGUE_KEY = "dialogue" _DIALOGUE_NAME_KEY = "dialogue_name" @@ -306,26 +308,6 @@ class DialogueStructureType(TypedDict): extras: Optional[Dict[str, Any]] -def _load_dialogue_structure(filename: str) -> DialogueStructureType: - """Loads dialogue structure from TOML file.""" - basepath, _ = os.path.split(os.path.realpath(__file__)) - fpath = os.path.join(basepath, "dialogues", filename + ".toml") - with open(fpath, mode="r") as file: - f = file.read() - obj: Dict[str, Any] = tomllib.loads(f) # type: ignore - assert _DIALOGUE_NAME_KEY in obj - assert _DIALOGUE_RESOURCES_KEY in obj - resource_dict: Dict[str, Resource] = {} - for resource in obj[_DIALOGUE_RESOURCES_KEY]: - assert "name" in resource - if "type" not in resource: - resource["type"] = "Resource" - # Create instances of Resource classes (and its subclasses) - resource_dict[resource["name"]] = _RESOURCE_TYPES[resource["type"]](**resource) - obj[_DIALOGUE_RESOURCES_KEY] = resource_dict - return cast(DialogueStructureType, obj) - - class DialogueStateManager: def __init__( self, @@ -348,12 +330,33 @@ def __init__( # TODO: Delegate answering from a resource to another resource or to another dialogue # TODO: í ávaxtasamtali "ég vil panta flug" "viltu að ég geymi ávaxtapöntunina eða eyði henni?" ... + def _load_dialogue_structure(self, filename: str) -> DialogueStructureType: + """Loads dialogue structure from TOML file.""" + basepath, _ = os.path.split(os.path.realpath(__file__)) + fpath = os.path.join(basepath, "dialogues", filename + ".toml") + with open(fpath, mode="r") as file: + f = file.read() + obj: Dict[str, Any] = tomllib.loads(f) # type: ignore + assert _DIALOGUE_RESOURCES_KEY in obj + resource_dict: Dict[str, Resource] = {} + for resource in obj[_DIALOGUE_RESOURCES_KEY]: + assert "name" in resource + if "type" not in resource: + resource["type"] = "Resource" + # Create instances of Resource classes (and its subclasses) + resource_dict[resource["name"]] = _RESOURCE_TYPES[resource["type"]]( + **resource + ) + obj[_DIALOGUE_RESOURCES_KEY] = resource_dict + return cast(DialogueStructureType, obj) + def not_in_dialogue(self) -> bool: """Check if the client is in or wants to start this dialogue""" return ( self._result.get("qtype") != self._start_qtype and self._saved_state.get(_DIALOGUE_NAME_KEY) != self._dialogue_name ) + # TODO: Add check for newest dialogue def setup_dialogue(self, answering_functions: AnsweringFunctionMap) -> None: """ @@ -361,7 +364,7 @@ def setup_dialogue(self, answering_functions: AnsweringFunctionMap) -> None: Should be called after initializing an instance of DialogueStateManager and before calling get_answer. """ - obj = _load_dialogue_structure(self._dialogue_name) + obj = self._load_dialogue_structure(self._dialogue_name) for rname, resource in obj[_DIALOGUE_RESOURCES_KEY].items(): if rname in self._saved_state[_DIALOGUE_RESOURCES_KEY]: # Update empty resource with serialized data @@ -492,8 +495,12 @@ def _set_dialogue_state(self, ds: DialogueStructureType) -> None: ds_json: str = json.dumps(ds, cls=DialogueJSONEncoder) # Wrap data before saving dialogue state into client data # (due to custom JSON serialization) - cd = {self._dialogue_name: ds_json} - self._q.set_client_data(_DIALOGUE_KEY, cast(ClientDataDict, cd)) + # TODO: add datetime stuff + self._q.set_client_data( + _DIALOGUE_KEY, + cast(ClientDataDict, cd), + update_in_place=True, + ) def set_resource_state(self, resource_name: str, state: ResourceState): """ @@ -541,7 +548,9 @@ def end_dialogue(self) -> None: # TODO: Doesn't allow multiple conversations at once # (set_client_data overwrites other conversations) self._q.set_client_data( - _DIALOGUE_KEY, {self._dialogue_name: _EMPTY_DIALOGUE_DATA} + _DIALOGUE_KEY, + {self._dialogue_name: _EMPTY_DIALOGUE_DATA}, + update_in_place=True, ) def set_error(self) -> None: @@ -593,6 +602,17 @@ def default(self, o: Any) -> Any: "second": o.second, "microsecond": o.microsecond, } + if isinstance(o, datetime.datetime): + return { + "__type__": "datetime", + "year": o.year, + "month": o.month, + "day": o.day, + "hour": o.hour, + "minute": o.minute, + "second": o.second, + "microsecond": o.microsecond, + } return json.JSONEncoder.default(self, o) @@ -610,4 +630,6 @@ def dialogue_decoding(self, d: Dict[Any, Any]) -> Any: return datetime.date(**d) if t == "time": return datetime.time(**d) + if t == "datetime": + return datetime.datetime(**d) return _RESOURCE_TYPES[t](**d) diff --git a/queries/disabled/time.py b/queries/time.py similarity index 100% rename from queries/disabled/time.py rename to queries/time.py diff --git a/query.py b/query.py index 7d41bb3c..7172ab51 100755 --- a/query.py +++ b/query.py @@ -51,6 +51,7 @@ import json import re import random +from copy import deepcopy from collections import defaultdict from settings import Settings @@ -309,6 +310,24 @@ def process_queries( return False +def _merge_two_dicts(dict_a: Dict[str, Any], dict_b: Dict[str, Any]) -> Dict[str, Any]: + """ + Recursively merge two dicts, + updating dict_a with values from dict_b, + leaving other values intact. + """ + for key in dict_b: + if ( + key in dict_a + and isinstance(dict_a[key], dict) + and isinstance(dict_b[key], dict) + ): + _merge_two_dicts(dict_a[key], dict_b[key]) + else: + dict_a[key] = dict_b[key] + return dict_a + + class Query: """A Query is initialized by parsing a query string using QueryRoot as the @@ -885,15 +904,21 @@ def client_data(self, key: str) -> Optional[ClientDataDict]: ) return None - def set_client_data(self, key: str, data: ClientDataDict) -> None: + def set_client_data( + self, key: str, data: ClientDataDict, *, update_in_place: bool = False + ) -> None: """Setter for client query data""" if not self.client_id or not key: logging.warning("Couldn't save query data, no client ID or key") return - Query.store_query_data(self.client_id, key, data) + Query.store_query_data( + self.client_id, key, data, update_in_place=update_in_place + ) @staticmethod - def store_query_data(client_id: str, key: str, data: ClientDataDict) -> bool: + def store_query_data( + client_id: str, key: str, data: ClientDataDict, *, update_in_place: bool = False + ) -> bool: """Save client query data in the database, under the given key""" if not client_id or not key: return False @@ -916,6 +941,9 @@ def store_query_data(client_id: str, key: str, data: ClientDataDict) -> bool: ) session.add(row) else: + if update_in_place: + stored_data = deepcopy(row.data) + data = _merge_two_dicts(stored_data, data) # Already present: update row.data = data # type: ignore row.modified = now # type: ignore From 50436272b3962c753e0a16916e75cde960fa3e3c Mon Sep 17 00:00:00 2001 From: Logi E Date: Tue, 5 Jul 2022 15:17:17 +0000 Subject: [PATCH 168/371] fixed cd wrap --- queries/dialogue.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index af043027..c44a79cd 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -498,7 +498,7 @@ def _set_dialogue_state(self, ds: DialogueStructureType) -> None: # TODO: add datetime stuff self._q.set_client_data( _DIALOGUE_KEY, - cast(ClientDataDict, cd), + cast(ClientDataDict, ds_json), update_in_place=True, ) From d2b8591e58e644ad66aa63bb618dde5fc0aacff3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Tue, 5 Jul 2022 15:19:09 +0000 Subject: [PATCH 169/371] Removed multiple_times_for_date from result and added a partially_fulfilled check to the datetime answer instead --- queries/theater_module.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/queries/theater_module.py b/queries/theater_module.py index 63526acc..4e95fe89 100644 --- a/queries/theater_module.py +++ b/queries/theater_module.py @@ -212,6 +212,7 @@ def help_text(lemma: str) -> str: QTheaterShowSeats → QTheaterEgVil? "sæti"? "númer"? QNum "til"? QNum? + | QTheaterEgVil? "sæti"? "númer"? QNum "og"? QNum? QTheaterDateOptions → "hvaða" "dagsetningar" "eru" "í" "boði" @@ -425,7 +426,7 @@ def _generate_date_answer( return gen_answer(resource.prompts["no_time_matched"]) if result.get("many_matching_times"): return gen_answer(resource.prompts["many_matching_times"]) - if result.get("multiple_times_for_date"): + if resource.is_partially_fulfilled: show_date: Optional[datetime.date] = cast( DateResource, dsm.get_resource("ShowDate") ).date @@ -662,7 +663,6 @@ def _generate_final_answer( time = cast(TimeResource, dsm.get_resource("ShowTime")).data number_of_seats = cast(NumberResource, dsm.get_resource("ShowSeatCount")).data seats = dsm.get_resource("ShowSeatNumber").data - seat_string: str = "" seat_voice_string: str = "" seats_text_string: str = "" if number_of_seats > 1: @@ -764,7 +764,6 @@ def _date_callback( dsm.set_resource_state(time_resource.name, ResourceState.FULFILLED) dsm.set_resource_state(datetime_resource.name, ResourceState.FULFILLED) else: - result.multiple_times_for_date = True dsm.set_resource_state( datetime_resource.name, ResourceState.PARTIALLY_FULFILLED ) @@ -776,8 +775,6 @@ def _time_callback( dsm.set_resource_state(resource.name, ResourceState.UNFULFILLED) if result.get("no_date_matched"): return - if result.get("multiple_times_for_date"): - result.multiple_times_for_date = False if dsm.get_resource("Show").is_confirmed: show_title: str = dsm.get_resource("Show").data[0] date_resource: DateResource = cast(DateResource, dsm.get_resource("ShowDate")) From d3482bf71aa321fee1b1ece3bafef4687fee31eb Mon Sep 17 00:00:00 2001 From: Logi E Date: Tue, 5 Jul 2022 15:25:06 +0000 Subject: [PATCH 170/371] fixed cd wrapper again --- queries/dialogue.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index c44a79cd..ba7530b6 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -495,10 +495,11 @@ def _set_dialogue_state(self, ds: DialogueStructureType) -> None: ds_json: str = json.dumps(ds, cls=DialogueJSONEncoder) # Wrap data before saving dialogue state into client data # (due to custom JSON serialization) + cd = {self._dialogue_name: ds_json} # TODO: add datetime stuff self._q.set_client_data( _DIALOGUE_KEY, - cast(ClientDataDict, ds_json), + cast(ClientDataDict, cd), update_in_place=True, ) From eb7015ed2b5b45432c4dc4f0279609861abd3d15 Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Tue, 5 Jul 2022 16:28:28 +0000 Subject: [PATCH 171/371] Updated radio station list in IoTSpeakers --- queries/iot_speakers.py | 58 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 55 insertions(+), 3 deletions(-) diff --git a/queries/iot_speakers.py b/queries/iot_speakers.py index fd351649..3507246f 100644 --- a/queries/iot_speakers.py +++ b/queries/iot_speakers.py @@ -28,16 +28,24 @@ "Rás 2": "http://netradio.ruv.is/ras2.mp3", "Rondó": "http://netradio.ruv.is/rondo.mp3", "Bylgjan": "https://live.visir.is/hls-radio/bylgjan/playlist.m3u8", + "Léttbylgjan": "https://live.visir.is/hls-radio/lettbylgjan/playlist.m3u8", + "Gullbylgjan": "https://live.visir.is/hls-radio/gullbylgjan/playlist.m3u8", + "80s Bylgjan": "https://live.visir.is/hls-radio/80s/chunklist_DVR.m3u8", + "Íslenska Bylgjan": "https://live.visir.is/hls-radio/islenska/chunklist_DVR.m3u8", "FM957": "https://live.visir.is/hls-radio/fm957/playlist.m3u8", "Útvarp Saga": "https://stream.utvarpsaga.is/Hljodver", "K100": "https://k100streymi.mbl.is/beint/k100/tracks-v1a1/rewind-3600.m3u8", - "Gullbylgjan": "https://live.visir.is/hls-radio/gullbylgjan/playlist.m3u8", "X977": "https://live.visir.is/hls-radio/x977/playlist.m3u8", - "Léttbylgjan": "https://live.visir.is/hls-radio/lettbylgjan/playlist.m3u8", "Retro": "https://k100straumar.mbl.is/retromobile", "KissFM": "http://stream3.radio.is:443/kissfm", - "Flashback": "http://stream.radio.is:443/flashback", "Útvarp 101": "https://stream.101.live/audio/101/chunklist.m3u8", + "Apparatið": "https://live.visir.is/hls-radio/apparatid/chunklist_DVR.m3u8", + "FM Extra": "https://live.visir.is/hls-radio/fmextra/chunklist_DVR.m3u8", + "Útvarp Suðurland": "http://ice-11.spilarinn.is/tsudurlandfm", + "Flashback": "http://stream.radio.is:443/flashback", + "70s Flashback": "http://stream3.radio.is:443/70flashback", + "80s Flashback": "http://stream3.radio.is:443/80flashback", + "90s Flashback": "http://stream3.radio.is:443/90flashback", } @@ -206,6 +214,50 @@ def QIoTSpeakerK100(node: Node, params: QueryStateDict, result: Result) -> None: result["station"] = "K100" +def QIoTSpeakerIslenskaBylgjan( + node: Node, params: QueryStateDict, result: Result +) -> None: + result["qkey"] = "radio" + result["station"] = "Íslenska Bylgjan" + + +def QIoT80sBylgjan(node: Node, params: QueryStateDict, result: Result) -> None: + result["qkey"] = "radio" + result["station"] = "80s Bylgjan" + + +def QIoTSpeakerApparatid(node: Node, params: QueryStateDict, result: Result) -> None: + result["qkey"] = "radio" + result["station"] = "Apparatið" + + +def QIoTSpeakerFmExtra(node: Node, params: QueryStateDict, result: Result) -> None: + result["qkey"] = "radio" + result["station"] = "FM Extra" + + +def QIoTSpeaker70sFlashback(node: Node, params: QueryStateDict, result: Result) -> None: + result["qkey"] = "radio" + result["station"] = "70s Flashback" + + +def QIoTSpeaker80sFlashback(node: Node, params: QueryStateDict, result: Result) -> None: + result["qkey"] = "radio" + result["station"] = "80s Flashback" + + +def QIoTSpeaker90sFlashback(node: Node, params: QueryStateDict, result: Result) -> None: + result["qkey"] = "radio" + result["station"] = "90s Flashback" + + +def QIoTSpeakerUtvarpSudurland( + node: Node, params: QueryStateDict, result: Result +) -> None: + result["qkey"] = "radio" + result["station"] = "Útvarp Suðurland" + + def call_sonos_client(sonos_client, result): """Call the appropriate function in the SonosClient based on the result""" handler_func = _HANDLER_MAP[result.qkey][0] From 8d7ee2d5a9156c5d189cad13ca812e2a0c69068e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Tue, 5 Jul 2022 16:36:36 +0000 Subject: [PATCH 172/371] Fixed bug where if one seat was selected it would not find the second number and cause an error --- queries/theater_module.py | 53 ++++++++++++++++++++++++++++----------- 1 file changed, 39 insertions(+), 14 deletions(-) diff --git a/queries/theater_module.py b/queries/theater_module.py index 4e95fe89..54c51cc6 100644 --- a/queries/theater_module.py +++ b/queries/theater_module.py @@ -258,6 +258,7 @@ def help_text(lemma: str) -> str: | "leikhús" "miða" "pöntunina" | "leikhús" "miða" "pöntuninni" + """ @@ -609,9 +610,12 @@ def _generate_seat_number_answer( ) return (dict(answer=text_ans), text_ans, voice_ans) if result.get("wrong_number_seats_selected"): - chosen_seats = len( - range(result.get("numbers")[0], result.get("numbers")[1] + 1) - ) + if len(result.get("numbers")) > 1: + chosen_seats = len( + range(result.get("numbers")[0], result.get("numbers")[1] + 1) + ) + else: + chosen_seats = 1 ans = resource.prompts["wrong_number_seats_selected"] text_ans = ans.format(chosen_seats=chosen_seats, seats=seats) voice_ans = ans.format( @@ -842,8 +846,12 @@ def QTheaterDateTime(node: Node, params: QueryStateDict, result: Result) -> None result["show_time"] = datetime.time(h, min) result["show_date"] = datetime.date(y, m, d) - DialogueStateManager.add_callback(result, lambda r: r.name == "ShowDate", _date_callback) - DialogueStateManager.add_callback(result, lambda r: r.name == "ShowTime", _time_callback) + DialogueStateManager.add_callback( + result, lambda r: r.name == "ShowDate", _date_callback + ) + DialogueStateManager.add_callback( + result, lambda r: r.name == "ShowTime", _time_callback + ) def QTheaterDate(node: Node, params: QueryStateDict, result: Result) -> None: @@ -863,7 +871,9 @@ def QTheaterDate(node: Node, params: QueryStateDict, result: Result) -> None: y += 1 result["show_date"] = datetime.date(day=d, month=m, year=y) - DialogueStateManager.add_callback(result, lambda r: r.name == "ShowDate", _date_callback) + DialogueStateManager.add_callback( + result, lambda r: r.name == "ShowDate", _date_callback + ) return raise ValueError("No date in {0}".format(str(datenode))) @@ -880,7 +890,9 @@ def QTheaterTime(node: Node, params: QueryStateDict, result: Result) -> None: result["show_time"] = datetime.time(hour, minute) - DialogueStateManager.add_callback(result, lambda r: r.name == "ShowTime", _time_callback) + DialogueStateManager.add_callback( + result, lambda r: r.name == "ShowTime", _time_callback + ) def QTheaterMoreDates(node: Node, params: QueryStateDict, result: Result) -> None: @@ -893,7 +905,9 @@ def _next_dates( else: extras["page_index"] = 3 - DialogueStateManager.add_callback(result, lambda r: r.name == "ShowDate", _next_dates) + DialogueStateManager.add_callback( + result, lambda r: r.name == "ShowDate", _next_dates + ) def QTheaterPreviousDates(node: Node, params: QueryStateDict, result: Result) -> None: @@ -906,7 +920,9 @@ def _prev_dates( else: extras["page_index"] = 0 - DialogueStateManager.add_callback(result, lambda r: r.name == "ShowDate", _prev_dates) + DialogueStateManager.add_callback( + result, lambda r: r.name == "ShowDate", _prev_dates + ) def QTheaterShowSeatCountQuery( @@ -922,7 +938,9 @@ def _add_seat_number( else: result.invalid_seat_count = True - DialogueStateManager.add_callback(result, lambda r: r.name == "ShowSeatCount", _add_seat_number) + DialogueStateManager.add_callback( + result, lambda r: r.name == "ShowSeatCount", _add_seat_number + ) def QTheaterShowRow(node: Node, params: QueryStateDict, result: Result) -> None: @@ -953,7 +971,10 @@ def _add_row( dsm.set_resource_state(resource.name, ResourceState.UNFULFILLED) result.no_row_matched = True - DialogueStateManager.add_callback(result, lambda r: r.name == "ShowSeatRow", _add_row) + DialogueStateManager.add_callback( + result, lambda r: r.name == "ShowSeatRow", _add_row + ) + def QTheaterShowSeats(node: Node, params: QueryStateDict, result: Result) -> None: def _add_seats( @@ -964,7 +985,7 @@ def _add_seats( row: int = dsm.get_resource("ShowSeatRow").data[0] number_of_seats: int = dsm.get_resource("ShowSeatCount").data selected_seats: list[int] = [] - if number_of_seats > 1: + if len(result.numbers) > 1: selected_seats = [ seat for seat in range(result.numbers[0], result.numbers[1] + 1) ] @@ -994,7 +1015,9 @@ def _add_seats( if len(resource.data) > 0: dsm.set_resource_state(resource.name, ResourceState.FULFILLED) - DialogueStateManager.add_callback(result, lambda r: r.name == "ShowSeatNumber", _add_seats) + DialogueStateManager.add_callback( + result, lambda r: r.name == "ShowSeatNumber", _add_seats + ) def QTheaterGeneralOptions(node: Node, params: QueryStateDict, result: Result) -> None: @@ -1041,7 +1064,9 @@ def _cancel_order( dsm.end_dialogue() result.qtype = "QCancel" - DialogueStateManager.add_callback(result, lambda r: r.name == "Final", _cancel_order) + DialogueStateManager.add_callback( + result, lambda r: r.name == "Final", _cancel_order + ) def QYes(node: Node, params: QueryStateDict, result: Result): From c7f2bf839aa67831364bcb075fadf6dfe7bdeeaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Wed, 6 Jul 2022 13:03:03 +0000 Subject: [PATCH 173/371] Added resource graph to be able to identify parents and children of a resource, also made resources hashable --- queries/dialogue.py | 53 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 42 insertions(+), 11 deletions(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index ba7530b6..10aca959 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -84,7 +84,7 @@ class ResourceState(IntFlag): ########################## -@dataclass +@dataclass(eq=False) class Resource: """ Base class representing a dialogue resource. @@ -146,8 +146,14 @@ def format_data(self, format_func: Optional[Callable[[Any], str]] = None) -> str """ return format_func(self.data) if format_func else self.data + def __hash__(self) -> int: + return hash(self.name) -@dataclass + def __eq__(self, other: object) -> bool: + return isinstance(other, Resource) and self.name == other.name + + +@dataclass(eq=False) class ListResource(Resource): """Resource representing a list of items.""" @@ -167,7 +173,7 @@ def format_data(self, format_func: Optional[Callable[[Any], str]] = None) -> str # ... -@dataclass +@dataclass(eq=False) class YesNoResource(Resource): """Resource representing a yes/no answer.""" @@ -187,7 +193,7 @@ def format_data(self, format_func: Optional[Callable[[Any], str]] = None) -> str return "já" if self.data else "nei" -@dataclass +@dataclass(eq=False) class ConfirmResource(YesNoResource): """Resource representing a confirmation of other resources.""" @@ -211,7 +217,7 @@ def _confirm_children( req_res.state = ResourceState.CONFIRMED -@dataclass +@dataclass(eq=False) class DateResource(Resource): """Resource representing a date.""" @@ -230,7 +236,7 @@ def format_data(self, format_func: Optional[Callable[[Any], str]] = None) -> str return self.data.strftime("%x") -@dataclass +@dataclass(eq=False) class TimeResource(Resource): """Resource representing a time (00:00-23:59).""" @@ -249,31 +255,31 @@ def format_data(self, format_func: Optional[Callable[[Any], str]] = None) -> str return self.data.strftime("%X") -@dataclass +@dataclass(eq=False) class DatetimeResource(Resource): """Resource for wrapping date and time resources.""" ... -@dataclass +@dataclass(eq=False) class NumberResource(Resource): """Resource representing a number.""" data: int = 0 -@dataclass +@dataclass(eq=False) class OrResource(Resource): exclusive: bool = False # Only one of the resources should be fulfilled -@dataclass +@dataclass(eq=False) class AndResource(Resource): # For answering multiple resources at the same time ... -@dataclass +@dataclass(eq=False) class FinalResource(Resource): """Resource representing the final state of a dialogue.""" @@ -296,6 +302,14 @@ class FinalResource(Resource): ################################ +class ResourceGraphItem(TypedDict): + children: List[Resource] + parents: List[Resource] + + +ResourceGraph = Dict[Resource, ResourceGraphItem] + + class DialogueStructureType(TypedDict): """ Representation of the dialogue structure, @@ -327,9 +341,24 @@ def __init__( self._answer_tuple: Optional[AnswerTuple] = None self._error: bool = False self._extras: Dict[str, Any] = {} + self._resource_graph: ResourceGraph = {} # TODO: Delegate answering from a resource to another resource or to another dialogue # TODO: í ávaxtasamtali "ég vil panta flug" "viltu að ég geymi ávaxtapöntunina eða eyði henni?" ... + def _initialize_resource_graph(self) -> None: + """ + Initializes the resource graph with each + resource having children and parents according + to what each resource requires. + """ + for resource in self._resources.values(): + self._resource_graph[resource] = {"children": [], "parents": []} + + for resource in self._resources.values(): + for req in resource.requires: + self._resource_graph[self._resources[req]]["parents"].append(resource) + self._resource_graph[resource]["children"].append(self._resources[req]) + def _load_dialogue_structure(self, filename: str) -> DialogueStructureType: """Loads dialogue structure from TOML file.""" basepath, _ = os.path.split(os.path.realpath(__file__)) @@ -382,6 +411,8 @@ def setup_dialogue(self, answering_functions: AnsweringFunctionMap) -> None: # (in order to resume dialogue upon next query) self._start_dialogue() + self._initialize_resource_graph() + def _start_dialogue(self): """Save client's state as having started this dialogue""" # New empty dialogue state, with correct dialogue name From aa4ba341230c971352e51d021d4c14b84eeb6f7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Wed, 6 Jul 2022 13:29:40 +0000 Subject: [PATCH 174/371] Changed _find_parent_resources to be recursive and utilize _resource_graph --- queries/dialogue.py | 37 +++++++++++++------------------------ 1 file changed, 13 insertions(+), 24 deletions(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index 10aca959..452e6b15 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -548,32 +548,21 @@ def set_resource_state(self, resource_name: str, state: ResourceState): if resource.cascade_state and lowered_state: # Find all parent resources and set to corresponding state print("SEARCHING FOR PARENTS") - parent_names = self._find_parent_resources(resource_name) - print("PARENTS FOUND:", parent_names) - if parent_names: - for parent in parent_names: - self._resources[parent].state = ResourceState.UNFULFILLED + parents = self._find_parent_resources(self._resources[resource_name]) + print("PARENTS FOUND:", parents) + for parent in parents: + parent.state = ResourceState.UNFULFILLED - def _find_parent_resources(self, resource_name: str) -> Optional[Set[str]]: + def _find_parent_resources(self, resource: Resource) -> Set[Resource]: """Find all parent resources of a resource""" - # TODO: FIX ME, UGLY - all_parents: Set[str] = set() - ap_len: int - i = 0 - print("LENGTH OF RESOURCES:", len(self._resources)) - while i < len(self._resources): - print(list(self._resources.values())) - ap_len = len(all_parents) - for rname, resource in self._resources.items(): - if resource_name in resource.requires and rname not in all_parents: - all_parents.add(rname) - for r2name in all_parents.copy(): - if r2name in resource.requires and rname not in all_parents: - all_parents.add(rname) - # If no new parent resources added to ps, return all_parents - if len(all_parents) == ap_len: - return all_parents - i += 1 + all_parents: Set[Resource] = set() + resource_parents: list[Resource] = self._resource_graph[resource]["parents"] + if len(resource_parents) > 0: + for parent in resource_parents: + if parent not in all_parents: + all_parents.add(parent) + all_parents.update(self._find_parent_resources(parent)) + return all_parents def end_dialogue(self) -> None: """End the client's current dialogue""" From deb8a816a7636cf4a9d9178a8b0129eacd90f60c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Wed, 6 Jul 2022 13:42:55 +0000 Subject: [PATCH 175/371] Changed _execute_callbacks_postorder and _get_answer_postorder to utilize _resource_graph --- queries/dialogue.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index 452e6b15..7904754b 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -479,12 +479,12 @@ def get_answer(self) -> Optional[AnswerTuple]: return self._answer_tuple def _get_answer_postorder( - self, curr_resource: Resource, finished: Set[str] + self, curr_resource: Resource, finished: Set[Resource] ) -> Optional[AnswerTuple]: - for rname in curr_resource.requires: - if rname not in finished: - finished.add(rname) - ans = self._get_answer_postorder(self._resources[rname], finished) + for resource in self._resource_graph[curr_resource]["children"]: + if resource not in finished: + finished.add(resource) + ans = self._get_answer_postorder(resource, finished) if ans: return ans if curr_resource.name in self._answering_functions: @@ -492,12 +492,15 @@ def _get_answer_postorder( return None def _execute_callbacks_postorder( - self, curr_resource: Resource, cbs: List[_CallbackTupleType], finished: Set[str] + self, + curr_resource: Resource, + cbs: List[_CallbackTupleType], + finished: Set[Resource], ) -> None: - for rname in curr_resource.requires: - if rname not in finished: - finished.add(rname) - self._execute_callbacks_postorder(self._resources[rname], cbs, finished) + for resource in self._resource_graph[curr_resource]["children"]: + if resource not in finished: + finished.add(resource) + self._execute_callbacks_postorder(resource, cbs, finished) for filter_func, cb in cbs: if filter_func(curr_resource): From 9577afb74b2d4334562982bfb5783bf85b1a3771 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Wed, 6 Jul 2022 14:41:12 +0000 Subject: [PATCH 176/371] Added current resource in dsm and _find_current_resource --- queries/dialogue.py | 60 ++++++++++++++++++++++++++-------- queries/dialogues/theater.toml | 3 +- 2 files changed, 49 insertions(+), 14 deletions(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index 7904754b..c8c72bfa 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -36,6 +36,7 @@ _DIALOGUE_RESOURCES_KEY = "resources" _DIALOGUE_LAST_INTERACTED_WITH_KEY = "last_interacted_with" _DIALOGUE_EXTRAS_KEY = "extras" +_DIALOGUE_INITIAL_RESOURCE_KEY = "initial_resource" _EMPTY_DIALOGUE_DATA = "{}" _FINAL_RESOURCE_NAME = "Final" _CALLBACK_LOCATION = "callbacks" @@ -84,7 +85,7 @@ class ResourceState(IntFlag): ########################## -@dataclass(eq=False) +@dataclass(eq=False, repr=False) class Resource: """ Base class representing a dialogue resource. @@ -152,8 +153,14 @@ def __hash__(self) -> int: def __eq__(self, other: object) -> bool: return isinstance(other, Resource) and self.name == other.name + def __repr__(self) -> str: + return f"<{self.name}>" -@dataclass(eq=False) + def __str__(self) -> str: + return f"<{self.name}>" + + +@dataclass(eq=False, repr=False) class ListResource(Resource): """Resource representing a list of items.""" @@ -173,7 +180,7 @@ def format_data(self, format_func: Optional[Callable[[Any], str]] = None) -> str # ... -@dataclass(eq=False) +@dataclass(eq=False, repr=False) class YesNoResource(Resource): """Resource representing a yes/no answer.""" @@ -193,7 +200,7 @@ def format_data(self, format_func: Optional[Callable[[Any], str]] = None) -> str return "já" if self.data else "nei" -@dataclass(eq=False) +@dataclass(eq=False, repr=False) class ConfirmResource(YesNoResource): """Resource representing a confirmation of other resources.""" @@ -217,7 +224,7 @@ def _confirm_children( req_res.state = ResourceState.CONFIRMED -@dataclass(eq=False) +@dataclass(eq=False, repr=False) class DateResource(Resource): """Resource representing a date.""" @@ -236,7 +243,7 @@ def format_data(self, format_func: Optional[Callable[[Any], str]] = None) -> str return self.data.strftime("%x") -@dataclass(eq=False) +@dataclass(eq=False, repr=False) class TimeResource(Resource): """Resource representing a time (00:00-23:59).""" @@ -255,31 +262,31 @@ def format_data(self, format_func: Optional[Callable[[Any], str]] = None) -> str return self.data.strftime("%X") -@dataclass(eq=False) +@dataclass(eq=False, repr=False) class DatetimeResource(Resource): """Resource for wrapping date and time resources.""" ... -@dataclass(eq=False) +@dataclass(eq=False, repr=False) class NumberResource(Resource): """Resource representing a number.""" data: int = 0 -@dataclass(eq=False) +@dataclass(eq=False, repr=False) class OrResource(Resource): exclusive: bool = False # Only one of the resources should be fulfilled -@dataclass(eq=False) -class AndResource(Resource): # For answering multiple resources at the same time +@dataclass(eq=False, repr=False) # Wrapper when multiple resources are required +class WrapperResource(Resource): ... -@dataclass(eq=False) +@dataclass(eq=False, repr=False) class FinalResource(Resource): """Resource representing the final state of a dialogue.""" @@ -295,6 +302,8 @@ class FinalResource(Resource): "DatetimeResource": DatetimeResource, "NumberResource": NumberResource, "FinalResource": FinalResource, + "WrapperResource": WrapperResource, + "OrResource": OrResource, } ################################ @@ -310,13 +319,14 @@ class ResourceGraphItem(TypedDict): ResourceGraph = Dict[Resource, ResourceGraphItem] -class DialogueStructureType(TypedDict): +class DialogueStructureType(TypedDict, total=False): """ Representation of the dialogue structure, as it is read from the TOML files and saved to the database. """ dialogue_name: str + initial_resource: str resources: Dict[str, Resource] last_interacted_with: Optional[datetime.datetime] extras: Optional[Dict[str, Any]] @@ -342,6 +352,7 @@ def __init__( self._error: bool = False self._extras: Dict[str, Any] = {} self._resource_graph: ResourceGraph = {} + self._current_resource: Optional[Resource] = None # TODO: Delegate answering from a resource to another resource or to another dialogue # TODO: í ávaxtasamtali "ég vil panta flug" "viltu að ég geymi ávaxtapöntunina eða eyði henni?" ... @@ -358,6 +369,7 @@ def _initialize_resource_graph(self) -> None: for req in resource.requires: self._resource_graph[self._resources[req]]["parents"].append(resource) self._resource_graph[resource]["children"].append(self._resources[req]) + print(self._resource_graph) def _load_dialogue_structure(self, filename: str) -> DialogueStructureType: """Loads dialogue structure from TOML file.""" @@ -394,6 +406,7 @@ def setup_dialogue(self, answering_functions: AnsweringFunctionMap) -> None: DialogueStateManager and before calling get_answer. """ obj = self._load_dialogue_structure(self._dialogue_name) + # TODO: fix type hints for rname, resource in obj[_DIALOGUE_RESOURCES_KEY].items(): if rname in self._saved_state[_DIALOGUE_RESOURCES_KEY]: # Update empty resource with serialized data @@ -411,6 +424,8 @@ def setup_dialogue(self, answering_functions: AnsweringFunctionMap) -> None: # (in order to resume dialogue upon next query) self._start_dialogue() + assert _DIALOGUE_INITIAL_RESOURCE_KEY in obj + self._initial_resource = self._resources[obj[_DIALOGUE_INITIAL_RESOURCE_KEY]] self._initialize_resource_graph() def _start_dialogue(self): @@ -466,6 +481,8 @@ def get_answer(self) -> Optional[AnswerTuple]: raise ValueError("No answer for cancelled dialogue") return self._answer_tuple + self._current_resource = self._find_current_resource() + # Iterate through resources (inorder traversal) # until one generates an answer self._answer_tuple = self._get_answer_postorder(curr_resource, set()) @@ -567,6 +584,23 @@ def _find_parent_resources(self, resource: Resource) -> Set[Resource]: all_parents.update(self._find_parent_resources(parent)) return all_parents + def _find_current_resource(self) -> Resource: + """ + Finds the current resource in the resource graph. + """ + curr_res: Resource = self._initial_resource + while curr_res.is_confirmed: + for parent in self._resource_graph[curr_res]["parents"]: + curr_res = parent + grandparents = self._resource_graph[parent]["parents"] + if len(grandparents) == 1 and isinstance( + grandparents[0], WrapperResource + ): + curr_res = grandparents[0] + break + print("CURRENT RESOURCE:", curr_res) + return curr_res + def end_dialogue(self) -> None: """End the client's current dialogue""" # TODO: Doesn't allow multiple conversations at once diff --git a/queries/dialogues/theater.toml b/queries/dialogues/theater.toml index a2801532..f219cd67 100644 --- a/queries/dialogues/theater.toml +++ b/queries/dialogues/theater.toml @@ -1,4 +1,5 @@ dialogue_name = "theater" +initial_resource = "Show" [[resources]] name = "Show" @@ -23,7 +24,7 @@ requires = ["Show"] [[resources]] name = "ShowDateTime" -type = "ListResource" +type = "WrapperResource" requires = ["ShowDate", "ShowTime"] cascade_state = true prompts.initial = "Hvenær viltu fara á sýninguna {show}? Í boði eru {date_number} dagsetningar.\n{dates}" From bfa4d6b5e388ac4be5939b6e135704739b5df1a6 Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Wed, 6 Jul 2022 15:49:32 +0000 Subject: [PATCH 177/371] SonosClient HUGE update, data not used in db anymore SonosClient HUGE update, data not used in db anymore --- queries/grammars/iot_speakers.grammar | 1 + queries/iot_hue.py | 1 + queries/iot_speakers.py | 37 ++- queries/sonos.py | 354 ++++++++++++++++---------- routes/api.py | 9 +- 5 files changed, 254 insertions(+), 148 deletions(-) diff --git a/queries/grammars/iot_speakers.grammar b/queries/grammars/iot_speakers.grammar index e2e9abe6..1a37923b 100644 --- a/queries/grammars/iot_speakers.grammar +++ b/queries/grammars/iot_speakers.grammar @@ -196,6 +196,7 @@ QIoTSpeakerLocationPrepositionSecondPart -> QIoTSpeakerGroupName/fall -> no/fall + | sérnafn/fall QIoTSpeakerBeOrBecome -> QIoTSpeakerBe diff --git a/queries/iot_hue.py b/queries/iot_hue.py index 865adcec..3f6a55f7 100755 --- a/queries/iot_hue.py +++ b/queries/iot_hue.py @@ -292,6 +292,7 @@ def QIoTLightName(node: Node, params: QueryStateDict, result: Result) -> None: "útvarp hundrað og einn", "útvarp hundrað einn", "útvarp hundrað 1", + "útvarp", ) ) diff --git a/queries/iot_speakers.py b/queries/iot_speakers.py index 3507246f..42740ec4 100644 --- a/queries/iot_speakers.py +++ b/queries/iot_speakers.py @@ -258,14 +258,20 @@ def QIoTSpeakerUtvarpSudurland( result["station"] = "Útvarp Suðurland" +def QIoTSpeakerGroupName(node: Node, params: QueryStateDict, result: Result) -> None: + result["group_name"] = result._indefinite + + def call_sonos_client(sonos_client, result): """Call the appropriate function in the SonosClient based on the result""" handler_func = _HANDLER_MAP[result.qkey][0] if result.get("station") is not None: radio_url = _RADIO_STREAMS.get(f"{result.station}") - getattr(sonos_client, handler_func)(radio_url) + response = getattr(sonos_client, handler_func)(radio_url) + return response else: - getattr(sonos_client, handler_func)() + response = getattr(sonos_client, handler_func)() + return response return @@ -284,17 +290,29 @@ def sentence(state: QueryStateDict, result: Result) -> None: print("sentence") q: Query = state["query"] if "qtype" in result and "qkey" in result: + print("IF QTYPE AND QKEY") try: q.set_qtype(result.qtype) device_data = q.client_data("iot_speakers") if device_data is not None: - sonos_client = SonosClient(device_data, q.client_id) - call_sonos_client(sonos_client, result) - handler_answer = _HANDLER_MAP[result.qkey][1] - answer = handler_answer - answer_list = gen_answer(answer) - answer_list[1].replace("Sonos", "Sónos") - q.set_answer(*answer_list) + print("JUST BEFORE SONOS CLIENT") + sonos_client = SonosClient( + device_data, q.client_id, group_name=result.get("group_name") + ) + print("JUST AFTER SONOS CLIENT") + response = call_sonos_client(sonos_client, result) + if response == "Group not found": + text_ans = f"Herbergið '{result.group_name}' fannst ekki. Vinsamlegast athugaðu í Sonos appinu hvort nafnið sé rétt." + else: + handler_answer = _HANDLER_MAP[result.qkey][1] + text_ans = handler_answer + + answer = ( + dict(answer=text_ans), + text_ans, + text_ans.replace("Sonos", "Sónos"), + ) + q.set_answer(*answer) return else: print("No device data found for this account") @@ -304,6 +322,7 @@ def sentence(state: QueryStateDict, result: Result) -> None: q.set_error("E_EXCEPTION: {0}".format(e)) return else: + print("ELSE") q.set_error("E_QUERY_NOT_UNDERSTOOD") return diff --git a/queries/sonos.py b/queries/sonos.py index 7eb2f175..311ddc4a 100644 --- a/queries/sonos.py +++ b/queries/sonos.py @@ -22,7 +22,7 @@ """ -_GROUPS = { +_GROUPS_DICT = { "fjölskylduherbergi": "Family Room", "fjölskyldu herbergi": "Family Room", "stofa": "Living Room", @@ -67,6 +67,7 @@ } +from inspect import getargs import requests from datetime import datetime, timedelta import flask @@ -75,15 +76,22 @@ from util import read_api_key from queries import query_json_api, post_to_json_api from query import Query +from typing import Dict import json - +# TODO - Decide what should happen if user does not designate a speaker but owns multiple speakers class SonosClient: - def __init__(self, device_data, client_id, query=None, radio_name=None): + def __init__( + self, + device_data: Dict[str, str], + client_id: str, + group_name=None, + radio_name=None, + ): self._client_id = client_id self._device_data = device_data - self._query = query + self._group_name = group_name self._radio_name = radio_name self._sonos_encoded_credentials = read_api_key("SonosEncodedCredentials") self._code = self._device_data["sonos"]["credentials"]["code"] @@ -97,14 +105,18 @@ def __init__(self, device_data, client_id, query=None, radio_name=None): self._refresh_token = self._device_data["sonos"]["credentials"][ "refresh_token" ] - except KeyError: + except (KeyError, TypeError): self._create_token() self._check_token_expiration() self._households = self._get_households() self._household_id = self._households[0]["id"] self._groups = self._get_groups() self._players = self._get_players() - self.store_sonos_data_and_credentials() + self._store_sonos_data_and_credentials() + + """ + ------------------------------------- PRIVATE METHODS -------------------------------------------------------------------------------- + """ def _check_token_expiration(self): """ @@ -112,7 +124,7 @@ def _check_token_expiration(self): """ try: timestamp = self._device_data["sonos"]["credentials"]["timestamp"] - except KeyError: + except (KeyError, TypeError): print("No timestamp found for Sonos token.") return timestamp = datetime.strptime(timestamp, "%Y-%m-%d %H:%M:%S.%f") @@ -168,38 +180,19 @@ def _create_token(self): self._refresh_token = response.get("refresh_token") return response - def toggle_play_pause(self): - """ - Toggles play/pause of a group - """ - print("toggle playpause") - group_id = self._get_group_id() - print("exited group_id") - url = f"https://api.ws.sonos.com/control/api/v1/groups/{group_id}/playback/togglePlayPause" - headers = { - "Content-Type": "application/json", - "Authorization": f"Bearer {self._access_token}", - } - - # response = requests.request("POST", url, headers=headers, data=payload) - response = post_to_json_api(url, headers=headers) - print("response :", response) - - return response - def _get_households(self): """ Returns the list of households of the user """ print("get households") - try: - return self._device_data["sonos"]["data"]["households"] - except KeyError: - url = f"https://api.ws.sonos.com/control/api/v1/households" - headers = {"Authorization": f"Bearer {self._access_token}"} + # try: + # return self._device_data["sonos"]["data"]["households"] + # except (KeyError, TypeError): + url = f"https://api.ws.sonos.com/control/api/v1/households" + headers = {"Authorization": f"Bearer {self._access_token}"} - response = query_json_api(url, headers=headers) - return response["households"] + response = query_json_api(url, headers=headers) + return response["households"] def _get_household_id(self): """ @@ -220,28 +213,16 @@ def _get_groups(self): Returns the list of groups of the user """ print("get groups") - try: - return self._device_data["sonos"]["data"]["groups"] - except KeyError: - for i in range(len(self._households)): - url = f"https://api.ws.sonos.com/control/api/v1/households/{self._household_id}/groups" - headers = {"Authorization": f"Bearer {self._access_token}"} - - response = query_json_api(url, headers=headers) - cleaned_groups_list = self._create_grouplist_for_db(response["groups"]) - return cleaned_groups_list - - def get_groups_and_players(self): - """ - Returns the list of groups of the user - """ - print("get groups and players") - - url = f"https://api.ws.sonos.com/control/api/v1/households/{self._household_id}/groups" - headers = {"Authorization": f"Bearer {self._access_token}"} + # try: + # return self._device_data["sonos"]["data"]["groups"] + # except (KeyError, TypeError): + for i in range(len(self._households)): + url = f"https://api.ws.sonos.com/control/api/v1/households/{self._household_id}/groups" + headers = {"Authorization": f"Bearer {self._access_token}"} - response = query_json_api(url, headers) - return response + response = query_json_api(url, headers=headers) + cleaned_groups_dict = self._create_groupdict_for_db(response["groups"]) + return cleaned_groups_dict def _get_group_id(self): """ @@ -249,9 +230,24 @@ def _get_group_id(self): """ print("get group_id") try: - group_id = self._groups[0]["id"] - return group_id - except KeyError: + if self._group_name is not None: + print("GROUP NAME NOT NONE") + translated_group_name = self._translate_group_name() + print("Self groups :", self._groups) + print("GROUP NAME :", self._group_name) + print("GROUPS NAME :", self._group_name) + group_id = self._groups.get(translated_group_name.casefold()) + print("GROUP ID :", group_id) + return group_id + else: + print("GROUP NAME IS NONE") + if len(self._groups) == 1: + + print("LEN 1") + group_name = iter(self._groups[0]) + return self._groups[0][group_name] + except (KeyError, TypeError): + print("GROUP EXCEPT") url = f"https://api.ws.sonos.com/control/api/v1/households/{self._household_id}/groups" headers = { "Content-Type": "application/json", @@ -261,24 +257,34 @@ def _get_group_id(self): response = query_json_api(url, headers) return response["groups"][0]["id"] + def _translate_group_name(self): + """ + Translates the group name to the correct group name + """ + print("Translate group name") + try: + english_group_name = _GROUPS_DICT[self._group_name] + print("TRANSLATED GROUP NAME :", english_group_name) + return english_group_name + except (KeyError, TypeError): + return self._group_name + def _get_players(self): """ Returns the list of groups of the user """ print("get players") - try: - return self._device_data["sonos"]["data"]["players"] - except KeyError: - print("keyerror") - for i in range(len(self._households)): - url = f"https://api.ws.sonos.com/control/api/v1/households/{self._household_id}/groups" - headers = {"Authorization": f"Bearer {self._access_token}"} - - response = query_json_api(url, headers) - cleaned_players_list = self._create_playerlist_for_db( - response["players"] - ) - return cleaned_players_list + # try: + # return self._device_data["sonos"]["data"]["players"] + # except (KeyError, TypeError): + print("keyerror") + for i in range(len(self._households)): + url = f"https://api.ws.sonos.com/control/api/v1/households/{self._household_id}/groups" + headers = {"Authorization": f"Bearer {self._access_token}"} + + response = query_json_api(url, headers) + cleaned_players_dict = self._create_playerdict_for_db(response["players"]) + return cleaned_players_dict def _get_player_id(self): """ @@ -288,7 +294,7 @@ def _get_player_id(self): try: player_id = self._players[0]["id"] return player_id - except KeyError: + except (KeyError, TypeError): url = f"https://api.ws.sonos.com/control/api/v1/households/{self._household_id}/groups" headers = { "Content-Type": "application/json", @@ -303,13 +309,11 @@ def _create_sonos_data_dict(self): print("_create_sonos_data_dict") data_dict = {"households": self._households} for i in range(len(self._households)): - groups_raw = self._groups - players_raw = self._players - groups_list = self._groups - players_list = self._players + groups_dict = self._groups + players_dict = self._players - data_dict["groups"] = groups_list - data_dict["players"] = players_list + data_dict["groups"] = groups_dict + data_dict["players"] = players_dict return data_dict def _create_sonos_cred_dict(self): @@ -324,12 +328,12 @@ def _create_sonos_cred_dict(self): ) return cred_dict - def store_sonos_data_and_credentials(self): - print("store_sonos_data_and_credentials") - data_dict = self._create_sonos_data_dict() + def _store_sonos_data_and_credentials(self): + print("_store_sonos_data_and_credentials") + # data_dict = self._create_sonos_data_dict() cred_dict = self._create_sonos_cred_dict() sonos_dict = {} - sonos_dict["sonos"] = {"credentials": cred_dict, "data": data_dict} + sonos_dict["sonos"] = {"credentials": cred_dict} self._store_data(sonos_dict) def _store_data(self, data): @@ -337,63 +341,19 @@ def _store_data(self, data): self._client_id, "iot_speakers", data, update_in_place=True ) - def _create_grouplist_for_db(self, groups): - print("create_grouplist_for_db") - groups_list = [] + def _create_groupdict_for_db(self, groups: list): + print("create_groupdict_for_db") + groups_dict = {} for i in range(len(groups)): - groups_list.append({groups[i]["name"]: groups[i]["id"]}) - return groups_list + groups_dict[groups[i]["name"].casefold()] = groups[i]["id"] + return groups_dict - def _create_playerlist_for_db(self, players): - print("create_playerlist_for_db") - player_list = [] + def _create_playerdict_for_db(self, players: list): + print("create_playerdict_for_db") + players_dict = {} for i in range(len(players)): - player_list.append({players[i]["name"]: players[i]["id"]}) - return player_list - - def set_credentials(self, access_token, refresh_token): - print("set_credentials") - self._access_token = access_token - self._refresh_token = refresh_token - return - - def set_data(self): - print("set_data") - try: - self._households = self._get_households() - self._household_id = self._get_household_id() - self._groups = self._get_groups() - self._players = self._get_players() - self._group_id = self._get_group_id() - except KeyError: - print("Missing device data for this account") - return - - def audio_clip(self, audioclip_url): - """ - Plays an audioclip from link to .mp3 file - """ - print("audio_clip") - player_id = self._get_player_id() - url = f"https://api.ws.sonos.com/control/api/v1/players/{player_id}/audioClip" - - payload = json.dumps( - { - "name": "Embla", - "appId": "com.acme.app", - "streamUrl": f"{audioclip_url}", - "volume": 50, - "priority": "HIGH", - "clipType": "CUSTOM", - } - ) - headers = { - "Content-Type": "application/json", - "Authorization": f"Bearer {self._access_token}", - } - - response = post_to_json_api(url, payload, headers) - return response + players_dict[players[i]["name"]] = players[i]["id"] + return players_dict def _create_or_join_session(self): print("_create_or_join_session") @@ -411,7 +371,11 @@ def _create_or_join_session(self): session_id = response["sessionId"] return session_id - def play_radio_stream(self, radio_url): + """ + ------------------------------------- PUBLIC METHODS -------------------------------------------------------------------------------- + """ + + def play_radio_stream(self, radio_url: str): print("play radio stream") session_id = self._create_or_join_session() @@ -431,6 +395,8 @@ def play_radio_stream(self, radio_url): } response = post_to_json_api(url, payload, headers) + if response is None: + return "Group not found" print(response.get("text")) def increase_volume(self): @@ -445,6 +411,8 @@ def increase_volume(self): } response = post_to_json_api(url, payload, headers) + if response is None: + self._refresh_data("increase_volume") print(response.get("text")) def decrease_volume(self): @@ -459,4 +427,118 @@ def decrease_volume(self): } response = post_to_json_api(url, payload, headers) + if response is None: + return "Group not found" print(response.get("text")) + + def toggle_play_pause(self): + """ + Toggles play/pause of a group + """ + print("toggle playpause") + group_id = self._get_group_id() + print("exited group_id") + url = f"https://api.ws.sonos.com/control/api/v1/groups/{group_id}/playback/togglePlayPause" + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {self._access_token}", + } + + # response = requests.request("POST", url, headers=headers, data=payload) + response = post_to_json_api(url, headers=headers) + print("response :", response) + if response is None: + return "Group not found" + + return response + + def play_audio_clip(self, audioclip_url: str): + """ + Plays an audioclip from link to .mp3 file + """ + print("play_audio_clip") + player_id = self._get_player_id() + url = f"https://api.ws.sonos.com/control/api/v1/players/{player_id}/audioClip" + + payload = json.dumps( + { + "name": "Embla", + "appId": "com.acme.app", + "streamUrl": f"{audioclip_url}", + "volume": 30, + "priority": "HIGH", + "clipType": "CUSTOM", + } + ) + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {self._access_token}", + } + + response = post_to_json_api(url, payload, headers) + if response is None: + return "Group not found" + return response + + def play_chime(self): + player_id = self._get_player_id() + url = f"https://api.ws.sonos.com/control/api/v1/players/{player_id}/audioClip" + + payload = json.dumps( + { + "name": "Embla", + "appId": "com.acme.app", + "volume": 30, + "priority": "HIGH", + "clipType": "CHIME", + } + ) + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {self._access_token}", + } + + response = requests.request("POST", url, headers=headers, data=payload) + + print(response.text) + + def _refresh_data(self, function): + print("refresh data") + print("device_data: ", self._device_data) + # self._device_data["sonos"]["data"] = None + # print("device_data after deletion: ", self._device_data) + self._households = self._get_households() + self._groups = self._get_groups() + self._players = self._get_players() + # self._store_sonos_data_and_credentials() + getattr(self, function)() + + # def get_groups_and_players(self): + # """ + # Returns the list of groups of the user + # """ + # print("get groups and players") + + # url = f"https://api.ws.sonos.com/control/api/v1/households/{self._household_id}/groups" + # headers = {"Authorization": f"Bearer {self._access_token}"} + + # response = query_json_api(url, headers) + # return response + + # def set_credentials(self, access_token, refresh_token): + # print("set_credentials") + # self._access_token = access_token + # self._refresh_token = refresh_token + # return + + # def set_data(self): + # print("set_data") + # try: + # self._households = self._get_households() + # self._household_id = self._get_household_id() + # self._groups = self._get_groups() + # self._players = self._get_players() + # self._group_id = self._get_group_id() + # except KeyError: + # print("Missing device data for this account") + # return diff --git a/routes/api.py b/routes/api.py index 4e73a895..dc9128ec 100755 --- a/routes/api.py +++ b/routes/api.py @@ -26,6 +26,7 @@ from datetime import datetime import logging +import time from flask import request, abort from flask.wrappers import Response @@ -646,10 +647,10 @@ def register_query_data_api(version: int = 1) -> Response: 'philips_hue': { 'credentials': { 'username': username, - 'ip_address': ip address, + 'ip_address': IP address, }, 'data': { - 'groups': [{group1}, {group2}] + 'groups': {name, id}, {name, id}, ... }, }; @@ -743,7 +744,9 @@ def sonos_code(version: int = 1) -> Response: sonos_voice_clip = ( f"Hæ! Embla hérna! Ég er búin að tengja þennan Sónos hátalara." ) - sonos_client.audio_clip( + sonos_client.play_chime() + time.sleep(1.3) + sonos_client.play_audio_clip( text_to_audio_url(sonos_voice_clip) ) # Send the above message to the Sonos speaker return better_jsonify(valid=True, msg="Registered sonos code") From 6ef5e6a83a192f6a4dfc1a9be36a0afedb33626c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Wed, 6 Jul 2022 16:23:56 +0000 Subject: [PATCH 178/371] Beginning to remove callbacks in theater module --- queries/theater_module.py | 197 +++++++++++++++++++++++++------------- 1 file changed, 128 insertions(+), 69 deletions(-) diff --git a/queries/theater_module.py b/queries/theater_module.py index 54c51cc6..ba54148d 100644 --- a/queries/theater_module.py +++ b/queries/theater_module.py @@ -42,6 +42,7 @@ Resource, ResourceState, TimeResource, + WrapperResource, ) _THEATER_DIALOGUE_NAME = "theater" @@ -721,7 +722,7 @@ def QTheaterShowQuery(node: Node, params: QueryStateDict, result: Result) -> Non def _add_show( resource: Resource, dsm: DialogueStateManager, result: Result ) -> None: - selected_show: str = dsm.get_result().show_name + selected_show: str = result.show_name show_exists = False for show in _SHOWS: if show["title"] == selected_show: @@ -735,7 +736,10 @@ def _add_show( if resource.is_fulfilled: result.no_show_matched_data_exists = True - DialogueStateManager.add_callback(result, lambda r: r.name == "Show", _add_show) + dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm + _add_show(dsm.get_resource("Show"), dsm, result) + + # DialogueStateManager.add_callback(result, lambda r: r.name == "Show", _add_show) def _date_callback( @@ -846,12 +850,16 @@ def QTheaterDateTime(node: Node, params: QueryStateDict, result: Result) -> None result["show_time"] = datetime.time(h, min) result["show_date"] = datetime.date(y, m, d) - DialogueStateManager.add_callback( - result, lambda r: r.name == "ShowDate", _date_callback - ) - DialogueStateManager.add_callback( - result, lambda r: r.name == "ShowTime", _time_callback - ) + dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm + _date_callback(dsm.get_resource("ShowDate"), dsm, result) + _time_callback(dsm.get_resource("ShowTime"), dsm, result) + + # DialogueStateManager.add_callback( + # result, lambda r: r.name == "ShowDate", _date_callback + # ) + # DialogueStateManager.add_callback( + # result, lambda r: r.name == "ShowTime", _time_callback + # ) def QTheaterDate(node: Node, params: QueryStateDict, result: Result) -> None: @@ -871,9 +879,12 @@ def QTheaterDate(node: Node, params: QueryStateDict, result: Result) -> None: y += 1 result["show_date"] = datetime.date(day=d, month=m, year=y) - DialogueStateManager.add_callback( - result, lambda r: r.name == "ShowDate", _date_callback - ) + dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm + _date_callback(dsm.get_resource("ShowDate"), dsm, result) + + # DialogueStateManager.add_callback( + # result, lambda r: r.name == "ShowDate", _date_callback + # ) return raise ValueError("No date in {0}".format(str(datenode))) @@ -890,9 +901,12 @@ def QTheaterTime(node: Node, params: QueryStateDict, result: Result) -> None: result["show_time"] = datetime.time(hour, minute) - DialogueStateManager.add_callback( - result, lambda r: r.name == "ShowTime", _time_callback - ) + dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm + _time_callback(dsm.get_resource("ShowTime"), dsm, result) + + # DialogueStateManager.add_callback( + # result, lambda r: r.name == "ShowTime", _time_callback + # ) def QTheaterMoreDates(node: Node, params: QueryStateDict, result: Result) -> None: @@ -905,9 +919,12 @@ def _next_dates( else: extras["page_index"] = 3 - DialogueStateManager.add_callback( - result, lambda r: r.name == "ShowDate", _next_dates - ) + dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm + _next_dates(dsm.get_resource("ShowDate"), dsm, result) + + # DialogueStateManager.add_callback( + # result, lambda r: r.name == "ShowDate", _next_dates + # ) def QTheaterPreviousDates(node: Node, params: QueryStateDict, result: Result) -> None: @@ -920,9 +937,12 @@ def _prev_dates( else: extras["page_index"] = 0 - DialogueStateManager.add_callback( - result, lambda r: r.name == "ShowDate", _prev_dates - ) + dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm + _prev_dates(dsm.get_resource("ShowDate"), dsm, result) + + # DialogueStateManager.add_callback( + # result, lambda r: r.name == "ShowDate", _prev_dates + # ) def QTheaterShowSeatCountQuery( @@ -938,9 +958,12 @@ def _add_seat_number( else: result.invalid_seat_count = True - DialogueStateManager.add_callback( - result, lambda r: r.name == "ShowSeatCount", _add_seat_number - ) + dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm + _add_seat_number(dsm.get_resource("ShowSeatCount"), dsm, result) + + # DialogueStateManager.add_callback( + # result, lambda r: r.name == "ShowSeatCount", _add_seat_number + # ) def QTheaterShowRow(node: Node, params: QueryStateDict, result: Result) -> None: @@ -971,9 +994,12 @@ def _add_row( dsm.set_resource_state(resource.name, ResourceState.UNFULFILLED) result.no_row_matched = True - DialogueStateManager.add_callback( - result, lambda r: r.name == "ShowSeatRow", _add_row - ) + dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm + _add_row(dsm.get_resource("ShowSeatRow"), dsm, result) + + # DialogueStateManager.add_callback( + # result, lambda r: r.name == "ShowSeatRow", _add_row + # ) def QTheaterShowSeats(node: Node, params: QueryStateDict, result: Result) -> None: @@ -1015,9 +1041,12 @@ def _add_seats( if len(resource.data) > 0: dsm.set_resource_state(resource.name, ResourceState.FULFILLED) - DialogueStateManager.add_callback( - result, lambda r: r.name == "ShowSeatNumber", _add_seats - ) + dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm + _add_seats(dsm.get_resource("ShowSeatNumber"), dsm, result) + + # DialogueStateManager.add_callback( + # result, lambda r: r.name == "ShowSeatNumber", _add_seats + # ) def QTheaterGeneralOptions(node: Node, params: QueryStateDict, result: Result) -> None: @@ -1057,54 +1086,84 @@ def QNum(node: Node, params: QueryStateDict, result: Result): def QCancel(node: Node, params: QueryStateDict, result: Result): - def _cancel_order( - resource: Resource, dsm: DialogueStateManager, result: Result - ) -> None: - dsm.set_resource_state(resource.name, ResourceState.CANCELLED) - dsm.end_dialogue() + # def _cancel_order( + # resource: Resource, dsm: DialogueStateManager, result: Result + # ) -> None: + + dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm + dsm.set_resource_state(dsm.get_resource("Final").name, ResourceState.CANCELLED) + dsm.end_dialogue() result.qtype = "QCancel" - DialogueStateManager.add_callback( - result, lambda r: r.name == "Final", _cancel_order - ) + # DialogueStateManager.add_callback( + # result, lambda r: r.name == "Final", _cancel_order + # ) def QYes(node: Node, params: QueryStateDict, result: Result): - def _parse_yes( - resource: Resource, dsm: DialogueStateManager, result: Result - ) -> None: - if "yes_used" not in result and resource.is_fulfilled: - dsm.set_resource_state(resource.name, ResourceState.CONFIRMED) - result.yes_used = True - if resource.name == "ShowDateTime": - for rname in resource.requires: - dsm.get_resource(rname).state = ResourceState.CONFIRMED - - filter_func: Callable[[Resource], bool] = ( - lambda r: r.name - in ("Show", "ShowDateTime", "ShowSeatCount", "ShowSeatRow", "ShowSeatNumber") - and not r.is_confirmed - ) - DialogueStateManager.add_callback(result, filter_func, _parse_yes) + # def _parse_yes( + # resource: Resource, dsm: DialogueStateManager, result: Result + # ) -> None: + # if "yes_used" not in result and resource.is_fulfilled: + # dsm.set_resource_state(resource.name, ResourceState.CONFIRMED) + # result.yes_used = True + # if resource.name == "ShowDateTime": + # for rname in resource.requires: + # dsm.get_resource(rname).state = ResourceState.CONFIRMED + + dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm + current_resource = dsm.get_current_resource() + if current_resource.is_fulfilled and current_resource.name in ( + "Show", + "ShowDateTime", + "ShowSeatCount", + "ShowSeatRow", + "ShowSeatNumber", + ): + dsm.set_resource_state(current_resource, ResourceState.CONFIRMED) + if isinstance(current_resource, WrapperResource): + for rname in current_resource.requires: + dsm.get_resource(rname).state = ResourceState.CONFIRMED + + # filter_func: Callable[[Resource], bool] = ( + # lambda r: r.name + # in ("Show", "ShowDateTime", "ShowSeatCount", "ShowSeatRow", "ShowSeatNumber") + # and not r.is_confirmed + # ) + # DialogueStateManager.add_callback(result, filter_func, _parse_yes) def QNo(node: Node, params: QueryStateDict, result: Result): - def _parse_no( - resource: Resource, dsm: DialogueStateManager, result: Result - ) -> None: - if "no_used" not in result and resource.is_fulfilled: - dsm.set_resource_state(resource.name, ResourceState.UNFULFILLED) - result.no_used = True - if resource.name == "ShowDateTime": - dsm.get_resource("ShowDate").state = ResourceState.UNFULFILLED - dsm.get_resource("ShowTime").state = ResourceState.UNFULFILLED - - filter_func: Callable[[Resource], bool] = ( - lambda r: r.name - in ("Show", "ShowDateTime", "ShowSeatCount", "ShowSeatRow", "ShowSeatNumber") - and not r.is_confirmed - ) - DialogueStateManager.add_callback(result, filter_func, _parse_no) + # def _parse_no( + # resource: Resource, dsm: DialogueStateManager, result: Result + # ) -> None: + # if "no_used" not in result and resource.is_fulfilled: + # dsm.set_resource_state(resource.name, ResourceState.UNFULFILLED) + # result.no_used = True + # if resource.name == "ShowDateTime": + # dsm.get_resource("ShowDate").state = ResourceState.UNFULFILLED + # dsm.get_resource("ShowTime").state = ResourceState.UNFULFILLED + + dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm + current_resource = dsm.get_current_resource() + if current_resource.is_fulfilled and current_resource.name in ( + "Show", + "ShowDateTime", + "ShowSeatCount", + "ShowSeatRow", + "ShowSeatNumber", + ): + dsm.set_resource_state(resource.name, ResourceState.UNFULFILLED) + if isinstance(current_resource, WrapperResource): + for rname in current_resource.requires: + dsm.get_resource(rname).state = ResourceState.UNFULFILLED + + # filter_func: Callable[[Resource], bool] = ( + # lambda r: r.name + # in ("Show", "ShowDateTime", "ShowSeatCount", "ShowSeatRow", "ShowSeatNumber") + # and not r.is_confirmed + # ) + # DialogueStateManager.add_callback(result, filter_func, _parse_no) def QStatus(node: Node, params: QueryStateDict, result: Result): From 9fd52e14c06331bc01f64099b94ed63f2792f61a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Wed, 6 Jul 2022 16:29:17 +0000 Subject: [PATCH 179/371] Added result parameter to answering functions --- queries/theater_module.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/queries/theater_module.py b/queries/theater_module.py index ba54148d..9ecf944e 100644 --- a/queries/theater_module.py +++ b/queries/theater_module.py @@ -329,9 +329,8 @@ class ShowType(TypedDict): def _generate_show_answer( - resource: ListResource, dsm: DialogueStateManager + resource: ListResource, dsm: DialogueStateManager, result: Result ) -> Optional[AnswerTuple]: - result = dsm.get_result() if (not resource.is_confirmed and result.get("options_info")) or result.get( "show_options" ): @@ -360,9 +359,8 @@ def _generate_show_answer( def _generate_date_answer( - resource: ListResource, dsm: DialogueStateManager + resource: ListResource, dsm: DialogueStateManager, result: Result ) -> Optional[AnswerTuple]: - result = dsm.get_result() title = dsm.get_resource("Show").data[0] dates: list[str] = [] @@ -495,9 +493,8 @@ def _generate_date_answer( def _generate_seat_count_answer( - resource: NumberResource, dsm: DialogueStateManager + resource: NumberResource, dsm: DialogueStateManager, result: Result ) -> Optional[AnswerTuple]: - result = dsm.get_result() if result.get("invalid_seat_count"): return gen_answer(resource.prompts["invalid_seat_count"]) if resource.is_unfulfilled: @@ -513,9 +510,8 @@ def _generate_seat_count_answer( def _generate_row_answer( - resource: ListResource, dsm: DialogueStateManager + resource: ListResource, dsm: DialogueStateManager, result: Result ) -> Optional[AnswerTuple]: - result = dsm.get_result() title: str = dsm.get_resource("Show").data[0] seats: int = dsm.get_resource("ShowSeatCount").data available_rows: list[str] = [] @@ -585,9 +581,8 @@ def _generate_row_answer( def _generate_seat_number_answer( - resource: ListResource, dsm: DialogueStateManager + resource: ListResource, dsm: DialogueStateManager, result: Result ) -> Optional[AnswerTuple]: - result = dsm.get_result() title: str = dsm.get_resource("Show").data[0] seats: int = dsm.get_resource("ShowSeatCount").data chosen_row: int = dsm.get_resource("ShowSeatRow").data[0] @@ -657,7 +652,7 @@ def _generate_seat_number_answer( def _generate_final_answer( - resource: ListResource, dsm: DialogueStateManager + resource: ListResource, dsm: DialogueStateManager, result: Result ) -> Optional[AnswerTuple]: if resource.is_cancelled: return gen_answer(resource.prompts["cancelled"]) From c9332b21c892c2c1361a70dde983268a7236306a Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Wed, 6 Jul 2022 16:34:46 +0000 Subject: [PATCH 180/371] fixed music word non-terminal --- queries/grammars/iot_speakers.grammar | 35 +++++++++------------------ 1 file changed, 11 insertions(+), 24 deletions(-) diff --git a/queries/grammars/iot_speakers.grammar b/queries/grammars/iot_speakers.grammar index 1a37923b..73ae27f9 100644 --- a/queries/grammars/iot_speakers.grammar +++ b/queries/grammars/iot_speakers.grammar @@ -69,7 +69,7 @@ QIoTSpeakerMakeRest -> # | QCHANGEHvar? QCHANGEHvernigMake QCHANGESubject/þf # | QCHANGEHvernigMake QCHANGESubject/þf QCHANGEHvar? # | QCHANGEHvernigMake QCHANGEHvar? QCHANGESubject/þf - QIoTSpeakerMusicWordÞf QIoTSpeakerHvar? + QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? # TODO: Add support for "stilltu rauðan lit á ljósið í eldhúsinu" QIoTSpeakerSetRest -> @@ -96,8 +96,8 @@ QIoTSpeakerLetRest -> # | QCHANGEHvar? QCHANGEHvernigLet QCHANGESubject/þf # | QCHANGEHvernigLet QCHANGESubject/þf QCHANGEHvar? # | QCHANGEHvernigLet QCHANGEHvar? QCHANGESubject/þf - QIoTSpeakerBeOrBecome QIoTSpeakerMusicWordNf QIoTSpeakerHvar? - | "á" QIoTSpeakerMusicWordNf QIoTSpeakerHvar? + QIoTSpeakerBeOrBecome QIoTSpeakerMusicWord/nf QIoTSpeakerHvar? + | "á" QIoTSpeakerMusicWord/nf QIoTSpeakerHvar? # TODO: Find out why they conjugate this incorrectly "tónlist" is in þgf here, not þf QIoTSpeakerTurnOnRest -> @@ -107,29 +107,29 @@ QIoTSpeakerTurnOnRest -> # Would be good to add "slökktu á rauða litnum" functionality QIoTSpeakerTurnOffRest -> # QCHANGETurnOffLightsRest - "á" QIoTSpeakerMusicWordÞgf QIoTSpeakerHvar? + "á" QIoTSpeakerMusicWord/þgf QIoTSpeakerHvar? QIoTSpeakerPlayRest -> - QIoTSpeakerMusicWordÞf QIoTSpeakerHvar? + QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? | QIoTSpeakerRadioStationWord/þf? QIoTSpeakerRadioStationName QIoTSpeakerHvar? # TODO: Make the subject categorization cleaner QIoTSpeakerIncreaseOrDecreaseRest -> # QCHANGELightSubject/þf QCHANGEHvar? # | QCHANGEBrightnessSubject/þf QCHANGEHvar? - QIoTSpeakerMusicWordÞf QIoTSpeakerHvar? - | "í" QIoTSpeakerMusicWordÞgf QIoTSpeakerHvar? + QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? + | "í" QIoTSpeakerMusicWord/þgf QIoTSpeakerHvar? # Increase specificity # QIoTSpeakerMusicWord -> # 'tónlist' QIoTSpeakerAHvad -> - "á" QIoTSpeakerMusicWordÞf + "á" QIoTSpeakerMusicWord/þf | "á" QIoTSpeakerNewSetting/þf QIoTSpeakerAHverju -> - "á" QIoTSpeakerMusicWordÞf + "á" QIoTSpeakerMusicWord/þf | "á" QIoTSpeakerNewSetting/þgf QIoTSpeakerNewSetting/fall -> @@ -159,21 +159,8 @@ QIoTSpeakerRadioStationName -> QIoTSpeakerRadioStationWord/fall -> 'útvarpsstöð:no'/fall -QIoTSpeakerMusicWordNf -> - "tónlist" - | "tónlistin" - -QIoTSpeakerMusicWordÞf -> - "tónlist" - | "tónlistina" - -QIoTSpeakerMusicWordÞgf -> - "tónlist" - | "tónlistinni" - -QIoTSpeakerMusicWordEf -> - "tónlistar" - | "tónlistarinnar" +QIoTSpeakerMusicWord/fall -> + 'tónlist'/fall QIoTSpeakerHvar -> QIoTSpeakerLocationPreposition QIoTSpeakerGroupName/þgf From dab6b7b4ff5dcf4300e78975336ece9509f0535a Mon Sep 17 00:00:00 2001 From: Logi E Date: Wed, 6 Jul 2022 16:52:25 +0000 Subject: [PATCH 181/371] Moved DSM to Query, large refactoring, needs code cleanup --- queries/dialogue.py | 257 ++++++++++++++++++++-------------- queries/fruitseller_module.py | 3 +- queries/theater_module.py | 53 ++++--- query.py | 29 ++++ 4 files changed, 213 insertions(+), 129 deletions(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index c8c72bfa..1af7613c 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -25,10 +25,9 @@ import tomli as tomllib # Used for Python <3.11 from queries import AnswerTuple, natlang_seq -from query import Query, ClientDataDict -from tree import Result # TODO: Add timezone info to json encoding/decoding? +# TODO: FIX TYPE HINTS (esp. 'Any') # Keys for accessing saved client data for dialogues _DIALOGUE_KEY = "dialogue" @@ -41,17 +40,19 @@ _FINAL_RESOURCE_NAME = "Final" _CALLBACK_LOCATION = "callbacks" +QueryType = TypeVar("QueryType") + # Generic resource type ResourceType_co = TypeVar("ResourceType_co", bound="Resource") # Types for use in callbacks -_CallbackType = Callable[[ResourceType_co, "DialogueStateManager", Result], None] +_CallbackType = Callable[[ResourceType_co, "DialogueStateManager", Any], None] _FilterFuncType = Type[Callable[[ResourceType_co], bool]] _CallbackTupleType = Tuple[_FilterFuncType["Resource"], _CallbackType["Resource"]] # Types for use in generating prompts/answers AnsweringFunctionType = Callable[ - [ResourceType_co, "DialogueStateManager"], Optional[AnswerTuple] + [ResourceType_co, "DialogueStateManager", Any], Optional[AnswerTuple] ] # TODO: Fix 'Any' in type hint (Callable args are contravariant) AnsweringFunctionMap = Mapping[str, AnsweringFunctionType[Any]] @@ -333,28 +334,73 @@ class DialogueStructureType(TypedDict, total=False): class DialogueStateManager: - def __init__( - self, - dialogue_name: str, - start_dialogue_qtype: str, - query: Query, - result: Result, - ): + def __init__(self, dialogue_name: str, saved_state: Optional[str] = None): self._dialogue_name: str = dialogue_name - self._start_qtype: str = start_dialogue_qtype - self._q: Query = query - self._result: Result = result self._resources: Dict[str, Resource] = {} - self._saved_state: DialogueStructureType = self._get_saved_dialogue_state() - self._data: Dict[str, Any] = {} - self._answering_functions: AnsweringFunctionMap = {} - self._answer_tuple: Optional[AnswerTuple] = None - self._error: bool = False + self._in_this_dialogue: bool = False self._extras: Dict[str, Any] = {} - self._resource_graph: ResourceGraph = {} + # self._error: bool = False + # self._answering_functions = answering_functions + self._answer_tuple: Optional[AnswerTuple] = None self._current_resource: Optional[Resource] = None - # TODO: Delegate answering from a resource to another resource or to another dialogue - # TODO: í ávaxtasamtali "ég vil panta flug" "viltu að ég geymi ávaxtapöntunina eða eyði henni?" ... + self._resource_graph: ResourceGraph = {} + self._saved_state: Optional[DialogueStructureType] = None + + if isinstance(saved_state, str): + # TODO: Add try-except block + # TODO: Add check for datetime last interaction + self._saved_state = cast( + DialogueStructureType, json.loads(saved_state, cls=DialogueJSONDecoder) + ) + # Check that we have saved data for this dialogue + if self._saved_state.get(_DIALOGUE_RESOURCES_KEY): + self._in_this_dialogue = True + print("setting up dialogue") + self.setup_dialogue() # TODO: Rename me + + def setup_dialogue(self) -> None: + """ + Load dialogue structure from TOML file and update resource states from client data. + Should be called after initializing an instance of + DialogueStateManager and before calling get_answer. + """ + obj = self._load_dialogue_structure(self._dialogue_name) + # TODO: fix type hints + for rname, resource in obj[_DIALOGUE_RESOURCES_KEY].items(): + if self._saved_state and rname in self._saved_state.get(_DIALOGUE_RESOURCES_KEY, {}): + # Update empty resource with serialized data + resource.update(self._saved_state[_DIALOGUE_RESOURCES_KEY][rname]) + # Change from int to enum type + resource.state = ResourceState(resource.state) + self._resources[rname] = resource + if self._saved_state and _DIALOGUE_EXTRAS_KEY in self._saved_state: + self._extras = self._saved_state.get(_DIALOGUE_EXTRAS_KEY) or self._extras + + assert _DIALOGUE_INITIAL_RESOURCE_KEY in obj + self._initial_resource = self._resources[obj[_DIALOGUE_INITIAL_RESOURCE_KEY]] + self._initialize_resource_graph() + + # # We just started this dialogue, + # # save an empty dialogue state for this device + # # (in order to resume dialogue upon next query) + # self._start_dialogue() + + # def old__init__( + # self, + # dialogue_name: str, + # start_dialogue_qtype: str, + # result: Any, + # ): + # self._dialogue_name: str = dialogue_name + # self._start_qtype: str = start_dialogue_qtype + # self._result: Any = result + # self._resources: Dict[str, Resource] = {} + # self._saved_state: Optional[DialogueStructureType] = self._get_saved_dialogue_state() + # self._answering_functions: AnsweringFunctionMap = {} + # self._extras: Dict[str, Any] = {} + # self._current_resource: Optional[Resource] = None + # # TODO: Delegate answering from a resource to another resource or to another dialogue + # # TODO: í ávaxtasamtali "ég vil panta flug" "viltu að ég geymi ávaxtapöntunina eða eyði henni?" ... def _initialize_resource_graph(self) -> None: """ @@ -391,43 +437,19 @@ def _load_dialogue_structure(self, filename: str) -> DialogueStructureType: obj[_DIALOGUE_RESOURCES_KEY] = resource_dict return cast(DialogueStructureType, obj) + def activate_dialogue(self) -> None: + self._in_this_dialogue = True + self.setup_dialogue() + def not_in_dialogue(self) -> bool: """Check if the client is in or wants to start this dialogue""" - return ( - self._result.get("qtype") != self._start_qtype - and self._saved_state.get(_DIALOGUE_NAME_KEY) != self._dialogue_name - ) + return not self._in_this_dialogue + # return ( + # self._result.get("qtype") != self._start_qtype + # and self._saved_state.get(_DIALOGUE_NAME_KEY) != self._dialogue_name + # ) # TODO: Add check for newest dialogue - def setup_dialogue(self, answering_functions: AnsweringFunctionMap) -> None: - """ - Load dialogue structure from TOML file and update resource states from client data. - Should be called after initializing an instance of - DialogueStateManager and before calling get_answer. - """ - obj = self._load_dialogue_structure(self._dialogue_name) - # TODO: fix type hints - for rname, resource in obj[_DIALOGUE_RESOURCES_KEY].items(): - if rname in self._saved_state[_DIALOGUE_RESOURCES_KEY]: - # Update empty resource with serialized data - resource.update(self._saved_state[_DIALOGUE_RESOURCES_KEY][rname]) - # Change from int to enum type - resource.state = ResourceState(resource.state) - self._resources[rname] = resource - if _DIALOGUE_EXTRAS_KEY in self._saved_state: - self._extras = self._saved_state[_DIALOGUE_EXTRAS_KEY] or self._extras - self._answering_functions = answering_functions - - if self._result.qtype == self._start_qtype: - # We just started this dialogue, - # save an empty dialogue state for this device - # (in order to resume dialogue upon next query) - self._start_dialogue() - - assert _DIALOGUE_INITIAL_RESOURCE_KEY in obj - self._initial_resource = self._resources[obj[_DIALOGUE_INITIAL_RESOURCE_KEY]] - self._initialize_resource_graph() - def _start_dialogue(self): """Save client's state as having started this dialogue""" # New empty dialogue state, with correct dialogue name @@ -452,40 +474,47 @@ def update_dialogue_state(self): } ) + @property + def current_resource(self) -> Resource: + if self._current_resource is None: + self._current_resource = self._find_current_resource() + return self._current_resource + def get_resource(self, name: str) -> Resource: return self._resources[name] - def get_result(self) -> Result: - return self._result - def get_extras(self) -> Dict[str, Any]: return self._extras - def get_answer(self) -> Optional[AnswerTuple]: + def get_answer(self, answering_functions: AnsweringFunctionMap, result: Any) -> Optional[AnswerTuple]: # Executing callbacks - cbs: Optional[List[_CallbackTupleType]] = self._result.get(_CALLBACK_LOCATION) - curr_resource = self._resources[_FINAL_RESOURCE_NAME] - if cbs: - self._execute_callbacks_postorder(curr_resource, cbs, set()) - - if self._error: - # An error was raised somewhere during the callbacks - return None + # cbs: Optional[List[_CallbackTupleType]] = self._result.get(_CALLBACK_LOCATION) + # curr_resource = self._resources[_FINAL_RESOURCE_NAME] + # if cbs: + # self._execute_callbacks_postorder(curr_resource, cbs, set()) + self._current_resource = self._find_current_resource() + # if self._error: + # # An error was raised somewhere during the callbacks + # return None + self._answering_functions = answering_functions # Check if dialogue was cancelled - if curr_resource.is_cancelled: + if self._current_resource.is_cancelled: self._answer_tuple = self._answering_functions[_FINAL_RESOURCE_NAME]( - curr_resource, self + self._current_resource, self, result ) if not self._answer_tuple: raise ValueError("No answer for cancelled dialogue") return self._answer_tuple - self._current_resource = self._find_current_resource() + if self._current_resource.name in self._answering_functions: + ans= self._answering_functions[self._current_resource.name](self._current_resource, self, result) + print("GENERATED DATE ANSWERRRRRRRRRRRRRRRRR") + return ans # Iterate through resources (inorder traversal) # until one generates an answer - self._answer_tuple = self._get_answer_postorder(curr_resource, set()) + self._answer_tuple = self._get_answer_postorder(self._current_resource, result, set()) if self._resources[_FINAL_RESOURCE_NAME].is_confirmed: # Final callback (performing some operation with the dialogue's data) @@ -496,16 +525,16 @@ def get_answer(self) -> Optional[AnswerTuple]: return self._answer_tuple def _get_answer_postorder( - self, curr_resource: Resource, finished: Set[Resource] + self, curr_resource: Resource, result: Any, finished: Set[Resource] ) -> Optional[AnswerTuple]: for resource in self._resource_graph[curr_resource]["children"]: if resource not in finished: finished.add(resource) - ans = self._get_answer_postorder(resource, finished) + ans = self._get_answer_postorder(resource, result, finished) if ans: return ans if curr_resource.name in self._answering_functions: - return self._answering_functions[curr_resource.name](curr_resource, self) + return self._answering_functions[curr_resource.name](curr_resource, self, result) return None def _execute_callbacks_postorder( @@ -519,26 +548,29 @@ def _execute_callbacks_postorder( finished.add(resource) self._execute_callbacks_postorder(resource, cbs, finished) - for filter_func, cb in cbs: - if filter_func(curr_resource): - cb(curr_resource, self, self._result) - - def _get_saved_dialogue_state(self) -> DialogueStructureType: - """Load the dialogue state for a client""" - cd = self._q.client_data(_DIALOGUE_KEY) - # Return empty DialogueStructureType in case no dialogue state exists - dialogue_struct: DialogueStructureType = { - _DIALOGUE_NAME_KEY: "", - _DIALOGUE_RESOURCES_KEY: {}, - _DIALOGUE_LAST_INTERACTED_WITH_KEY: datetime.datetime.now(), - _DIALOGUE_EXTRAS_KEY: {}, - } - if cd: - ds_str = cd.get(self._dialogue_name) - if isinstance(ds_str, str) and ds_str != _EMPTY_DIALOGUE_DATA: - # TODO: Add try-except block - dialogue_struct = json.loads(ds_str, cls=DialogueJSONDecoder) - return dialogue_struct + # for filter_func, cb in cbs: + # if filter_func(curr_resource): + # cb(curr_resource, self, self._result) + + # def _get_saved_dialogue_state(self) -> Optional[DialogueStructureType]: + # """Load the dialogue state for a client""" + # cd = self._q.client_data(_DIALOGUE_KEY) + # dialogue_struct: Optional[DialogueStructureType] = None + # if cd: + # ds_str = cd.get(self._dialogue_name) + # if isinstance(ds_str, str) and ds_str != _EMPTY_DIALOGUE_DATA: + # # TODO: Add try-except block + # dialogue_struct = json.loads(ds_str, cls=DialogueJSONDecoder) + # # if dialogue_struct is None: + # # self._in_this_dialogue = False + # # # Return empty DialogueStructureType in case no dialogue state exists + # # dialogue_struct = { + # # _DIALOGUE_NAME_KEY: "", + # # _DIALOGUE_RESOURCES_KEY: {}, + # # _DIALOGUE_LAST_INTERACTED_WITH_KEY: datetime.datetime.now(), + # # _DIALOGUE_EXTRAS_KEY: {}, + # # } + # return dialogue_struct def _set_dialogue_state(self, ds: DialogueStructureType) -> None: """Save the state of a dialogue for a client""" @@ -548,11 +580,11 @@ def _set_dialogue_state(self, ds: DialogueStructureType) -> None: # (due to custom JSON serialization) cd = {self._dialogue_name: ds_json} # TODO: add datetime stuff - self._q.set_client_data( - _DIALOGUE_KEY, - cast(ClientDataDict, cd), - update_in_place=True, - ) + # self._q.set_client_data( + # _DIALOGUE_KEY, + # cast(Any, cd), + # update_in_place=True, + # ) def set_resource_state(self, resource_name: str, state: ResourceState): """ @@ -605,19 +637,32 @@ def end_dialogue(self) -> None: """End the client's current dialogue""" # TODO: Doesn't allow multiple conversations at once # (set_client_data overwrites other conversations) - self._q.set_client_data( - _DIALOGUE_KEY, - {self._dialogue_name: _EMPTY_DIALOGUE_DATA}, - update_in_place=True, + self._resources = {} + + def serialize_data(self): + """Serialize the dialogue's data""" + # TODO: Add try-except block? + ds_json: str = json.dumps( + { + _DIALOGUE_RESOURCES_KEY: self._resources, + _DIALOGUE_LAST_INTERACTED_WITH_KEY: datetime.datetime.now(), + _DIALOGUE_EXTRAS_KEY: self._extras, + }, + cls=DialogueJSONEncoder, ) + # Wrap data before saving dialogue state into client data + # (due to custom JSON serialization) + cd = {self._dialogue_name: ds_json} + # TODO: add datetime stuff + return cd - def set_error(self) -> None: - self._error = True + # def set_error(self) -> None: + # self._error = True @classmethod # TODO: Fix type hints? def add_callback( cls, - result: Result, + result: Any, filter_func: _FilterFuncType[Resource], cb: _CallbackType[Resource], ): diff --git a/queries/fruitseller_module.py b/queries/fruitseller_module.py index 57d6a4f3..9edf8029 100644 --- a/queries/fruitseller_module.py +++ b/queries/fruitseller_module.py @@ -21,6 +21,7 @@ # Indicate that this module wants to handle parse trees for queries, # as opposed to simple literal text strings HANDLE_TREE = True +DIALOGUE_NAME = "fruitseller" # The grammar nonterminals this module wants to handle QUERY_NONTERMINALS = {"QFruitSeller"} @@ -470,7 +471,7 @@ def QFruitInfoQuery(node: Node, params: QueryStateDict, result: Result): def sentence(state: QueryStateDict, result: Result) -> None: """Called when sentence processing is complete""" q: Query = state["query"] - dsm = DialogueStateManager(_DIALOGUE_NAME, _START_DIALOGUE_QTYPE, q, result) + dsm = q._dsm if dsm.not_in_dialogue() or result.get("parse_error"): q.set_error("E_QUERY_NOT_UNDERSTOOD") diff --git a/queries/theater_module.py b/queries/theater_module.py index 9ecf944e..48028bc4 100644 --- a/queries/theater_module.py +++ b/queries/theater_module.py @@ -63,6 +63,9 @@ def help_text(lemma: str) -> str: # This module wants to handle parse trees for queries HANDLE_TREE = True +# This module involves dialogue functionality +DIALOGUE_NAME = "theater" + # The grammar nonterminals this module wants to handle QUERY_NONTERMINALS = {"QTheater"} @@ -362,15 +365,17 @@ def _generate_date_answer( resource: ListResource, dsm: DialogueStateManager, result: Result ) -> Optional[AnswerTuple]: title = dsm.get_resource("Show").data[0] - + print("GENERATING DATE ANSWER") dates: list[str] = [] text_dates: list[str] = [] index: int = 0 extras: Dict[str, Any] = dsm.get_extras() if "page_index" in extras: index = extras["page_index"] + print("itering shows") for show in _SHOWS: if show["title"] == title: + print("itering dates") for date in show["date"]: with changedlocale(category="LC_TIME"): text_dates.append(date.strftime("\n - %a %d. %b kl. %H:%M")) @@ -383,6 +388,7 @@ def _generate_date_answer( if date_number == 2 else "Næstu þrjár dagsetningarnar eru:" ) + print("blaaaaa") if index == 0: start_string = start_string.replace("Næstu", "Fyrstu", 1) if len(dates) < 3: @@ -450,7 +456,9 @@ def _generate_date_answer( .replace("dagur", "dagurinn") ) return (dict(answer=text_ans), text_ans, numbers_to_ordinal(voice_ans)) + print("UNFULFILLLED?") if resource.is_unfulfilled: + print("UNFULFILLLED") if len(dates) > 0: ans = resource.prompts["initial"] if date_number == 1: @@ -711,6 +719,7 @@ def QTheaterDialogue(node: Node, params: QueryStateDict, result: Result) -> None def QTheaterHotWord(node: Node, params: QueryStateDict, result: Result) -> None: result.qtype = _START_DIALOGUE_QTYPE + cast(QueryStateDict, result.state)["query"].dsm.activate_dialogue() def QTheaterShowQuery(node: Node, params: QueryStateDict, result: Result) -> None: @@ -1107,7 +1116,7 @@ def QYes(node: Node, params: QueryStateDict, result: Result): # dsm.get_resource(rname).state = ResourceState.CONFIRMED dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm - current_resource = dsm.get_current_resource() + current_resource = dsm.current_resource if current_resource.is_fulfilled and current_resource.name in ( "Show", "ShowDateTime", @@ -1115,7 +1124,9 @@ def QYes(node: Node, params: QueryStateDict, result: Result): "ShowSeatRow", "ShowSeatNumber", ): - dsm.set_resource_state(current_resource, ResourceState.CONFIRMED) + print("CONFIRIMNGG") + dsm.set_resource_state(current_resource.name, ResourceState.CONFIRMED) + print("CASCADED STATE") if isinstance(current_resource, WrapperResource): for rname in current_resource.requires: dsm.get_resource(rname).state = ResourceState.CONFIRMED @@ -1140,8 +1151,8 @@ def QNo(node: Node, params: QueryStateDict, result: Result): # dsm.get_resource("ShowTime").state = ResourceState.UNFULFILLED dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm - current_resource = dsm.get_current_resource() - if current_resource.is_fulfilled and current_resource.name in ( + resource = dsm.current_resource + if resource.is_fulfilled and resource.name in ( "Show", "ShowDateTime", "ShowSeatCount", @@ -1149,8 +1160,8 @@ def QNo(node: Node, params: QueryStateDict, result: Result): "ShowSeatNumber", ): dsm.set_resource_state(resource.name, ResourceState.UNFULFILLED) - if isinstance(current_resource, WrapperResource): - for rname in current_resource.requires: + if isinstance(resource, WrapperResource): + for rname in resource.requires: dsm.get_resource(rname).state = ResourceState.UNFULFILLED # filter_func: Callable[[Resource], bool] = ( @@ -1188,29 +1199,27 @@ def _fetch_shows() -> Any: def sentence(state: QueryStateDict, result: Result) -> None: """Called when sentence processing is complete""" q: Query = state["query"] - dsm: DialogueStateManager = DialogueStateManager( - _THEATER_DIALOGUE_NAME, _START_DIALOGUE_QTYPE, q, result - ) + dsm: DialogueStateManager = q.dsm if dsm.not_in_dialogue(): q.set_error("E_QUERY_NOT_UNDERSTOOD") return try: print("A") - result.shows = _fetch_shows() - dsm.setup_dialogue(_ANSWERING_FUNCTIONS) - if result.qtype == "QStatus": - # Example info handling functionality - text = "Leikhúsmiðapöntunin þín gengur bara vel. " - ans = dsm.get_answer() or gen_answer(text) - q.set_answer(*ans) - return - print("C") - print(dsm._resources) - ans: Optional[AnswerTuple] = dsm.get_answer() + # result.shows = _fetch_shows() + # dsm.setup_dialogue(_ANSWERING_FUNCTIONS) + # if result.qtype == "QStatus": + # # Example info handling functionality + # text = "Leikhúsmiðapöntunin þín gengur bara vel. " + # ans = dsm.get_answer() or gen_answer(text) + # q.set_answer(*ans) + # return + # print("C") + # print(dsm._resources) + ans: Optional[AnswerTuple] = dsm.get_answer(_ANSWERING_FUNCTIONS, result) if "show_options" not in result: q.query_is_command() - print("D") + print("D", ans) if not ans: print("No answer generated") q.set_error("E_QUERY_NOT_UNDERSTOOD") diff --git a/query.py b/query.py index 7172ab51..ff918d81 100755 --- a/query.py +++ b/query.py @@ -53,6 +53,7 @@ import random from copy import deepcopy from collections import defaultdict +from queries.dialogue import DialogueStateManager from settings import Settings @@ -634,10 +635,21 @@ def execute_from_tree(self) -> bool: if self._tree is None: self.set_error("E_QUERY_NOT_PARSED") return False + dialogue_data: Optional[ClientDataDict] = None # For storing all dialogue data # Try each tree processor in turn, in priority order (highest priority first) for processor in self._tree_processors: self._error = None self._qtype = None + # Check if processor is a dialogue module + dialogue_name = getattr(processor, "DIALOGUE_NAME", None) + if dialogue_name: + if dialogue_data is None: + dialogue_data = self.client_data("dialogue") + self._dsm = DialogueStateManager( + dialogue_name, + dialogue_data.get(dialogue_name) if dialogue_data else None, + ) + print("INITIALIZED DSM FOR {0}".format(dialogue_name)) # Process the tree, which has only one sentence, but may # have multiple matching query nonterminals # (children of Query in the grammar) @@ -646,6 +658,18 @@ def execute_from_tree(self) -> bool: # "query" field of the TreeStateDict is populated, # turning it into a QueryStateDict. if self._tree.process_queries(self, self._session, processor): + if dialogue_name: + print( + "SAVING DSM STATE FOR {0} {1}".format( + dialogue_name, self._dsm.serialize_data() + ) + ) + # Save the dialogue state + self.set_client_data( + "dialogue", + self._dsm.serialize_data(), + update_in_place=True, + ) # This processor found an answer, which is already stored # in the Query object: return True return True @@ -845,6 +869,11 @@ def client_version(self) -> Optional[str]: """Return client version string, e.g. "1.0.3" """ return self._client_version + @property + def dsm(self) -> DialogueStateManager: + assert self._dsm is not None + return self._dsm + def response(self) -> Optional[ResponseType]: """Return the detailed query answer""" return self._response From a2fc3f45c96b06e64b6681c8f16ee21db3cd2562 Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Thu, 7 Jul 2022 10:24:35 +0000 Subject: [PATCH 182/371] toggle_play_pause -> toggle_pause & toggle_play --- queries/iot_speakers.py | 4 ++-- queries/sonos.py | 27 +++++++++++++++++++++++++-- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/queries/iot_speakers.py b/queries/iot_speakers.py index 42740ec4..f06a7507 100644 --- a/queries/iot_speakers.py +++ b/queries/iot_speakers.py @@ -277,8 +277,8 @@ def call_sonos_client(sonos_client, result): # Map of query keys to handler functions and the corresponding answer string for Embla _HANDLER_MAP = { - "play_music": ["toggle_play_pause", "Ég kveikti á tónlist"], - "pause_music": ["toggle_play_pause", "Ég slökkti á tónlist"], + "play_music": ["toggle_play", "Ég kveikti á tónlist"], + "pause_music": ["toggle_pause", "Ég slökkti á tónlist"], "increase_volume": ["increase_volume", "Ég hækkaði í tónlistinni"], "decrease_volume": ["decrease_volume", "Ég lækkaði í tónlistinni"], "radio": ["play_radio_stream", "Ég setti á útvarpstöðina"], diff --git a/queries/sonos.py b/queries/sonos.py index 311ddc4a..e4f3f9c7 100644 --- a/queries/sonos.py +++ b/queries/sonos.py @@ -431,14 +431,37 @@ def decrease_volume(self): return "Group not found" print(response.get("text")) - def toggle_play_pause(self): + def toggle_play(self): """ Toggles play/pause of a group """ print("toggle playpause") group_id = self._get_group_id() print("exited group_id") - url = f"https://api.ws.sonos.com/control/api/v1/groups/{group_id}/playback/togglePlayPause" + url = f"https://api.ws.sonos.com/control/api/v1/groups/{group_id}/playback/play" + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {self._access_token}", + } + + # response = requests.request("POST", url, headers=headers, data=payload) + response = post_to_json_api(url, headers=headers) + print("response :", response) + if response is None: + return "Group not found" + + return response + + def toggle_pause(self): + """ + Toggles play/pause of a group + """ + print("toggle playpause") + group_id = self._get_group_id() + print("exited group_id") + url = ( + f"https://api.ws.sonos.com/control/api/v1/groups/{group_id}/playback/pause" + ) headers = { "Content-Type": "application/json", "Authorization": f"Bearer {self._access_token}", From 00ce0c40d1412e41abbe2db7defbfbc4986ff3f8 Mon Sep 17 00:00:00 2001 From: Logi E Date: Thu, 7 Jul 2022 10:33:21 +0000 Subject: [PATCH 183/371] Moved resources to separate file, fixed type hints, other minor refactoring --- queries/dialogue.py | 421 ++++------------------------- queries/dialogues/fruitseller.toml | 2 - queries/dialogues/theater.toml | 3 - queries/resources.py | 333 +++++++++++++++++++++++ 4 files changed, 381 insertions(+), 378 deletions(-) create mode 100644 queries/resources.py diff --git a/queries/dialogue.py b/queries/dialogue.py index 1af7613c..0bc3e1a7 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -16,16 +16,24 @@ import os.path import json import datetime -from enum import IntFlag, auto -from dataclasses import dataclass, field try: import tomllib # type: ignore (module not available in Python <3.11) except ModuleNotFoundError: import tomli as tomllib # Used for Python <3.11 -from queries import AnswerTuple, natlang_seq +from queries import AnswerTuple +from queries.resources import ( + RESOURCE_MAP, + Resource, + DialogueJSONDecoder, + DialogueJSONEncoder, + ResourceState, + WrapperResource, +) +# TODO:? Delegate answering from a resource to another resource or to another dialogue +# TODO:? í ávaxtasamtali "ég vil panta flug" "viltu að ég geymi ávaxtapöntunina eða eyði henni?" ... # TODO: Add timezone info to json encoding/decoding? # TODO: FIX TYPE HINTS (esp. 'Any') @@ -35,13 +43,10 @@ _DIALOGUE_RESOURCES_KEY = "resources" _DIALOGUE_LAST_INTERACTED_WITH_KEY = "last_interacted_with" _DIALOGUE_EXTRAS_KEY = "extras" -_DIALOGUE_INITIAL_RESOURCE_KEY = "initial_resource" _EMPTY_DIALOGUE_DATA = "{}" _FINAL_RESOURCE_NAME = "Final" _CALLBACK_LOCATION = "callbacks" -QueryType = TypeVar("QueryType") - # Generic resource type ResourceType_co = TypeVar("ResourceType_co", bound="Resource") @@ -58,255 +63,6 @@ AnsweringFunctionMap = Mapping[str, AnsweringFunctionType[Any]] -class ResourceState(IntFlag): - """Enum representing the different states a dialogue resource can be in.""" - - # Main states (order matters, lower state should equal a lower number) - UNFULFILLED = auto() - PARTIALLY_FULFILLED = auto() - FULFILLED = auto() - CONFIRMED = auto() - # ---- Extra states - PAUSED = auto() - SKIPPED = auto() - CANCELLED = auto() - ALL = ( - UNFULFILLED - | PARTIALLY_FULFILLED - | FULFILLED - | CONFIRMED - | PAUSED - | SKIPPED - | CANCELLED - ) - - -########################## -# RESOURCE CLASSES # -########################## - - -@dataclass(eq=False, repr=False) -class Resource: - """ - Base class representing a dialogue resource. - Keeps track of the state of the resource, and the data it contains. - """ - - # Name of resource - name: str = "" - # Type (child class) of Resource - type: str = "" - # Contained data - data: Any = None - # Resource state (unfulfilled, partially fulfilled, etc.) - state: ResourceState = ResourceState.UNFULFILLED - # Resources that must be confirmed before moving on to this resource - requires: List[str] = field(default_factory=list) - # Dictionary containing different prompts/responses - prompts: Mapping[str, str] = field(default_factory=dict) - # When this resource's state is changed, change all parent resource states as well - cascade_state: bool = False - - @property - def is_unfulfilled(self) -> bool: - return ResourceState.UNFULFILLED in self.state - - @property - def is_partially_fulfilled(self) -> bool: - return ResourceState.PARTIALLY_FULFILLED in self.state - - @property - def is_fulfilled(self) -> bool: - return ResourceState.FULFILLED in self.state - - @property - def is_confirmed(self) -> bool: - return ResourceState.CONFIRMED in self.state - - @property - def is_paused(self) -> bool: - return ResourceState.PAUSED in self.state - - @property - def is_skipped(self) -> bool: - return ResourceState.SKIPPED in self.state - - @property - def is_cancelled(self) -> bool: - return ResourceState.CANCELLED in self.state - - def update(self, new_data: Optional["Resource"]) -> None: - """Update resource with attributes from another resource.""" - if new_data: - self.__dict__.update(new_data.__dict__) - - def format_data(self, format_func: Optional[Callable[[Any], str]] = None) -> str: - """ - Function to format data for display, - optionally taking in a formatting function. - """ - return format_func(self.data) if format_func else self.data - - def __hash__(self) -> int: - return hash(self.name) - - def __eq__(self, other: object) -> bool: - return isinstance(other, Resource) and self.name == other.name - - def __repr__(self) -> str: - return f"<{self.name}>" - - def __str__(self) -> str: - return f"<{self.name}>" - - -@dataclass(eq=False, repr=False) -class ListResource(Resource): - """Resource representing a list of items.""" - - data: List[Any] = field(default_factory=list) - max_items: Optional[int] = None - - def format_data(self, format_func: Optional[Callable[[Any], str]] = None) -> str: - if format_func: - return format_func(self.data) - return natlang_seq([str(x) for x in self.data]) - - -# TODO: ? -# ExactlyOneResource (choose one resource from options) -# SetResource (a set of resources)? -# UserInfoResource (user info, e.g. name, age, home address, etc., can use saved data to autofill) -# ... - - -@dataclass(eq=False, repr=False) -class YesNoResource(Resource): - """Resource representing a yes/no answer.""" - - data: bool = False - - def set_yes(self): - self.data = True - self.state = ResourceState.CONFIRMED - - def set_no(self): - self.data = False - self.state = ResourceState.CONFIRMED - - def format_data(self, format_func: Optional[Callable[[Any], str]] = None) -> str: - if format_func: - return format_func(self.data) - return "já" if self.data else "nei" - - -@dataclass(eq=False, repr=False) -class ConfirmResource(YesNoResource): - """Resource representing a confirmation of other resources.""" - - def set_no(self): - self.data = False - self.state = ResourceState.CANCELLED # TODO: ? - - def confirm_children(self, dsm: "DialogueStateManager") -> None: - """Confirm all child/required resources.""" - ConfirmResource._confirm_children(self, dsm) - - @staticmethod - def _confirm_children( - res: Resource, - dsm: "DialogueStateManager", - ) -> None: - for req in res.requires: - req_res = dsm.get_resource(req) - if not isinstance(req_res, ConfirmResource): - ConfirmResource._confirm_children(req_res, dsm) - req_res.state = ResourceState.CONFIRMED - - -@dataclass(eq=False, repr=False) -class DateResource(Resource): - """Resource representing a date.""" - - data: datetime.date = field(default_factory=datetime.date.today) - - @property - def date(self) -> Optional[datetime.date]: - return self.data if self.is_fulfilled else None - - def set_date(self, new_date: datetime.date) -> None: - self.data = new_date - - def format_data(self, format_func: Optional[Callable[[Any], str]] = None) -> str: - if format_func: - return format_func(self.data) - return self.data.strftime("%x") - - -@dataclass(eq=False, repr=False) -class TimeResource(Resource): - """Resource representing a time (00:00-23:59).""" - - data: datetime.time = field(default_factory=datetime.time) - - @property - def time(self) -> Optional[datetime.time]: - return self.data if self.is_fulfilled else None - - def set_time(self, new_time: datetime.time) -> None: - self.data = new_time - - def format_data(self, format_func: Optional[Callable[[Any], str]] = None) -> str: - if format_func: - return format_func(self.data) - return self.data.strftime("%X") - - -@dataclass(eq=False, repr=False) -class DatetimeResource(Resource): - """Resource for wrapping date and time resources.""" - - ... - - -@dataclass(eq=False, repr=False) -class NumberResource(Resource): - """Resource representing a number.""" - - data: int = 0 - - -@dataclass(eq=False, repr=False) -class OrResource(Resource): - exclusive: bool = False # Only one of the resources should be fulfilled - - -@dataclass(eq=False, repr=False) # Wrapper when multiple resources are required -class WrapperResource(Resource): - ... - - -@dataclass(eq=False, repr=False) -class FinalResource(Resource): - """Resource representing the final state of a dialogue.""" - - data: Any = None - - -_RESOURCE_TYPES: Mapping[str, Any] = { - "Resource": Resource, - "ListResource": ListResource, - "YesNoResource": YesNoResource, - "DateResource": DateResource, - "TimeResource": TimeResource, - "DatetimeResource": DatetimeResource, - "NumberResource": NumberResource, - "FinalResource": FinalResource, - "WrapperResource": WrapperResource, - "OrResource": OrResource, -} - ################################ # DIALOGUE STATE MANAGER # ################################ @@ -320,14 +76,16 @@ class ResourceGraphItem(TypedDict): ResourceGraph = Dict[Resource, ResourceGraphItem] -class DialogueStructureType(TypedDict, total=False): +class DialogueTOMLStructure(TypedDict): + resources: List[Dict[str, Any]] + + +class DialogueDBStructure(TypedDict): """ Representation of the dialogue structure, - as it is read from the TOML files and saved to the database. + as it is saved to the database. """ - dialogue_name: str - initial_resource: str resources: Dict[str, Resource] last_interacted_with: Optional[datetime.datetime] extras: Optional[Dict[str, Any]] @@ -344,19 +102,19 @@ def __init__(self, dialogue_name: str, saved_state: Optional[str] = None): self._answer_tuple: Optional[AnswerTuple] = None self._current_resource: Optional[Resource] = None self._resource_graph: ResourceGraph = {} - self._saved_state: Optional[DialogueStructureType] = None + # Database data for this dialogue, if any + self._saved_state: Optional[DialogueDBStructure] = None if isinstance(saved_state, str): # TODO: Add try-except block # TODO: Add check for datetime last interaction self._saved_state = cast( - DialogueStructureType, json.loads(saved_state, cls=DialogueJSONDecoder) + DialogueDBStructure, json.loads(saved_state, cls=DialogueJSONDecoder) ) # Check that we have saved data for this dialogue if self._saved_state.get(_DIALOGUE_RESOURCES_KEY): self._in_this_dialogue = True - print("setting up dialogue") - self.setup_dialogue() # TODO: Rename me + self.setup_dialogue() # TODO: Rename me def setup_dialogue(self) -> None: """ @@ -364,11 +122,14 @@ def setup_dialogue(self) -> None: Should be called after initializing an instance of DialogueStateManager and before calling get_answer. """ - obj = self._load_dialogue_structure(self._dialogue_name) - # TODO: fix type hints - for rname, resource in obj[_DIALOGUE_RESOURCES_KEY].items(): - if self._saved_state and rname in self._saved_state.get(_DIALOGUE_RESOURCES_KEY, {}): - # Update empty resource with serialized data + resource_dict: Dict[str, Resource] = self._initialize_resources( + self._dialogue_name + ) + for rname, resource in resource_dict.items(): + if self._saved_state and rname in self._saved_state.get( + _DIALOGUE_RESOURCES_KEY, {} + ): + # Update empty resource with data from database resource.update(self._saved_state[_DIALOGUE_RESOURCES_KEY][rname]) # Change from int to enum type resource.state = ResourceState(resource.state) @@ -376,32 +137,8 @@ def setup_dialogue(self) -> None: if self._saved_state and _DIALOGUE_EXTRAS_KEY in self._saved_state: self._extras = self._saved_state.get(_DIALOGUE_EXTRAS_KEY) or self._extras - assert _DIALOGUE_INITIAL_RESOURCE_KEY in obj - self._initial_resource = self._resources[obj[_DIALOGUE_INITIAL_RESOURCE_KEY]] self._initialize_resource_graph() - # # We just started this dialogue, - # # save an empty dialogue state for this device - # # (in order to resume dialogue upon next query) - # self._start_dialogue() - - # def old__init__( - # self, - # dialogue_name: str, - # start_dialogue_qtype: str, - # result: Any, - # ): - # self._dialogue_name: str = dialogue_name - # self._start_qtype: str = start_dialogue_qtype - # self._result: Any = result - # self._resources: Dict[str, Resource] = {} - # self._saved_state: Optional[DialogueStructureType] = self._get_saved_dialogue_state() - # self._answering_functions: AnsweringFunctionMap = {} - # self._extras: Dict[str, Any] = {} - # self._current_resource: Optional[Resource] = None - # # TODO: Delegate answering from a resource to another resource or to another dialogue - # # TODO: í ávaxtasamtali "ég vil panta flug" "viltu að ég geymi ávaxtapöntunina eða eyði henni?" ... - def _initialize_resource_graph(self) -> None: """ Initializes the resource graph with each @@ -417,25 +154,24 @@ def _initialize_resource_graph(self) -> None: self._resource_graph[resource]["children"].append(self._resources[req]) print(self._resource_graph) - def _load_dialogue_structure(self, filename: str) -> DialogueStructureType: + def _initialize_resources(self, filename: str) -> Dict[str, Resource]: """Loads dialogue structure from TOML file.""" basepath, _ = os.path.split(os.path.realpath(__file__)) fpath = os.path.join(basepath, "dialogues", filename + ".toml") with open(fpath, mode="r") as file: f = file.read() - obj: Dict[str, Any] = tomllib.loads(f) # type: ignore + obj: DialogueTOMLStructure = tomllib.loads(f) # type: ignore assert _DIALOGUE_RESOURCES_KEY in obj resource_dict: Dict[str, Resource] = {} - for resource in obj[_DIALOGUE_RESOURCES_KEY]: + for i, resource in enumerate(obj[_DIALOGUE_RESOURCES_KEY]): assert "name" in resource if "type" not in resource: resource["type"] = "Resource" # Create instances of Resource classes (and its subclasses) - resource_dict[resource["name"]] = _RESOURCE_TYPES[resource["type"]]( - **resource + resource_dict[resource["name"]] = RESOURCE_MAP[resource["type"]]( + **resource, order_index=i ) - obj[_DIALOGUE_RESOURCES_KEY] = resource_dict - return cast(DialogueStructureType, obj) + return resource_dict def activate_dialogue(self) -> None: self._in_this_dialogue = True @@ -455,7 +191,6 @@ def _start_dialogue(self): # New empty dialogue state, with correct dialogue name self._set_dialogue_state( { - _DIALOGUE_NAME_KEY: self._dialogue_name, _DIALOGUE_RESOURCES_KEY: {}, _DIALOGUE_LAST_INTERACTED_WITH_KEY: datetime.datetime.now(), _DIALOGUE_EXTRAS_KEY: self._extras, @@ -467,7 +202,6 @@ def update_dialogue_state(self): # Save resources to client data self._set_dialogue_state( { - _DIALOGUE_NAME_KEY: self._dialogue_name, _DIALOGUE_RESOURCES_KEY: self._resources, _DIALOGUE_LAST_INTERACTED_WITH_KEY: datetime.datetime.now(), _DIALOGUE_EXTRAS_KEY: self._extras, @@ -486,7 +220,9 @@ def get_resource(self, name: str) -> Resource: def get_extras(self) -> Dict[str, Any]: return self._extras - def get_answer(self, answering_functions: AnsweringFunctionMap, result: Any) -> Optional[AnswerTuple]: + def get_answer( + self, answering_functions: AnsweringFunctionMap, result: Any + ) -> Optional[AnswerTuple]: # Executing callbacks # cbs: Optional[List[_CallbackTupleType]] = self._result.get(_CALLBACK_LOCATION) # curr_resource = self._resources[_FINAL_RESOURCE_NAME] @@ -507,14 +243,17 @@ def get_answer(self, answering_functions: AnsweringFunctionMap, result: Any) -> raise ValueError("No answer for cancelled dialogue") return self._answer_tuple - if self._current_resource.name in self._answering_functions: - ans= self._answering_functions[self._current_resource.name](self._current_resource, self, result) + ans = self._answering_functions[self._current_resource.name]( + self._current_resource, self, result + ) print("GENERATED DATE ANSWERRRRRRRRRRRRRRRRR") return ans # Iterate through resources (inorder traversal) # until one generates an answer - self._answer_tuple = self._get_answer_postorder(self._current_resource, result, set()) + self._answer_tuple = self._get_answer_postorder( + self._current_resource, result, set() + ) if self._resources[_FINAL_RESOURCE_NAME].is_confirmed: # Final callback (performing some operation with the dialogue's data) @@ -534,7 +273,9 @@ def _get_answer_postorder( if ans: return ans if curr_resource.name in self._answering_functions: - return self._answering_functions[curr_resource.name](curr_resource, self, result) + return self._answering_functions[curr_resource.name]( + curr_resource, self, result + ) return None def _execute_callbacks_postorder( @@ -572,7 +313,7 @@ def _execute_callbacks_postorder( # # } # return dialogue_struct - def _set_dialogue_state(self, ds: DialogueStructureType) -> None: + def _set_dialogue_state(self, ds: DialogueDBStructure) -> None: """Save the state of a dialogue for a client""" # TODO: Add try-except block? ds_json: str = json.dumps(ds, cls=DialogueJSONEncoder) @@ -670,69 +411,3 @@ def add_callback( if _CALLBACK_LOCATION not in result: result[_CALLBACK_LOCATION] = [] result.callbacks.append((filter_func, cb)) - - -################################### -# ENCODING/DECODING CLASSES # -################################### - - -class DialogueJSONEncoder(json.JSONEncoder): - def default(self, o: Any) -> Any: - # Add JSON encoding for any new classes here - - if isinstance(o, Resource): - # CLASSES THAT INHERIT FROM RESOURCE - d = o.__dict__.copy() - for key in list(d.keys()): - # Skip serializing attributes that start with an underscore - if key.startswith("_"): - del d[key] - d["__type__"] = o.__class__.__name__ - return d - if isinstance(o, datetime.date): - return { - "__type__": "date", - "year": o.year, - "month": o.month, - "day": o.day, - } - if isinstance(o, datetime.time): - return { - "__type__": "time", - "hour": o.hour, - "minute": o.minute, - "second": o.second, - "microsecond": o.microsecond, - } - if isinstance(o, datetime.datetime): - return { - "__type__": "datetime", - "year": o.year, - "month": o.month, - "day": o.day, - "hour": o.hour, - "minute": o.minute, - "second": o.second, - "microsecond": o.microsecond, - } - return json.JSONEncoder.default(self, o) - - -class DialogueJSONDecoder(json.JSONDecoder): - def __init__(self, *args: Any, **kwargs: Any): - json.JSONDecoder.__init__( - self, object_hook=self.dialogue_decoding, *args, **kwargs - ) - - def dialogue_decoding(self, d: Dict[Any, Any]) -> Any: - if "__type__" not in d: - return d - t = d.pop("__type__") - if t == "date": - return datetime.date(**d) - if t == "time": - return datetime.time(**d) - if t == "datetime": - return datetime.datetime(**d) - return _RESOURCE_TYPES[t](**d) diff --git a/queries/dialogues/fruitseller.toml b/queries/dialogues/fruitseller.toml index f566aa43..1fff3df2 100644 --- a/queries/dialogues/fruitseller.toml +++ b/queries/dialogues/fruitseller.toml @@ -1,5 +1,3 @@ -dialogue_name = "fruitseller" - [[resources]] name = "Fruits" type = "ListResource" diff --git a/queries/dialogues/theater.toml b/queries/dialogues/theater.toml index f219cd67..43346de6 100644 --- a/queries/dialogues/theater.toml +++ b/queries/dialogues/theater.toml @@ -1,6 +1,3 @@ -dialogue_name = "theater" -initial_resource = "Show" - [[resources]] name = "Show" type = "ListResource" diff --git a/queries/resources.py b/queries/resources.py new file mode 100644 index 00000000..1a0d9177 --- /dev/null +++ b/queries/resources.py @@ -0,0 +1,333 @@ +from typing import ( + Any, + Callable, + Dict, + Mapping, + List, + Optional, + Type, +) + +import json +import datetime +from enum import IntFlag, auto +from dataclasses import dataclass, field + + +class ResourceState(IntFlag): + """Enum representing the different states a dialogue resource can be in.""" + + # Main states (order matters, lower state should equal a lower number) + UNFULFILLED = auto() + PARTIALLY_FULFILLED = auto() + FULFILLED = auto() + CONFIRMED = auto() + # ---- Extra states + PAUSED = auto() + SKIPPED = auto() + CANCELLED = auto() + ALL = ( + UNFULFILLED + | PARTIALLY_FULFILLED + | FULFILLED + | CONFIRMED + | PAUSED + | SKIPPED + | CANCELLED + ) + + +########################## +# RESOURCE CLASSES # +########################## + + +@dataclass(eq=False, repr=False) +class Resource: + """ + Base class representing a dialogue resource. + Keeps track of the state of the resource, and the data it contains. + """ + + # Name of resource + name: str = "" + # Type (child class) of Resource + type: str = "" + # Contained data + data: Any = None + # Resource state (unfulfilled, partially fulfilled, etc.) + state: ResourceState = ResourceState.UNFULFILLED + # Resources that must be confirmed before moving on to this resource + requires: List[str] = field(default_factory=list) + # Dictionary containing different prompts/responses + prompts: Mapping[str, str] = field(default_factory=dict) + # When this resource's state is changed, change all parent resource states as well + cascade_state: bool = False + # Used for comparing states (which one is earlier/later in the dialogue) + order_index: int = 0 + + @property + def is_unfulfilled(self) -> bool: + return ResourceState.UNFULFILLED in self.state + + @property + def is_partially_fulfilled(self) -> bool: + return ResourceState.PARTIALLY_FULFILLED in self.state + + @property + def is_fulfilled(self) -> bool: + return ResourceState.FULFILLED in self.state + + @property + def is_confirmed(self) -> bool: + return ResourceState.CONFIRMED in self.state + + @property + def is_paused(self) -> bool: + return ResourceState.PAUSED in self.state + + @property + def is_skipped(self) -> bool: + return ResourceState.SKIPPED in self.state + + @property + def is_cancelled(self) -> bool: + return ResourceState.CANCELLED in self.state + + def update(self, new_data: Optional["Resource"]) -> None: + """Update resource with attributes from another resource.""" + if new_data: + self.__dict__.update(new_data.__dict__) + + def format_data(self, format_func: Optional[Callable[[Any], str]] = None) -> str: + """ + Function to format data for display, + optionally taking in a formatting function. + """ + return format_func(self.data) if format_func else self.data + + def __hash__(self) -> int: + return hash(self.name) + + def __eq__(self, other: object) -> bool: + return isinstance(other, Resource) and self.name == other.name + + def __repr__(self) -> str: + return f"<{self.name}>" + + def __str__(self) -> str: + return f"<{self.name}>" + + +@dataclass(eq=False, repr=False) +class ListResource(Resource): + """Resource representing a list of items.""" + + data: List[Any] = field(default_factory=list) + max_items: Optional[int] = None + + def format_data(self, format_func: Optional[Callable[[Any], str]] = None) -> str: + if format_func: + return format_func(self.data) + return ",".join(str(x) for x in self.data) + + +# TODO: ? +# ExactlyOneResource (choose one resource from options) +# SetResource (a set of resources)? +# UserInfoResource (user info, e.g. name, age, home address, etc., can use saved data to autofill) +# ... + + +@dataclass(eq=False, repr=False) +class YesNoResource(Resource): + """Resource representing a yes/no answer.""" + + data: bool = False + + def set_yes(self): + self.data = True + self.state = ResourceState.CONFIRMED + + def set_no(self): + self.data = False + self.state = ResourceState.CONFIRMED + + def format_data(self, format_func: Optional[Callable[[Any], str]] = None) -> str: + if format_func: + return format_func(self.data) + return "já" if self.data else "nei" + + +@dataclass(eq=False, repr=False) +class ConfirmResource(YesNoResource): + """Resource representing a confirmation of other resources.""" + + def set_no(self): + self.data = False + self.state = ResourceState.CANCELLED # TODO: ? + + # def confirm_children(self, dsm: "DialogueStateManager") -> None: + # """Confirm all child/required resources.""" + # ConfirmResource._confirm_children(self, dsm) + + # @staticmethod + # def _confirm_children( + # res: Resource, + # dsm: "DialogueStateManager", + # ) -> None: + # for req in res.requires: + # req_res = dsm.get_resource(req) + # if not isinstance(req_res, ConfirmResource): + # ConfirmResource._confirm_children(req_res, dsm) + # req_res.state = ResourceState.CONFIRMED + + +@dataclass(eq=False, repr=False) +class DateResource(Resource): + """Resource representing a date.""" + + data: datetime.date = field(default_factory=datetime.date.today) + + @property + def date(self) -> Optional[datetime.date]: + return self.data if self.is_fulfilled else None + + def set_date(self, new_date: datetime.date) -> None: + self.data = new_date + + def format_data(self, format_func: Optional[Callable[[Any], str]] = None) -> str: + if format_func: + return format_func(self.data) + return self.data.strftime("%x") + + +@dataclass(eq=False, repr=False) +class TimeResource(Resource): + """Resource representing a time (00:00-23:59).""" + + data: datetime.time = field(default_factory=datetime.time) + + @property + def time(self) -> Optional[datetime.time]: + return self.data if self.is_fulfilled else None + + def set_time(self, new_time: datetime.time) -> None: + self.data = new_time + + def format_data(self, format_func: Optional[Callable[[Any], str]] = None) -> str: + if format_func: + return format_func(self.data) + return self.data.strftime("%X") + + +@dataclass(eq=False, repr=False) +class DatetimeResource(Resource): + """Resource for wrapping date and time resources.""" + + ... + + +@dataclass(eq=False, repr=False) +class NumberResource(Resource): + """Resource representing a number.""" + + data: int = 0 + + +@dataclass(eq=False, repr=False) +class OrResource(Resource): + exclusive: bool = False # Only one of the resources should be fulfilled + + +@dataclass(eq=False, repr=False) # Wrapper when multiple resources are required +class WrapperResource(Resource): + ... + + +@dataclass(eq=False, repr=False) +class FinalResource(Resource): + """Resource representing the final state of a dialogue.""" + + data: Any = None + + +################################### +# ENCODING/DECODING CLASSES # +################################### + + +# Add any new resource types here (for encoding/decoding) +RESOURCE_MAP: Mapping[str, Type[Resource]] = { + "Resource": Resource, + "DateResource": DateResource, + "DatetimeResource": DatetimeResource, + "FinalResource": FinalResource, + "ListResource": ListResource, + "NumberResource": NumberResource, + "OrResource": OrResource, + "TimeResource": TimeResource, + "WrapperResource": WrapperResource, + "YesNoResource": YesNoResource, +} + + +class DialogueJSONEncoder(json.JSONEncoder): + def default(self, o: Any) -> Any: + # Add JSON encoding for any new classes here + + if isinstance(o, Resource): + # CLASSES THAT INHERIT FROM RESOURCE + d = o.__dict__.copy() + for key in list(d.keys()): + # Skip serializing attributes that start with an underscore + if key.startswith("_"): + del d[key] + d["__type__"] = o.__class__.__name__ + return d + if isinstance(o, datetime.date): + return { + "__type__": "date", + "year": o.year, + "month": o.month, + "day": o.day, + } + if isinstance(o, datetime.time): + return { + "__type__": "time", + "hour": o.hour, + "minute": o.minute, + "second": o.second, + "microsecond": o.microsecond, + } + if isinstance(o, datetime.datetime): + return { + "__type__": "datetime", + "year": o.year, + "month": o.month, + "day": o.day, + "hour": o.hour, + "minute": o.minute, + "second": o.second, + "microsecond": o.microsecond, + } + return json.JSONEncoder.default(self, o) + + +class DialogueJSONDecoder(json.JSONDecoder): + def __init__(self, *args: Any, **kwargs: Any): + json.JSONDecoder.__init__( + self, object_hook=self.dialogue_decoding, *args, **kwargs + ) + + def dialogue_decoding(self, d: Dict[Any, Any]) -> Any: + if "__type__" not in d: + return d + t = d.pop("__type__") + if t == "date": + return datetime.date(**d) + if t == "time": + return datetime.time(**d) + if t == "datetime": + return datetime.datetime(**d) + return RESOURCE_MAP[t](**d) From b3ab17b5f8fa97515d51548a21750a78879d4d80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Thu, 7 Jul 2022 10:35:43 +0000 Subject: [PATCH 184/371] setting initial resource in _initialize_resource_graph --- queries/dialogue.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/queries/dialogue.py b/queries/dialogue.py index 0bc3e1a7..5a362c6d 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -114,6 +114,10 @@ def __init__(self, dialogue_name: str, saved_state: Optional[str] = None): # Check that we have saved data for this dialogue if self._saved_state.get(_DIALOGUE_RESOURCES_KEY): self._in_this_dialogue = True +<<<<<<< Updated upstream +======= + print("setting up dialogue") +>>>>>>> Stashed changes self.setup_dialogue() # TODO: Rename me def setup_dialogue(self) -> None: @@ -122,6 +126,7 @@ def setup_dialogue(self) -> None: Should be called after initializing an instance of DialogueStateManager and before calling get_answer. """ +<<<<<<< Updated upstream resource_dict: Dict[str, Resource] = self._initialize_resources( self._dialogue_name ) @@ -130,6 +135,15 @@ def setup_dialogue(self) -> None: _DIALOGUE_RESOURCES_KEY, {} ): # Update empty resource with data from database +======= + obj = self._load_dialogue_structure(self._dialogue_name) + # TODO: fix type hints + for rname, resource in obj[_DIALOGUE_RESOURCES_KEY].items(): + if self._saved_state and rname in self._saved_state.get( + _DIALOGUE_RESOURCES_KEY, {} + ): + # Update empty resource with serialized data +>>>>>>> Stashed changes resource.update(self._saved_state[_DIALOGUE_RESOURCES_KEY][rname]) # Change from int to enum type resource.state = ResourceState(resource.state) @@ -146,6 +160,8 @@ def _initialize_resource_graph(self) -> None: to what each resource requires. """ for resource in self._resources.values(): + if resource.order_index == 0: + self._initial_resource = resource self._resource_graph[resource] = {"children": [], "parents": []} for resource in self._resources.values(): From 757eec80578ed00e301503d9fb3fb1a287227a0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Thu, 7 Jul 2022 10:36:15 +0000 Subject: [PATCH 185/371] Fixed merge conflict --- queries/dialogue.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index 5a362c6d..f92ed4a0 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -114,10 +114,6 @@ def __init__(self, dialogue_name: str, saved_state: Optional[str] = None): # Check that we have saved data for this dialogue if self._saved_state.get(_DIALOGUE_RESOURCES_KEY): self._in_this_dialogue = True -<<<<<<< Updated upstream -======= - print("setting up dialogue") ->>>>>>> Stashed changes self.setup_dialogue() # TODO: Rename me def setup_dialogue(self) -> None: @@ -126,7 +122,6 @@ def setup_dialogue(self) -> None: Should be called after initializing an instance of DialogueStateManager and before calling get_answer. """ -<<<<<<< Updated upstream resource_dict: Dict[str, Resource] = self._initialize_resources( self._dialogue_name ) @@ -135,15 +130,6 @@ def setup_dialogue(self) -> None: _DIALOGUE_RESOURCES_KEY, {} ): # Update empty resource with data from database -======= - obj = self._load_dialogue_structure(self._dialogue_name) - # TODO: fix type hints - for rname, resource in obj[_DIALOGUE_RESOURCES_KEY].items(): - if self._saved_state and rname in self._saved_state.get( - _DIALOGUE_RESOURCES_KEY, {} - ): - # Update empty resource with serialized data ->>>>>>> Stashed changes resource.update(self._saved_state[_DIALOGUE_RESOURCES_KEY][rname]) # Change from int to enum type resource.state = ResourceState(resource.state) From 4efec714df8170ebc27efb60bbecd9f65c29237e Mon Sep 17 00:00:00 2001 From: Logi E Date: Thu, 7 Jul 2022 10:53:25 +0000 Subject: [PATCH 186/371] changed name of activate_dialogue and fixed theater imports --- queries/dialogue.py | 7 +------ queries/theater_module.py | 12 +++++++----- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index f92ed4a0..7740ae19 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -175,18 +175,13 @@ def _initialize_resources(self, filename: str) -> Dict[str, Resource]: ) return resource_dict - def activate_dialogue(self) -> None: + def hotword_activated(self) -> None: self._in_this_dialogue = True self.setup_dialogue() def not_in_dialogue(self) -> bool: """Check if the client is in or wants to start this dialogue""" return not self._in_this_dialogue - # return ( - # self._result.get("qtype") != self._start_qtype - # and self._saved_state.get(_DIALOGUE_NAME_KEY) != self._dialogue_name - # ) - # TODO: Add check for newest dialogue def _start_dialogue(self): """Save client's state as having started this dialogue""" diff --git a/queries/theater_module.py b/queries/theater_module.py index 48028bc4..cf6ae83e 100644 --- a/queries/theater_module.py +++ b/queries/theater_module.py @@ -21,7 +21,7 @@ This query module handles dialogue related to theater tickets. """ -from typing import Any, Callable, Dict, List, Optional, Tuple, cast +from typing import Any, Dict, List, Optional, Tuple, cast from typing_extensions import TypedDict import json import logging @@ -33,10 +33,8 @@ from tree import Result, Node, TerminalNode from queries import AnswerTuple, gen_answer, natlang_seq, parse_num, query_json_api from queries.num import number_to_text, numbers_to_ordinal -from queries.dialogue import ( - AnsweringFunctionMap, +from queries.resources import ( DateResource, - DialogueStateManager, ListResource, NumberResource, Resource, @@ -44,6 +42,10 @@ TimeResource, WrapperResource, ) +from queries.dialogue import ( + AnsweringFunctionMap, + DialogueStateManager, +) _THEATER_DIALOGUE_NAME = "theater" _THEATER_QTYPE = "theater" @@ -719,7 +721,7 @@ def QTheaterDialogue(node: Node, params: QueryStateDict, result: Result) -> None def QTheaterHotWord(node: Node, params: QueryStateDict, result: Result) -> None: result.qtype = _START_DIALOGUE_QTYPE - cast(QueryStateDict, result.state)["query"].dsm.activate_dialogue() + cast(QueryStateDict, result.state)["query"].dsm.hotword_activated() def QTheaterShowQuery(node: Node, params: QueryStateDict, result: Result) -> None: From e513c7b9aac00693d12e0ff657349f94cef12960 Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Thu, 7 Jul 2022 11:13:58 +0000 Subject: [PATCH 187/371] complete grammar to test --- queries/grammars/iot_speakers.grammar | 316 ++++++++++++++++++-------- queries/iot_hue.py | 3 + queries/sonos.py | 4 +- 3 files changed, 223 insertions(+), 100 deletions(-) diff --git a/queries/grammars/iot_speakers.grammar b/queries/grammars/iot_speakers.grammar index f6d4e327..35ab06c0 100644 --- a/queries/grammars/iot_speakers.grammar +++ b/queries/grammars/iot_speakers.grammar @@ -1,4 +1,6 @@ # TODO: Fix music hardcoding +# TODO: Check whether to force determinate form +# TODO: "Settu á útvarpið", which station to play? /þgf = þgf /ef = ef @@ -9,73 +11,77 @@ Query → QIoTSpeaker → QIoTSpeakerQuery -QIoTSpeakerQuery -> +QIoTSpeakerQuery → QIoTSpeakerMakeVerb QIoTSpeakerMakeRest | QIoTSpeakerSetVerb QIoTSpeakerSetRest # | QIoTSpeakerChangeVerb QIoTSpeakerChangeRest | QIoTSpeakerLetVerb QIoTSpeakerLetRest | QIoTSpeakerTurnOnVerb QIoTSpeakerTurnOnRest | QIoTSpeakerTurnOffVerb QIoTSpeakerTurnOffRest - | QIoTSpeakerPlayOrPauseVerb QIoTSpeakerPlayRest + | QIoTSpeakerPlayOrPauseVerb QIoTSpeakerPlayOrPauseRest | QIoTSpeakerIncreaseOrDecreaseVerb QIoTSpeakerIncreaseOrDecreaseRest | QIoTSpeakerRadioStationName -QIoTSpeakerMakeVerb -> +QIoTSpeakerMakeVerb → 'gera:so'_bh -QIoTSpeakerSetVerb -> +QIoTSpeakerSetVerb → 'setja:so'_bh | 'stilla:so'_bh + | 'láta:so'_bh -QIoTSpeakerChangeVerb -> +QIoTSpeakerChangeVerb → 'breyta:so'_bh -QIoTSpeakerLetVerb -> +QIoTSpeakerLetVerb → 'láta:so'_bh -QIoTSpeakerTurnOnVerb -> +QIoTSpeakerTurnOnVerb → 'kveikja:so'_bh -QIoTSpeakerTurnOffVerb -> +QIoTSpeakerTurnOffVerb → 'slökkva:so'_bh -QIoTSpeakerPlayOrPauseVerb -> +QIoTSpeakerPlayOrPauseVerb → QIoTSpeakerPlayVerb | QIoTSpeakerPauseVerb -QIoTSpeakerPlayVerb -> +QIoTSpeakerPlayVerb → 'spila:so'_bh -QIoTSpeakerPauseVerb -> +# "pása", as a verb, is not recognized by Árnastofnun's database, but is relatively common in casual speech. +QIoTSpeakerPauseVerb → 'stöðva:so'_bh | 'stoppa:so'_bh - | 'pása:so'_bh + | "pásaðu" -QIoTSpeakerIncreaseOrDecreaseVerb -> +QIoTSpeakerIncreaseOrDecreaseVerb → QIoTSpeakerIncreaseVerb | QIoTSpeakerDecreaseVerb -QIoTSpeakerIncreaseVerb -> +QIoTSpeakerIncreaseVerb → 'hækka:so'_bh | 'auka:so'_bh -QIoTSpeakerDecreaseVerb -> +QIoTSpeakerDecreaseVerb → 'lækka:so'_bh | 'minnka:so'_bh -QIoTSpeakerMakeRest -> - # QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigMake +QIoTSpeakerMakeRest → + QIoTSpeakerMusicPhrase/þf QIoTSpeakerHvar? QIoTSpeakerComparative/nf + | QIoTSpeakerMusicOrSoundPhrase/þf QIoTSpeakerHvar? QIoTSpeakerComparative/nf + | QIoTSpeakerMusicOrSoundPhrase/þf QIoTSpeakerComparative/nf QIoTSpeakerHvar? + # | QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigMake # | QCHANGESubject/þf QCHANGEHvernigMake QCHANGEHvar? # | QCHANGEHvar? QCHANGESubject/þf QCHANGEHvernigMake # | QCHANGEHvar? QCHANGEHvernigMake QCHANGESubject/þf # | QCHANGEHvernigMake QCHANGESubject/þf QCHANGEHvar? # | QCHANGEHvernigMake QCHANGEHvar? QCHANGESubject/þf - QIoTSpeakerMusicWordÞf QIoTSpeakerHvar? # TODO: Add support for "stilltu rauðan lit á ljósið í eldhúsinu" -QIoTSpeakerSetRest -> - QIoTSpeakerAHvad QIoTSpeakerHvar? - | "á" "tónlist" +QIoTSpeakerSetRest → + QIoTSpeakerApplianceWord/þf? QIoTSpeakerAHvad QIoTSpeakerHvar? + | QIoTSpeakerApplianceWord/þf? QIoTSpeakerHvar? QIoTSpeakerAHvad # QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigSet # | QCHANGESubject/þf QCHANGEHvernigSet QCHANGEHvar? # | QCHANGEHvar? QCHANGESubject/þf QCHANGEHvernigSet @@ -83,7 +89,7 @@ QIoTSpeakerSetRest -> # | QCHANGEHvernigSet QCHANGESubject/þf QCHANGEHvar? # | QCHANGEHvernigSet QCHANGEHvar? QCHANGESubject/þf -# QIoTSpeakerChangeRest -> +# QIoTSpeakerChangeRest → # QCHANGESubjectOne/þgf QCHANGEHvar? QCHANGEHvernigChange # | QCHANGESubjectOne/þgf QCHANGEHvernigChange QCHANGEHvar? # | QCHANGEHvar? QCHANGESubjectOne/þgf QCHANGEHvernigChange @@ -91,101 +97,136 @@ QIoTSpeakerSetRest -> # | QCHANGEHvernigChange QCHANGESubjectOne/þgf QCHANGEHvar? # | QCHANGEHvernigChange QCHANGEHvar? QCHANGESubjectOne/þgf -QIoTSpeakerLetRest -> - # QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigLet - # | QCHANGESubject/þf QCHANGEHvernigLet QCHANGEHvar? - # | QCHANGEHvar? QCHANGESubject/þf QCHANGEHvernigLet - # | QCHANGEHvar? QCHANGEHvernigLet QCHANGESubject/þf - # | QCHANGEHvernigLet QCHANGESubject/þf QCHANGEHvar? - # | QCHANGEHvernigLet QCHANGEHvar? QCHANGESubject/þf - QIoTSpeakerBeOrBecome QIoTSpeakerMusicWordNf QIoTSpeakerHvar? - | "á" QIoTSpeakerMusicWordNf QIoTSpeakerHvar? +QIoTSpeakerLetRest → + QIoTSpeakerBeOrBecome QIoTSpeakerMusicWord/nf QIoTSpeakerHvar? + | QIoTSpeakerMusicOrSoundPhrase/þf QIoTSpeakerHvar? QIoTSpeakerBeOrBecome QIoTSpeakerComparative/nf + | QIoTSpeakerMusicOrSoundPhrase/þf QIoTSpeakerBeOrBecome QIoTSpeakerComparative/nf QIoTSpeakerHvar? # TODO: Find out why they conjugate this incorrectly "tónlist" is in þgf here, not þf -QIoTSpeakerTurnOnRest -> +QIoTSpeakerTurnOnRest → QIoTSpeakerAHverju QIoTSpeakerHvar? # | QCHANGEHvar? QCHANGEAHverju # Would be good to add "slökktu á rauða litnum" functionality -QIoTSpeakerTurnOffRest -> +QIoTSpeakerTurnOffRest → # QCHANGETurnOffLightsRest - "á" QIoTSpeakerMusicWordÞgf QIoTSpeakerHvar? + "á" QIoTSpeakerMusicWord/þgf QIoTSpeakerHvar? -QIoTSpeakerPlayRest -> - QIoTSpeakerMusicWordÞf QIoTSpeakerHvar? - | QIoTSpeakerRadioStationWord/þf? QIoTSpeakerRadioStationName QIoTSpeakerHvar? +QIoTSpeakerPlayO → + QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? + | QIoTSpeakerNewRadio/þf? QIoTSpeakerHvar? # TODO: Make the subject categorization cleaner -QIoTSpeakerIncreaseOrDecreaseRest -> +QIoTSpeakerIncreaseOrDecreaseRest → # QCHANGELightSubject/þf QCHANGEHvar? # | QCHANGEBrightnessSubject/þf QCHANGEHvar? - QIoTSpeakerMusicWordÞf QIoTSpeakerHvar? - | "í" QIoTSpeakerMusicWordÞgf QIoTSpeakerHvar? + QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? + | QIoTSpeakerSoundPhrase/þf QIoTSpeakerHvar? + | "í" QIoTSpeakerMusicOrApplianceWord/þgf QIoTSpeakerHvar? # Increase specificity -# QIoTSpeakerMusicWord -> +# QIoTSpeakerMusicWord → # 'tónlist' -QIoTSpeakerAHvad -> - "á" QIoTSpeakerMusicWordÞf - | "á" QIoTSpeakerNewSetting/þf +QIoTSpeakerComparative/fall → + QIoTSpeakerMoreOrHigher/fall + | QIoTSpeakerLessOrLower/fall -QIoTSpeakerAHverju -> - "á" QIoTSpeakerMusicWordÞf - | "á" QIoTSpeakerNewSetting/þgf +# Sometimes "á" is not registered by Embla +QIoTSpeakerAHvad → + "á"? QIoTSpeakerMusicOrRadioWord/þf + | "á"? QIoTSpeakerNewSetting/þf -QIoTSpeakerNewSetting/fall -> +QIoTSpeakerAHverju → + "á" QIoTSpeakerMusicOrApplianceWord/þgf + | "á" QIoTSpeakerNewRadio/þgf + +QIoTSpeakerNewSetting/fall → QIoTSpeakerNewRadio/fall + | QIoTSpeakerNewPlay/fall + | QIoTSpeakerNewPause/fall -QIoTSpeakerNewRadio/fall -> +QIoTSpeakerNewRadio/fall → QIoTSpeakerRadioStationWord/fall? QIoTSpeakerRadioStationName # | QIoTSpeakerRadioStationWord/fall? "bylgjunni" # | QIoTSpeakerRadioStationWord/fall? QIoTSpeakerRadioStationNameIndeclinable -QIoTSpeakerRadioStationName -> - QIoTSpeakerBylgjan - | QIoTSpeakerUtvarpSaga - | QIoTSpeakerGullbylgjan - | QIoTSpeakerXId - | QIoTSpeakerLettbylgjan - | QIoTSpeakerRas1 - | QIoTSpeakerRas2 - | QIoTSpeakerRondo - | QIoTSpeakerFm957 - | QIoTSpeakerK100 - | QIoTSpeakerRetro - | QIoTSpeakerKissFm - | QIoTSpeakerFlashback - | QIoTSpeakerUtvarp101 +QIoTSpeakerNewPlay/fall → + "play" + | "plei" + | "pley" + +QIoTSpeakerNewPause/fall → + 'pása:no'/fall + | 'stopp:no'/fall + +QIoTSpeakerMusicOrSoundPhrase/fall → + QIoTSpeakerMusicPhrase/fall + | QIoTSpeakerSoundPhrase/fall + +QIoTSpeakerMusicPhrase/fall → + QIoTSpeakerMusicWord/fall QIoTSpeakerApplianceWord/ef? + | QIoTSpeakerMusicWord/fall "í" QIoTSpeakerApplianceWord/þgf + +QIoTSpeakerSoundPhrase/fall → + QIoTSpeakerVolumeOrNoiseWord/fall? QIoTSpeakerMusicOrApplianceWord/ef? + | QIoTSpeakerVolumeOrNoiseWord/fall "í" QIoTSpeakerMusicOrApplianceWord/þgf? + +QIoTSpeakerVolumeOrNoiseWord/fall → + QIoTSpeakerVolumeWord/fall + | QIoTSpeakerNoiseWord/fall + +QIoTSpeakerMusicOrApplianceWord/fall → + QIoTSpeakerMusicWord/fall + | QIoTSpeakerApplianceWord/fall -QIoTSpeakerRadioStationWord/fall -> +QIoTSpeakerRadioStationWord/fall → 'útvarpsstöð:no'/fall -QIoTSpeakerMusicWordNf -> - "tónlist" - | "tónlistin" +QIoTSpeakerMusicWord/fall → + 'tónlist'/fall -QIoTSpeakerMusicWordÞf -> - "tónlist" - | "tónlistina" +QIoTSpeakerVolumeWord/fall → + 'hljóð:no'/fall + | 'hljóðstyrkur:no'/fall -QIoTSpeakerMusicWordÞgf -> - "tónlist" - | "tónlistinni" +QIoTSpeakerNoiseWord/fall → + 'læti:no'/fall + | 'hávaði:no'/fall -QIoTSpeakerMusicWordEf -> - "tónlistar" - | "tónlistarinnar" +QIoTSpeakerApplianceWord/fall → + QIoTSpeakerSpeakerWord/fall + | QIoTSpeakerRadioWord/fall -QIoTSpeakerHvar -> +QIoTSpeakerMusicOrRadioWord/fall → + QIoTSpeakerMusicWord/fall + | QIoTSpeakerRadioWord/fall + +QIoTSpeakerSpeakerWord/fall → + 'hátalari:no'/fall + +QIoTSpeakerRadioWord/fall → + 'útvarp:no'/fall + +QIoTSpeakerMoreOrHigher/fall → + 'mikill:lo'_mst/fall + | 'ljós:lo'_mst/fall + | 'hár:lo'_mst/fall + +QIoTSpeakerLessOrLower/fall → + 'lítill:lo'_mst/fall + | 'dimmur:lo'_mst/fall + | 'lágur:lo'_mst/fall + +QIoTSpeakerHvar → QIoTSpeakerLocationPreposition QIoTSpeakerGroupName/þgf -QIoTSpeakerLocationPreposition -> +QIoTSpeakerLocationPreposition → QIoTSpeakerLocationPrepositionFirstPart? QIoTSpeakerLocationPrepositionSecondPart # The latter proverbs are grammatically incorrect, but common errors, both in speech and transcription. # The list provided is taken from StefnuAtv in Greynir.grammar. That includes "aftur:ao", which is not applicable here. -QIoTSpeakerLocationPrepositionFirstPart -> +QIoTSpeakerLocationPrepositionFirstPart → StaðarAtv | "fram:ao" | "inn:ao" @@ -193,23 +234,47 @@ QIoTSpeakerLocationPrepositionFirstPart -> | "upp:ao" | "út:ao" -QIoTSpeakerLocationPrepositionSecondPart -> +QIoTSpeakerLocationPrepositionSecondPart → "á" | "í" -QIoTSpeakerGroupName/fall -> +QIoTSpeakerGroupName/fall → no/fall -QIoTSpeakerBeOrBecome -> +QIoTSpeakerBeOrBecome → QIoTSpeakerBe | QIoTSpeakerBecome -QIoTSpeakerBe -> +QIoTSpeakerBe → 'vera:so'_nh -QIoTSpeakerBecome -> +QIoTSpeakerBecome → 'verða:so'_nh -QIoTSpeakerBylgjan -> +QIoTSpeakerRadioStationName → + QIoTSpeakerBylgjan + | QIoTSpeakerGullbylgjan + | QIoTSpeakerLettbylgjan + | QIoTSpeakerIslenskaBylgjan + | QIoTSpeaker80sBylgjan + | QIoTSpeakerRas1 + | QIoTSpeakerRas2 + | QIoTSpeakerRondo + | QIoTSpeakerUtvarpSaga + | QIoTSpeakerXid + | QIoTSpeakerFm957 + | QIoTSpeakerK100 + | QIoTSpeakerRetro + | QIoTSpeakerKissFm + | QIoTSpeakerUtvarp101 + | QIoTSpeakerApparatid + | QIoTSpeakerFmExtra + | QIoTSpeakerUtvarpSudurland + | QIoTSpeakerFlashback + | QIoTSpeaker70sFlashback + | QIoTSpeaker80sFlashback + | QIoTSpeaker90sFlashback + +QIoTSpeakerBylgjan → "Bylgjan" | "Bylgjuna" | "Bylgjunni" @@ -219,7 +284,7 @@ QIoTSpeakerBylgjan -> | "bylgjunni" | "bylgjunnar" -QIoTSpeakerUtvarpSaga -> +QIoTSpeakerUtvarpSaga → "Útvarp" "Saga" | "Útvarp" "Sögu" | "Útvarpi" "Sögu" @@ -233,7 +298,7 @@ QIoTSpeakerUtvarpSaga -> | "útvarpssaga" | "útvarpssögu" -QIoTSpeakerGullbylgjan -> +QIoTSpeakerGullbylgjan → "Gullbylgjan" | "Gullbylgjuna" | "Gullbylgjunni" @@ -247,7 +312,7 @@ QIoTSpeakerGullbylgjan -> | "gull" "bylgjunni" | "gull" "bylgjunnar" -QIoTSpeakerLettbylgjan -> +QIoTSpeakerLettbylgjan → "Léttbylgjan" | "Léttbylgjuna" | "Léttbylgjunni" @@ -261,7 +326,7 @@ QIoTSpeakerLettbylgjan -> | "létt" "bylgjunni" | "létt" "bylgjunnar" -QIoTSpeakerXId -> +QIoTSpeakerXid → "X-ið" "977"? | "X-inu" "977"? | "X-ins" "977"? @@ -273,23 +338,23 @@ QIoTSpeakerXId -> | "x" "níu" "sjö" "sjö" | "x-977" -QIoTSpeakerRas1 -> +QIoTSpeakerRas1 → "rás" "1" | "rás" "eitt" -QIoTSpeakerRas2 -> +QIoTSpeakerRas2 → "rás" "2" | "rás" "tvö" -QIoTSpeakerRondo -> +QIoTSpeakerRondo → "rondo" "fm"? | "rondó" "fm"? -QIoTSpeakerFm957 -> +QIoTSpeakerFm957 → "fm" "957" | "fm957" -QIoTSpeakerK100 -> +QIoTSpeakerK100 → "k" "100" | "k" "hundrað" | "k100" @@ -298,21 +363,74 @@ QIoTSpeakerK100 -> | "kk" "hundrað" | "kk" "100" -QIoTSpeakerRetro -> +QIoTSpeakerRetro → "retro" "fm"? | "retró" "fm"? -QIoTSpeakerKissFm -> +QIoTSpeakerKissFm → "kiss" "fm"? -QIoTSpeakerFlashback -> +QIoTSpeakerFlashback → "flassbakk" "fm"? | "flass" "bakk" "fm"? -QIoTSpeakerUtvarp101 -> +QIoTSpeakerUtvarp101 → "útvarp"? "101" | "útvarp"? "hundrað" "og" "einn" | "útvarp"? "hundrað" "og" "eitt" | "útvarp"? "hundrað" "einn" | "útvarp"? "hundrað" "1" +QIoTSpeaker80sBylgjan → + QIoTSpeaker80s "Bylgjan" + | QIoTSpeaker80s "Bylgjuna" + | QIoTSpeaker80s "Bylgjunni" + | QIoTSpeaker80s "Bylgjunnar" + | QIoTSpeaker80s "bylgjan" + | QIoTSpeaker80s "bylgjuna" + | QIoTSpeaker80s "bylgjunni" + | QIoTSpeaker80s "bylgjunnar" + +QIoTSpeakerIslenskaBylgjan → + "íslenska" "Bylgjan" + | "íslensku" "Bylgjuna" + | "íslensku" "Bylgjunni" + | "íslensku" "Bylgjunnar" + | "íslenska" "bylgjan" + | "íslensku" "bylgjuna" + | "íslensku" "bylgjunni" + | "íslensku" "bylgjunnar" + +QIoTSpeakerApparatid → + 'apparat'_gr + +QIoTSpeakerFmExtra → + "fm" "extra" + +QIoTSpeakerUtvarpSudurland → + "útvarp"? "suðurland" + | "útvarp"? "suðurland" + | "útvarps"? "suðurlands" + +QIoTSpeaker70sFlashback → + QIoTSpeaker70s "flassbakk" + +QIoTSpeaker80sFlashback → + QIoTSpeaker80s "flassbakk" + +QIoTSpeaker90sFlashback → + QIoTSpeaker90s "flassbakk" + +QIoTSpeaker70s → + "seventís" + | "seventies" + +QIoTSpeaker80s → + "eitís" + | "eydís" + | "eidís" + | "eighties" + +QIoTSpeaker90s → + "næntís" + | "nineties" \ No newline at end of file diff --git a/queries/iot_hue.py b/queries/iot_hue.py index 5900c410..e8eb04a7 100755 --- a/queries/iot_hue.py +++ b/queries/iot_hue.py @@ -263,6 +263,8 @@ def QIoTLightName(node: Node, params: QueryStateDict, result: Result) -> None: "tónlist", "hátalari", "bylgja", + "útvarp", + "útvarpsstöð", "útvarp saga", "gullbylgja", "x-ið", @@ -283,6 +285,7 @@ def QIoTLightName(node: Node, params: QueryStateDict, result: Result) -> None: "x 977", "x-977", "x-ið 977", + "x-ið", "retro", "kiss fm", "flassbakk", diff --git a/queries/sonos.py b/queries/sonos.py index 7eb2f175..c1e038bf 100644 --- a/queries/sonos.py +++ b/queries/sonos.py @@ -45,12 +45,14 @@ "leikherbergi": "Playroom", "leik herbergi": "Playroom", "sundlaug": "Pool", + "laug": "Pool", "sjónvarpsherbergi": "TV Room", "sjóvarps herbergi": "TV Room", "ferðahátalari": "Portable", "ferða hátalari": "Portable", "verönd": "Patio", "pallur": "Patio", + "altan": "Patio", "sjónvarpsherbergi": "Media Room", "sjónvarps herbergi": "Media Room", "hjónaherbergi": "Main Bedroom", @@ -382,7 +384,7 @@ def audio_clip(self, audioclip_url): "name": "Embla", "appId": "com.acme.app", "streamUrl": f"{audioclip_url}", - "volume": 50, + "volume": 20, "priority": "HIGH", "clipType": "CUSTOM", } From 63703f6bdb864230690034ce95f9df77b380af19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Thu, 7 Jul 2022 11:18:29 +0000 Subject: [PATCH 188/371] Removed callbacks from theater module --- queries/theater_module.py | 322 ++++++++++++++------------------------ 1 file changed, 115 insertions(+), 207 deletions(-) diff --git a/queries/theater_module.py b/queries/theater_module.py index cf6ae83e..4a92595c 100644 --- a/queries/theater_module.py +++ b/queries/theater_module.py @@ -725,30 +725,24 @@ def QTheaterHotWord(node: Node, params: QueryStateDict, result: Result) -> None: def QTheaterShowQuery(node: Node, params: QueryStateDict, result: Result) -> None: - def _add_show( - resource: Resource, dsm: DialogueStateManager, result: Result - ) -> None: - selected_show: str = result.show_name - show_exists = False - for show in _SHOWS: - if show["title"] == selected_show: - resource.data = [show["title"]] - dsm.set_resource_state(resource.name, ResourceState.FULFILLED) - show_exists = True - break - if not show_exists: - if resource.is_unfulfilled: - result.no_show_matched = True - if resource.is_fulfilled: - result.no_show_matched_data_exists = True - dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm - _add_show(dsm.get_resource("Show"), dsm, result) - - # DialogueStateManager.add_callback(result, lambda r: r.name == "Show", _add_show) - - -def _date_callback( + selected_show: str = result.show_name + resource: ListResource = cast(ListResource, dsm.get_resource("Show")) + show_exists = False + for show in _SHOWS: + if show["title"] == selected_show: + resource.data = [show["title"]] + dsm.set_resource_state(resource.name, ResourceState.FULFILLED) + show_exists = True + break + if not show_exists: + if resource.is_unfulfilled: + result.no_show_matched = True + if resource.is_fulfilled: + result.no_show_matched_data_exists = True + + +def _add_date( resource: DateResource, dsm: DialogueStateManager, result: Result ) -> None: dsm.set_resource_state(resource.name, ResourceState.UNFULFILLED) @@ -783,7 +777,7 @@ def _date_callback( ) -def _time_callback( +def _add_time( resource: TimeResource, dsm: DialogueStateManager, result: Result ) -> None: dsm.set_resource_state(resource.name, ResourceState.UNFULFILLED) @@ -857,15 +851,8 @@ def QTheaterDateTime(node: Node, params: QueryStateDict, result: Result) -> None result["show_date"] = datetime.date(y, m, d) dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm - _date_callback(dsm.get_resource("ShowDate"), dsm, result) - _time_callback(dsm.get_resource("ShowTime"), dsm, result) - - # DialogueStateManager.add_callback( - # result, lambda r: r.name == "ShowDate", _date_callback - # ) - # DialogueStateManager.add_callback( - # result, lambda r: r.name == "ShowTime", _time_callback - # ) + _add_date(cast(DateResource, dsm.get_resource("ShowDate")), dsm, result) + _add_time(cast(TimeResource, dsm.get_resource("ShowTime")), dsm, result) def QTheaterDate(node: Node, params: QueryStateDict, result: Result) -> None: @@ -886,11 +873,7 @@ def QTheaterDate(node: Node, params: QueryStateDict, result: Result) -> None: result["show_date"] = datetime.date(day=d, month=m, year=y) dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm - _date_callback(dsm.get_resource("ShowDate"), dsm, result) - - # DialogueStateManager.add_callback( - # result, lambda r: r.name == "ShowDate", _date_callback - # ) + _add_date(cast(DateResource, dsm.get_resource("ShowDate")), dsm, result) return raise ValueError("No date in {0}".format(str(datenode))) @@ -908,151 +891,108 @@ def QTheaterTime(node: Node, params: QueryStateDict, result: Result) -> None: result["show_time"] = datetime.time(hour, minute) dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm - _time_callback(dsm.get_resource("ShowTime"), dsm, result) - - # DialogueStateManager.add_callback( - # result, lambda r: r.name == "ShowTime", _time_callback - # ) + _add_time(cast(TimeResource, dsm.get_resource("ShowTime")), dsm, result) def QTheaterMoreDates(node: Node, params: QueryStateDict, result: Result) -> None: - def _next_dates( - resource: NumberResource, dsm: DialogueStateManager, result: Result - ) -> None: + dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm + if dsm.current_resource.name == "ShowDate": extras: Dict[str, Any] = dsm.get_extras() if "page_index" in extras: extras["page_index"] += 3 else: extras["page_index"] = 3 - dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm - _next_dates(dsm.get_resource("ShowDate"), dsm, result) - - # DialogueStateManager.add_callback( - # result, lambda r: r.name == "ShowDate", _next_dates - # ) - def QTheaterPreviousDates(node: Node, params: QueryStateDict, result: Result) -> None: - def _prev_dates( - resource: NumberResource, dsm: DialogueStateManager, result: Result - ) -> None: + dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm + if dsm.current_resource.name == "ShowDate": extras: Dict[str, Any] = dsm.get_extras() if "page_index" in extras: extras["page_index"] = max(extras["page_index"] - 3, 0) else: extras["page_index"] = 0 - dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm - _prev_dates(dsm.get_resource("ShowDate"), dsm, result) - - # DialogueStateManager.add_callback( - # result, lambda r: r.name == "ShowDate", _prev_dates - # ) - def QTheaterShowSeatCountQuery( node: Node, params: QueryStateDict, result: Result ) -> None: - def _add_seat_number( - resource: NumberResource, dsm: DialogueStateManager, result: Result - ) -> None: - if dsm.get_resource("ShowDateTime").is_confirmed: - if result.number > 0: - resource.data = result.number - dsm.set_resource_state(resource.name, ResourceState.FULFILLED) - else: - result.invalid_seat_count = True - dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm - _add_seat_number(dsm.get_resource("ShowSeatCount"), dsm, result) - - # DialogueStateManager.add_callback( - # result, lambda r: r.name == "ShowSeatCount", _add_seat_number - # ) + if dsm.get_resource("ShowDateTime").is_confirmed: + if result.number > 0: + resource: NumberResource = cast( + NumberResource, dsm.get_resource("ShowSeatCount") + ) + resource.data = result.number + dsm.set_resource_state(resource.name, ResourceState.FULFILLED) + else: + result.invalid_seat_count = True def QTheaterShowRow(node: Node, params: QueryStateDict, result: Result) -> None: - def _add_row( - resource: ListResource, dsm: DialogueStateManager, result: Result - ) -> None: - if dsm.get_resource("ShowSeatCount").is_confirmed: - title: str = dsm.get_resource("Show").data[0] - seats: int = dsm.get_resource("ShowSeatCount").data - available_rows: list[int] = [] - for show in _SHOWS: - if show["title"] == title: - checking_row: int = 1 - seats_in_row: int = 0 - for (row, _) in show["location"]: - if checking_row == row: - seats_in_row += 1 - if seats_in_row >= seats: - available_rows.append(row) - seats_in_row = 0 - else: - checking_row = row - seats_in_row = 1 - if result.number in available_rows: - resource.data = [result.number] - dsm.set_resource_state(resource.name, ResourceState.FULFILLED) - else: - dsm.set_resource_state(resource.name, ResourceState.UNFULFILLED) - result.no_row_matched = True - dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm - _add_row(dsm.get_resource("ShowSeatRow"), dsm, result) - - # DialogueStateManager.add_callback( - # result, lambda r: r.name == "ShowSeatRow", _add_row - # ) + if dsm.get_resource("ShowSeatCount").is_confirmed: + title: str = dsm.get_resource("Show").data[0] + seats: int = dsm.get_resource("ShowSeatCount").data + resource: ListResource = cast(ListResource, dsm.get_resource("ShowSeatRow")) + available_rows: list[int] = [] + for show in _SHOWS: + if show["title"] == title: + checking_row: int = 1 + seats_in_row: int = 0 + for (row, _) in show["location"]: + if checking_row == row: + seats_in_row += 1 + if seats_in_row >= seats: + available_rows.append(row) + seats_in_row = 0 + else: + checking_row = row + seats_in_row = 1 + if result.number in available_rows: + resource.data = [result.number] + dsm.set_resource_state(resource.name, ResourceState.FULFILLED) + else: + dsm.set_resource_state(resource.name, ResourceState.UNFULFILLED) + result.no_row_matched = True def QTheaterShowSeats(node: Node, params: QueryStateDict, result: Result) -> None: - def _add_seats( - resource: ListResource, dsm: DialogueStateManager, result: Result - ) -> None: - if dsm.get_resource("ShowSeatRow").is_confirmed: - title: str = dsm.get_resource("Show").data[0] - row: int = dsm.get_resource("ShowSeatRow").data[0] - number_of_seats: int = dsm.get_resource("ShowSeatCount").data - selected_seats: list[int] = [] - if len(result.numbers) > 1: - selected_seats = [ - seat for seat in range(result.numbers[0], result.numbers[1] + 1) - ] - else: - selected_seats = [result.numbers[0]] - if len(selected_seats) != number_of_seats: - resource.data = [] - dsm.set_resource_state(resource.name, ResourceState.UNFULFILLED) - result.wrong_number_seats_selected = True - return - for show in _SHOWS: - if show["title"] == title: - seats: list[int] = [] - for seat in selected_seats: - if (row, seat) in show["location"]: - seats.append(seat) - else: - resource.data = [] - dsm.set_resource_state( - resource.name, ResourceState.UNFULFILLED - ) - result.seats_unavailable = True - return - resource.data = [] - for seat in seats: - resource.data.append(seat) - if len(resource.data) > 0: - dsm.set_resource_state(resource.name, ResourceState.FULFILLED) dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm - _add_seats(dsm.get_resource("ShowSeatNumber"), dsm, result) - - # DialogueStateManager.add_callback( - # result, lambda r: r.name == "ShowSeatNumber", _add_seats - # ) + if dsm.get_resource("ShowSeatRow").is_confirmed: + resource: ListResource = cast(ListResource, dsm.get_resource("ShowSeatNumber")) + title: str = dsm.get_resource("Show").data[0] + row: int = dsm.get_resource("ShowSeatRow").data[0] + number_of_seats: int = dsm.get_resource("ShowSeatCount").data + selected_seats: list[int] = [] + if len(result.numbers) > 1: + selected_seats = [ + seat for seat in range(result.numbers[0], result.numbers[1] + 1) + ] + else: + selected_seats = [result.numbers[0]] + if len(selected_seats) != number_of_seats: + resource.data = [] + dsm.set_resource_state(resource.name, ResourceState.UNFULFILLED) + result.wrong_number_seats_selected = True + return + for show in _SHOWS: + if show["title"] == title: + seats: list[int] = [] + for seat in selected_seats: + if (row, seat) in show["location"]: + seats.append(seat) + else: + resource.data = [] + dsm.set_resource_state(resource.name, ResourceState.UNFULFILLED) + result.seats_unavailable = True + return + resource.data = [] + for seat in seats: + resource.data.append(seat) + if len(resource.data) > 0: + dsm.set_resource_state(resource.name, ResourceState.FULFILLED) def QTheaterGeneralOptions(node: Node, params: QueryStateDict, result: Result) -> None: @@ -1092,40 +1032,27 @@ def QNum(node: Node, params: QueryStateDict, result: Result): def QCancel(node: Node, params: QueryStateDict, result: Result): - # def _cancel_order( - # resource: Resource, dsm: DialogueStateManager, result: Result - # ) -> None: - dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm dsm.set_resource_state(dsm.get_resource("Final").name, ResourceState.CANCELLED) dsm.end_dialogue() result.qtype = "QCancel" - # DialogueStateManager.add_callback( - # result, lambda r: r.name == "Final", _cancel_order - # ) def QYes(node: Node, params: QueryStateDict, result: Result): - # def _parse_yes( - # resource: Resource, dsm: DialogueStateManager, result: Result - # ) -> None: - # if "yes_used" not in result and resource.is_fulfilled: - # dsm.set_resource_state(resource.name, ResourceState.CONFIRMED) - # result.yes_used = True - # if resource.name == "ShowDateTime": - # for rname in resource.requires: - # dsm.get_resource(rname).state = ResourceState.CONFIRMED - dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm current_resource = dsm.current_resource - if current_resource.is_fulfilled and current_resource.name in ( - "Show", - "ShowDateTime", - "ShowSeatCount", - "ShowSeatRow", - "ShowSeatNumber", - ): + if ( + not current_resource.is_confirmed + and current_resource.name + in ( + "Show", + "ShowDateTime", + "ShowSeatCount", + "ShowSeatRow", + "ShowSeatNumber", + ) + ) and current_resource.is_fulfilled: print("CONFIRIMNGG") dsm.set_resource_state(current_resource.name, ResourceState.CONFIRMED) print("CASCADED STATE") @@ -1133,48 +1060,29 @@ def QYes(node: Node, params: QueryStateDict, result: Result): for rname in current_resource.requires: dsm.get_resource(rname).state = ResourceState.CONFIRMED - # filter_func: Callable[[Resource], bool] = ( - # lambda r: r.name - # in ("Show", "ShowDateTime", "ShowSeatCount", "ShowSeatRow", "ShowSeatNumber") - # and not r.is_confirmed - # ) - # DialogueStateManager.add_callback(result, filter_func, _parse_yes) - def QNo(node: Node, params: QueryStateDict, result: Result): - # def _parse_no( - # resource: Resource, dsm: DialogueStateManager, result: Result - # ) -> None: - # if "no_used" not in result and resource.is_fulfilled: - # dsm.set_resource_state(resource.name, ResourceState.UNFULFILLED) - # result.no_used = True - # if resource.name == "ShowDateTime": - # dsm.get_resource("ShowDate").state = ResourceState.UNFULFILLED - # dsm.get_resource("ShowTime").state = ResourceState.UNFULFILLED - dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm resource = dsm.current_resource - if resource.is_fulfilled and resource.name in ( - "Show", - "ShowDateTime", - "ShowSeatCount", - "ShowSeatRow", - "ShowSeatNumber", - ): + if ( + not resource.is_confirmed + and resource.name + in ( + "Show", + "ShowDateTime", + "ShowSeatCount", + "ShowSeatRow", + "ShowSeatNumber", + ) + ) and resource.is_fulfilled: dsm.set_resource_state(resource.name, ResourceState.UNFULFILLED) if isinstance(resource, WrapperResource): for rname in resource.requires: dsm.get_resource(rname).state = ResourceState.UNFULFILLED - # filter_func: Callable[[Resource], bool] = ( - # lambda r: r.name - # in ("Show", "ShowDateTime", "ShowSeatCount", "ShowSeatRow", "ShowSeatNumber") - # and not r.is_confirmed - # ) - # DialogueStateManager.add_callback(result, filter_func, _parse_no) - def QStatus(node: Node, params: QueryStateDict, result: Result): + # TODO: Handle QStatus again with dsm in query result.qtype = "QStatus" From 8f6e6eef5a5a9515c2076dc2329facdb7d5dcece Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Thu, 7 Jul 2022 11:18:59 +0000 Subject: [PATCH 189/371] Added TODO to add specific prompt handling in dsm --- queries/dialogue.py | 1 + 1 file changed, 1 insertion(+) diff --git a/queries/dialogue.py b/queries/dialogue.py index 7740ae19..b51d94e6 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -36,6 +36,7 @@ # TODO:? í ávaxtasamtali "ég vil panta flug" "viltu að ég geymi ávaxtapöntunina eða eyði henni?" ... # TODO: Add timezone info to json encoding/decoding? # TODO: FIX TYPE HINTS (esp. 'Any') +# TODO: Add specific prompt handling to DSM to remove result from DSM. # Keys for accessing saved client data for dialogues _DIALOGUE_KEY = "dialogue" From 822f6ec02a947e4652dcad848dc1c1539a2f01cd Mon Sep 17 00:00:00 2001 From: Logi E Date: Thu, 7 Jul 2022 11:33:17 +0000 Subject: [PATCH 190/371] refactoring dialogue.py --- queries/dialogue.py | 199 ++++++++++---------------------------- queries/theater_module.py | 8 +- query.py | 17 ++-- 3 files changed, 61 insertions(+), 163 deletions(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index b51d94e6..b76102b5 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -4,10 +4,8 @@ Dict, Mapping, Set, - Tuple, List, Optional, - Type, TypeVar, cast, ) @@ -37,25 +35,20 @@ # TODO: Add timezone info to json encoding/decoding? # TODO: FIX TYPE HINTS (esp. 'Any') # TODO: Add specific prompt handling to DSM to remove result from DSM. +# TODO: Add try-except blocks where appropriate + +_TOML_FOLDER_NAME = "dialogues" +_DIALOGUE_EXPIRATION_TIME = 30 * 60 # 30 minutes (dialogue expires after 30 minutes) # Keys for accessing saved client data for dialogues -_DIALOGUE_KEY = "dialogue" -_DIALOGUE_NAME_KEY = "dialogue_name" _DIALOGUE_RESOURCES_KEY = "resources" _DIALOGUE_LAST_INTERACTED_WITH_KEY = "last_interacted_with" _DIALOGUE_EXTRAS_KEY = "extras" -_EMPTY_DIALOGUE_DATA = "{}" _FINAL_RESOURCE_NAME = "Final" -_CALLBACK_LOCATION = "callbacks" # Generic resource type ResourceType_co = TypeVar("ResourceType_co", bound="Resource") -# Types for use in callbacks -_CallbackType = Callable[[ResourceType_co, "DialogueStateManager", Any], None] -_FilterFuncType = Type[Callable[[ResourceType_co], bool]] -_CallbackTupleType = Tuple[_FilterFuncType["Resource"], _CallbackType["Resource"]] - # Types for use in generating prompts/answers AnsweringFunctionType = Callable[ [ResourceType_co, "DialogueStateManager", Any], Optional[AnswerTuple] @@ -88,53 +81,59 @@ class DialogueDBStructure(TypedDict): """ resources: Dict[str, Resource] - last_interacted_with: Optional[datetime.datetime] - extras: Optional[Dict[str, Any]] + last_interacted_with: datetime.datetime + extras: Dict[str, Any] class DialogueStateManager: + DIALOGUE_DATA_KEY = "dialogue" + def __init__(self, dialogue_name: str, saved_state: Optional[str] = None): self._dialogue_name: str = dialogue_name + # Dict mapping resource name to resource instance self._resources: Dict[str, Resource] = {} + # Boolean indicating if the client is in this dialogue self._in_this_dialogue: bool = False + # Extra information saved with the dialogue state self._extras: Dict[str, Any] = {} - # self._error: bool = False - # self._answering_functions = answering_functions + # Answer for the current query self._answer_tuple: Optional[AnswerTuple] = None + # Latest non-confirmed resource self._current_resource: Optional[Resource] = None + # Dependency graph for the resources self._resource_graph: ResourceGraph = {} # Database data for this dialogue, if any self._saved_state: Optional[DialogueDBStructure] = None if isinstance(saved_state, str): - # TODO: Add try-except block - # TODO: Add check for datetime last interaction self._saved_state = cast( DialogueDBStructure, json.loads(saved_state, cls=DialogueJSONDecoder) ) - # Check that we have saved data for this dialogue - if self._saved_state.get(_DIALOGUE_RESOURCES_KEY): + time_from_last_interaction = ( + datetime.datetime.now() - self._saved_state["last_interacted_with"] + ) + # Check that we have saved data for this dialogue and that it is not expired + if ( + self._saved_state[_DIALOGUE_RESOURCES_KEY] + and time_from_last_interaction.total_seconds() + < _DIALOGUE_EXPIRATION_TIME + ): self._in_this_dialogue = True - self.setup_dialogue() # TODO: Rename me + self.setup_dialogue() + # TODO: IF EXPIRED DO SOMETHING def setup_dialogue(self) -> None: """ - Load dialogue structure from TOML file and update resource states from client data. - Should be called after initializing an instance of - DialogueStateManager and before calling get_answer. + Load dialogue resources from TOML file and update their state from database data. """ - resource_dict: Dict[str, Resource] = self._initialize_resources( - self._dialogue_name - ) - for rname, resource in resource_dict.items(): - if self._saved_state and rname in self._saved_state.get( - _DIALOGUE_RESOURCES_KEY, {} - ): + assert self._saved_state + self._initialize_resources(self._dialogue_name) + for rname, resource in self._resources.items(): + if rname in self._saved_state["resources"]: # Update empty resource with data from database resource.update(self._saved_state[_DIALOGUE_RESOURCES_KEY][rname]) # Change from int to enum type resource.state = ResourceState(resource.state) - self._resources[rname] = resource if self._saved_state and _DIALOGUE_EXTRAS_KEY in self._saved_state: self._extras = self._saved_state.get(_DIALOGUE_EXTRAS_KEY) or self._extras @@ -157,24 +156,27 @@ def _initialize_resource_graph(self) -> None: self._resource_graph[resource]["children"].append(self._resources[req]) print(self._resource_graph) - def _initialize_resources(self, filename: str) -> Dict[str, Resource]: - """Loads dialogue structure from TOML file.""" + def _initialize_resources(self, filename: str) -> None: + """ + Loads dialogue structure from TOML file and + fills self._resources with empty Resource instances. + """ basepath, _ = os.path.split(os.path.realpath(__file__)) - fpath = os.path.join(basepath, "dialogues", filename + ".toml") + fpath = os.path.join(basepath, _TOML_FOLDER_NAME, filename + ".toml") with open(fpath, mode="r") as file: f = file.read() + # Read TOML file containing a list of resources for the dialogue obj: DialogueTOMLStructure = tomllib.loads(f) # type: ignore assert _DIALOGUE_RESOURCES_KEY in obj - resource_dict: Dict[str, Resource] = {} + # Create resource instances from TOML data and return as a dict for i, resource in enumerate(obj[_DIALOGUE_RESOURCES_KEY]): assert "name" in resource if "type" not in resource: resource["type"] = "Resource" # Create instances of Resource classes (and its subclasses) - resource_dict[resource["name"]] = RESOURCE_MAP[resource["type"]]( + self._resources[resource["name"]] = RESOURCE_MAP[resource["type"]]( **resource, order_index=i ) - return resource_dict def hotword_activated(self) -> None: self._in_this_dialogue = True @@ -184,28 +186,6 @@ def not_in_dialogue(self) -> bool: """Check if the client is in or wants to start this dialogue""" return not self._in_this_dialogue - def _start_dialogue(self): - """Save client's state as having started this dialogue""" - # New empty dialogue state, with correct dialogue name - self._set_dialogue_state( - { - _DIALOGUE_RESOURCES_KEY: {}, - _DIALOGUE_LAST_INTERACTED_WITH_KEY: datetime.datetime.now(), - _DIALOGUE_EXTRAS_KEY: self._extras, - } - ) - - def update_dialogue_state(self): - """Update the dialogue state for a client""" - # Save resources to client data - self._set_dialogue_state( - { - _DIALOGUE_RESOURCES_KEY: self._resources, - _DIALOGUE_LAST_INTERACTED_WITH_KEY: datetime.datetime.now(), - _DIALOGUE_EXTRAS_KEY: self._extras, - } - ) - @property def current_resource(self) -> Resource: if self._current_resource is None: @@ -215,24 +195,17 @@ def current_resource(self) -> Resource: def get_resource(self, name: str) -> Resource: return self._resources[name] - def get_extras(self) -> Dict[str, Any]: + @property + def extras(self) -> Dict[str, Any]: return self._extras def get_answer( self, answering_functions: AnsweringFunctionMap, result: Any ) -> Optional[AnswerTuple]: - # Executing callbacks - # cbs: Optional[List[_CallbackTupleType]] = self._result.get(_CALLBACK_LOCATION) - # curr_resource = self._resources[_FINAL_RESOURCE_NAME] - # if cbs: - # self._execute_callbacks_postorder(curr_resource, cbs, set()) - self._current_resource = self._find_current_resource() - # if self._error: - # # An error was raised somewhere during the callbacks - # return None self._answering_functions = answering_functions - # Check if dialogue was cancelled + + # Check if dialogue was cancelled # TODO: Change this (have separate cancel method) if self._current_resource.is_cancelled: self._answer_tuple = self._answering_functions[_FINAL_RESOURCE_NAME]( self._current_resource, self, result @@ -253,12 +226,6 @@ def get_answer( self._current_resource, result, set() ) - if self._resources[_FINAL_RESOURCE_NAME].is_confirmed: - # Final callback (performing some operation with the dialogue's data) - # should be called before ending dialogue - self.end_dialogue() - else: - self.update_dialogue_state() return self._answer_tuple def _get_answer_postorder( @@ -276,55 +243,6 @@ def _get_answer_postorder( ) return None - def _execute_callbacks_postorder( - self, - curr_resource: Resource, - cbs: List[_CallbackTupleType], - finished: Set[Resource], - ) -> None: - for resource in self._resource_graph[curr_resource]["children"]: - if resource not in finished: - finished.add(resource) - self._execute_callbacks_postorder(resource, cbs, finished) - - # for filter_func, cb in cbs: - # if filter_func(curr_resource): - # cb(curr_resource, self, self._result) - - # def _get_saved_dialogue_state(self) -> Optional[DialogueStructureType]: - # """Load the dialogue state for a client""" - # cd = self._q.client_data(_DIALOGUE_KEY) - # dialogue_struct: Optional[DialogueStructureType] = None - # if cd: - # ds_str = cd.get(self._dialogue_name) - # if isinstance(ds_str, str) and ds_str != _EMPTY_DIALOGUE_DATA: - # # TODO: Add try-except block - # dialogue_struct = json.loads(ds_str, cls=DialogueJSONDecoder) - # # if dialogue_struct is None: - # # self._in_this_dialogue = False - # # # Return empty DialogueStructureType in case no dialogue state exists - # # dialogue_struct = { - # # _DIALOGUE_NAME_KEY: "", - # # _DIALOGUE_RESOURCES_KEY: {}, - # # _DIALOGUE_LAST_INTERACTED_WITH_KEY: datetime.datetime.now(), - # # _DIALOGUE_EXTRAS_KEY: {}, - # # } - # return dialogue_struct - - def _set_dialogue_state(self, ds: DialogueDBStructure) -> None: - """Save the state of a dialogue for a client""" - # TODO: Add try-except block? - ds_json: str = json.dumps(ds, cls=DialogueJSONEncoder) - # Wrap data before saving dialogue state into client data - # (due to custom JSON serialization) - cd = {self._dialogue_name: ds_json} - # TODO: add datetime stuff - # self._q.set_client_data( - # _DIALOGUE_KEY, - # cast(Any, cd), - # update_in_place=True, - # ) - def set_resource_state(self, resource_name: str, state: ResourceState): """ Set the state of a resource. @@ -373,14 +291,15 @@ def _find_current_resource(self) -> Resource: return curr_res def end_dialogue(self) -> None: - """End the client's current dialogue""" - # TODO: Doesn't allow multiple conversations at once - # (set_client_data overwrites other conversations) + """Set the dialogue as finished (resources and extras set to empty).""" self._resources = {} + self._extras = {} - def serialize_data(self): - """Serialize the dialogue's data""" - # TODO: Add try-except block? + def serialize_data(self) -> Dict[str, str]: + """Serialize the dialogue's data for saving to database""" + if self._resources[_FINAL_RESOURCE_NAME].is_confirmed: + # When final resource is confirmed, the dialogue is over + self.end_dialogue() ds_json: str = json.dumps( { _DIALOGUE_RESOURCES_KEY: self._resources, @@ -391,21 +310,5 @@ def serialize_data(self): ) # Wrap data before saving dialogue state into client data # (due to custom JSON serialization) - cd = {self._dialogue_name: ds_json} - # TODO: add datetime stuff + cd: Dict[str, str] = {self._dialogue_name: ds_json} return cd - - # def set_error(self) -> None: - # self._error = True - - @classmethod # TODO: Fix type hints? - def add_callback( - cls, - result: Any, - filter_func: _FilterFuncType[Resource], - cb: _CallbackType[Resource], - ): - """Add a callback to the callback list""" - if _CALLBACK_LOCATION not in result: - result[_CALLBACK_LOCATION] = [] - result.callbacks.append((filter_func, cb)) diff --git a/queries/theater_module.py b/queries/theater_module.py index 4a92595c..12fc0846 100644 --- a/queries/theater_module.py +++ b/queries/theater_module.py @@ -371,7 +371,7 @@ def _generate_date_answer( dates: list[str] = [] text_dates: list[str] = [] index: int = 0 - extras: Dict[str, Any] = dsm.get_extras() + extras: Dict[str, Any] = dsm.extras if "page_index" in extras: index = extras["page_index"] print("itering shows") @@ -565,7 +565,7 @@ def _generate_row_answer( if resource.is_unfulfilled: if len(available_rows) == 0: dsm.set_resource_state("ShowDateTime", ResourceState.UNFULFILLED) - dsm.get_extras()["page_index"] = 0 + dsm.extras["page_index"] = 0 ans = resource.prompts["not_enough_seats"] if seats == 1: ans = ans.replace("laus", "laust") @@ -897,7 +897,7 @@ def QTheaterTime(node: Node, params: QueryStateDict, result: Result) -> None: def QTheaterMoreDates(node: Node, params: QueryStateDict, result: Result) -> None: dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm if dsm.current_resource.name == "ShowDate": - extras: Dict[str, Any] = dsm.get_extras() + extras: Dict[str, Any] = dsm.extras if "page_index" in extras: extras["page_index"] += 3 else: @@ -907,7 +907,7 @@ def QTheaterMoreDates(node: Node, params: QueryStateDict, result: Result) -> Non def QTheaterPreviousDates(node: Node, params: QueryStateDict, result: Result) -> None: dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm if dsm.current_resource.name == "ShowDate": - extras: Dict[str, Any] = dsm.get_extras() + extras: Dict[str, Any] = dsm.extras if "page_index" in extras: extras["page_index"] = max(extras["page_index"] - 3, 0) else: diff --git a/query.py b/query.py index ff918d81..d02ab32d 100755 --- a/query.py +++ b/query.py @@ -53,7 +53,7 @@ import random from copy import deepcopy from collections import defaultdict -from queries.dialogue import DialogueStateManager +from queries.dialogue import DialogueStateManager as DSM from settings import Settings @@ -644,8 +644,8 @@ def execute_from_tree(self) -> bool: dialogue_name = getattr(processor, "DIALOGUE_NAME", None) if dialogue_name: if dialogue_data is None: - dialogue_data = self.client_data("dialogue") - self._dsm = DialogueStateManager( + dialogue_data = self.client_data(DSM.DIALOGUE_DATA_KEY) + self._dsm = DSM( dialogue_name, dialogue_data.get(dialogue_name) if dialogue_data else None, ) @@ -659,15 +659,10 @@ def execute_from_tree(self) -> bool: # turning it into a QueryStateDict. if self._tree.process_queries(self, self._session, processor): if dialogue_name: - print( - "SAVING DSM STATE FOR {0} {1}".format( - dialogue_name, self._dsm.serialize_data() - ) - ) # Save the dialogue state self.set_client_data( - "dialogue", - self._dsm.serialize_data(), + DSM.DIALOGUE_DATA_KEY, + cast(ClientDataDict, self._dsm.serialize_data()), update_in_place=True, ) # This processor found an answer, which is already stored @@ -870,7 +865,7 @@ def client_version(self) -> Optional[str]: return self._client_version @property - def dsm(self) -> DialogueStateManager: + def dsm(self) -> DSM: assert self._dsm is not None return self._dsm From 29a782780a5bad81d9375543e47d9c4fe1666a26 Mon Sep 17 00:00:00 2001 From: Logi E Date: Thu, 7 Jul 2022 11:38:45 +0000 Subject: [PATCH 191/371] Fixed datetime bug and removed assert --- queries/dialogue.py | 3 +-- queries/resources.py | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index b76102b5..068b1eec 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -126,10 +126,9 @@ def setup_dialogue(self) -> None: """ Load dialogue resources from TOML file and update their state from database data. """ - assert self._saved_state self._initialize_resources(self._dialogue_name) for rname, resource in self._resources.items(): - if rname in self._saved_state["resources"]: + if self._saved_state and rname in self._saved_state["resources"]: # Update empty resource with data from database resource.update(self._saved_state[_DIALOGUE_RESOURCES_KEY][rname]) # Change from int to enum type diff --git a/queries/resources.py b/queries/resources.py index 1a0d9177..900b41af 100644 --- a/queries/resources.py +++ b/queries/resources.py @@ -285,27 +285,27 @@ def default(self, o: Any) -> Any: del d[key] d["__type__"] = o.__class__.__name__ return d - if isinstance(o, datetime.date): + if isinstance(o, datetime.datetime): return { - "__type__": "date", + "__type__": "datetime", "year": o.year, "month": o.month, "day": o.day, - } - if isinstance(o, datetime.time): - return { - "__type__": "time", "hour": o.hour, "minute": o.minute, "second": o.second, "microsecond": o.microsecond, } - if isinstance(o, datetime.datetime): + if isinstance(o, datetime.date): return { - "__type__": "datetime", + "__type__": "date", "year": o.year, "month": o.month, "day": o.day, + } + if isinstance(o, datetime.time): + return { + "__type__": "time", "hour": o.hour, "minute": o.minute, "second": o.second, @@ -324,10 +324,10 @@ def dialogue_decoding(self, d: Dict[Any, Any]) -> Any: if "__type__" not in d: return d t = d.pop("__type__") + if t == "datetime": + return datetime.datetime(**d) if t == "date": return datetime.date(**d) if t == "time": return datetime.time(**d) - if t == "datetime": - return datetime.datetime(**d) return RESOURCE_MAP[t](**d) From 49b83960e098336a2e0c25d043171a1e877f7d10 Mon Sep 17 00:00:00 2001 From: Logi E Date: Thu, 7 Jul 2022 11:54:44 +0000 Subject: [PATCH 192/371] Added type hints and reset self._dsm before each module --- query.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/query.py b/query.py index d02ab32d..29674b56 100755 --- a/query.py +++ b/query.py @@ -401,6 +401,8 @@ def __init__( # Query context, which is None until fetched via self.fetch_context() # This should be a dict that can be represented in JSON self._context: Optional[ContextDict] = None + # Dialogue state manager, used for dialogue modules + self._dsm: Optional[DSM] = None def _preprocess_query_string(self, q: str) -> str: """Preprocess the query string prior to further analysis""" @@ -640,16 +642,19 @@ def execute_from_tree(self) -> bool: for processor in self._tree_processors: self._error = None self._qtype = None + self._dsm = None # Check if processor is a dialogue module - dialogue_name = getattr(processor, "DIALOGUE_NAME", None) + dialogue_name: Optional[str] = getattr(processor, "DIALOGUE_NAME", None) if dialogue_name: + # Processor uses dialogue functionality, + # initialize dialogue state manager if dialogue_data is None: + # Fetch saved dialogue state dialogue_data = self.client_data(DSM.DIALOGUE_DATA_KEY) self._dsm = DSM( dialogue_name, dialogue_data.get(dialogue_name) if dialogue_data else None, ) - print("INITIALIZED DSM FOR {0}".format(dialogue_name)) # Process the tree, which has only one sentence, but may # have multiple matching query nonterminals # (children of Query in the grammar) @@ -659,7 +664,9 @@ def execute_from_tree(self) -> bool: # turning it into a QueryStateDict. if self._tree.process_queries(self, self._session, processor): if dialogue_name: - # Save the dialogue state + assert self._dsm is not None + # Save the dialogue state when a dialogue module query + # is successfully processed self.set_client_data( DSM.DIALOGUE_DATA_KEY, cast(ClientDataDict, self._dsm.serialize_data()), From ef990a8f3eaea4b7cab668efa38d7543751d0770 Mon Sep 17 00:00:00 2001 From: Logi E Date: Thu, 7 Jul 2022 12:14:54 +0000 Subject: [PATCH 193/371] Renaming methods and constants, added comments --- queries/dialogue.py | 49 ++++++++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index 068b1eec..d4223cf6 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -38,12 +38,12 @@ # TODO: Add try-except blocks where appropriate _TOML_FOLDER_NAME = "dialogues" -_DIALOGUE_EXPIRATION_TIME = 30 * 60 # 30 minutes (dialogue expires after 30 minutes) +_EXPIRATION_TIME = 30 * 60 # 30 minutes (dialogue expires after 30 minutes) # Keys for accessing saved client data for dialogues -_DIALOGUE_RESOURCES_KEY = "resources" -_DIALOGUE_LAST_INTERACTED_WITH_KEY = "last_interacted_with" -_DIALOGUE_EXTRAS_KEY = "extras" +_RESOURCES_KEY = "resources" +_LAST_INTERACTED_WITH_KEY = "last_interacted_with" +_EXTRAS_KEY = "extras" _FINAL_RESOURCE_NAME = "Final" # Generic resource type @@ -110,32 +110,33 @@ def __init__(self, dialogue_name: str, saved_state: Optional[str] = None): DialogueDBStructure, json.loads(saved_state, cls=DialogueJSONDecoder) ) time_from_last_interaction = ( - datetime.datetime.now() - self._saved_state["last_interacted_with"] + datetime.datetime.now() - self._saved_state[_LAST_INTERACTED_WITH_KEY] ) # Check that we have saved data for this dialogue and that it is not expired if ( - self._saved_state[_DIALOGUE_RESOURCES_KEY] - and time_from_last_interaction.total_seconds() - < _DIALOGUE_EXPIRATION_TIME + self._saved_state[_RESOURCES_KEY] + and time_from_last_interaction.total_seconds() < _EXPIRATION_TIME ): self._in_this_dialogue = True - self.setup_dialogue() + self.setup_resources() # TODO: IF EXPIRED DO SOMETHING - def setup_dialogue(self) -> None: + def setup_resources(self) -> None: """ Load dialogue resources from TOML file and update their state from database data. """ + # Fetch empty resources from TOML self._initialize_resources(self._dialogue_name) + # Update empty resources with data from database for rname, resource in self._resources.items(): if self._saved_state and rname in self._saved_state["resources"]: - # Update empty resource with data from database - resource.update(self._saved_state[_DIALOGUE_RESOURCES_KEY][rname]) + resource.update(self._saved_state[_RESOURCES_KEY][rname]) # Change from int to enum type resource.state = ResourceState(resource.state) - if self._saved_state and _DIALOGUE_EXTRAS_KEY in self._saved_state: - self._extras = self._saved_state.get(_DIALOGUE_EXTRAS_KEY) or self._extras - + # Set extra data from database + if self._saved_state and _EXTRAS_KEY in self._saved_state: + self._extras = self._saved_state.get(_EXTRAS_KEY) or self._extras + # Create resource dependency relationship graph self._initialize_resource_graph() def _initialize_resource_graph(self) -> None: @@ -166,9 +167,9 @@ def _initialize_resources(self, filename: str) -> None: f = file.read() # Read TOML file containing a list of resources for the dialogue obj: DialogueTOMLStructure = tomllib.loads(f) # type: ignore - assert _DIALOGUE_RESOURCES_KEY in obj + assert _RESOURCES_KEY in obj # Create resource instances from TOML data and return as a dict - for i, resource in enumerate(obj[_DIALOGUE_RESOURCES_KEY]): + for i, resource in enumerate(obj[_RESOURCES_KEY]): assert "name" in resource if "type" not in resource: resource["type"] = "Resource" @@ -179,7 +180,13 @@ def _initialize_resources(self, filename: str) -> None: def hotword_activated(self) -> None: self._in_this_dialogue = True - self.setup_dialogue() + self.setup_resources() + + def pause_dialogue(self) -> None: + ... # TODO + + def resume_dialogue(self) -> None: + ... # TODO def not_in_dialogue(self) -> bool: """Check if the client is in or wants to start this dialogue""" @@ -301,9 +308,9 @@ def serialize_data(self) -> Dict[str, str]: self.end_dialogue() ds_json: str = json.dumps( { - _DIALOGUE_RESOURCES_KEY: self._resources, - _DIALOGUE_LAST_INTERACTED_WITH_KEY: datetime.datetime.now(), - _DIALOGUE_EXTRAS_KEY: self._extras, + _RESOURCES_KEY: self._resources, + _LAST_INTERACTED_WITH_KEY: datetime.datetime.now(), + _EXTRAS_KEY: self._extras, }, cls=DialogueJSONEncoder, ) From e4ac0c3f9988f70960e31ab6a8a4af44c2b9e648 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Thu, 7 Jul 2022 12:16:11 +0000 Subject: [PATCH 194/371] Removing callbacks from fruitseller --- queries/dialogues/fruitseller.toml | 1 + queries/fruitseller_module.py | 214 ++++++++++++----------------- queries/theater_module.py | 7 +- 3 files changed, 96 insertions(+), 126 deletions(-) diff --git a/queries/dialogues/fruitseller.toml b/queries/dialogues/fruitseller.toml index 1fff3df2..cbccec04 100644 --- a/queries/dialogues/fruitseller.toml +++ b/queries/dialogues/fruitseller.toml @@ -18,6 +18,7 @@ type = "TimeResource" [[resources]] name = "DateTime" +type = "WrapperResource" requires = ["Date", "Time"] prompts.initial = "Hvenær viltu fá ávextina?" prompts.time_fulfilled = "Afhendingin verður klukkan {time}. Hvaða dagsetningu viltu fá ávextina?" diff --git a/queries/fruitseller_module.py b/queries/fruitseller_module.py index 9edf8029..8a5913e8 100644 --- a/queries/fruitseller_module.py +++ b/queries/fruitseller_module.py @@ -1,4 +1,4 @@ -from typing import Any, Callable, List, Optional, cast +from typing import Any, List, Optional, cast import json import logging import datetime @@ -9,13 +9,16 @@ from queries import gen_answer, AnswerTuple, parse_num, natlang_seq, sing_or_plur from queries.dialogue import ( AnsweringFunctionMap, + DialogueStateManager, +) +from queries.resources import ( DateResource, - FinalResource, ListResource, Resource, ResourceState, - DialogueStateManager, TimeResource, + FinalResource, + WrapperResource, ) # Indicate that this module wants to handle parse trees for queries, @@ -146,9 +149,8 @@ def _generate_fruit_answer( - resource: ListResource, dsm: DialogueStateManager + resource: ListResource, dsm: DialogueStateManager, result: Result ) -> Optional[AnswerTuple]: - result = dsm.get_result() if result.get("fruitsEmpty"): return gen_answer(resource.prompts["empty"]) if result.get("fruitOptions"): @@ -172,7 +174,7 @@ def _generate_fruit_answer( def _generate_datetime_answer( - resource: Resource, dsm: DialogueStateManager + resource: Resource, dsm: DialogueStateManager, result: Result ) -> Optional[AnswerTuple]: ans: Optional[str] = None date_resource: DateResource = cast(DateResource, dsm.get_resource("Date")) @@ -202,13 +204,13 @@ def _generate_datetime_answer( def _generate_final_answer( - resource: FinalResource, dsm: DialogueStateManager + resource: FinalResource, dsm: DialogueStateManager, result: Result ) -> Optional[AnswerTuple]: ans: Optional[str] = None if resource.is_cancelled: return gen_answer(resource.prompts["cancelled"]) - resource.state = ResourceState.CONFIRMED + dsm.set_resource_state(resource.name, ResourceState.CONFIRMED) date_resource = dsm.get_resource("Date") time_resource = dsm.get_resource("Time") ans = resource.prompts["final"].format( @@ -235,65 +237,49 @@ def QFruitStartQuery(node: Node, params: QueryStateDict, result: Result): def QAddFruitQuery(node: Node, params: QueryStateDict, result: Result): - def _add_fruit( - resource: Resource, dsm: DialogueStateManager, result: Result - ) -> None: - if resource.data is None: - resource.data = [] - query_fruit_index = 0 - while query_fruit_index < len(result.queryfruits): - (number, name) = result.queryfruits[query_fruit_index] - added = False - for index, (fruit_number, fruit_name) in enumerate(resource.data): - if fruit_name == name: - resource.data[index] = (number + fruit_number, name) - added = True - break - if not added: - resource.data.append((number, name)) - query_fruit_index += 1 - resource.state = ResourceState.PARTIALLY_FULFILLED - result.qtype = "QAddFruitQuery" - DialogueStateManager.add_callback(result, lambda r: r.name == "Fruits", _add_fruit) + dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm + resource: ListResource = cast(ListResource, dsm.get_resource("Fruits")) + if resource.data is None: + resource.data = [] + query_fruit_index = 0 + while query_fruit_index < len(result.queryfruits): + (number, name) = result.queryfruits[query_fruit_index] + added = False + for index, (fruit_number, fruit_name) in enumerate(resource.data): + if fruit_name == name: + resource.data[index] = (number + fruit_number, name) + added = True + break + if not added: + resource.data.append((number, name)) + query_fruit_index += 1 + dsm.set_resource_state(resource.name, ResourceState.PARTIALLY_FULFILLED) def QRemoveFruitQuery(node: Node, params: QueryStateDict, result: Result): - def _remove_fruit( - resource: Resource, dsm: DialogueStateManager, result: Result - ) -> None: - result.actually_removed_something = False - if resource.data is not None: - for _, fruitname in result.queryfruits: - for number, name in resource.data: - if name == fruitname: - resource.data.remove([number, name]) - result.actually_removed_something = True - break - if len(resource.data) == 0: - resource.state = ResourceState.UNFULFILLED - result.fruitsEmpty = True - else: - resource.state = ResourceState.PARTIALLY_FULFILLED - result.qtype = "QRemoveFruitQuery" - DialogueStateManager.add_callback( - result, lambda r: r.name == "Fruits", _remove_fruit - ) + dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm + resource: ListResource = cast(ListResource, dsm.get_resource("Fruits")) + result.actually_removed_something = False + if resource.data is not None: + for _, fruitname in result.queryfruits: + for number, name in resource.data: + if name == fruitname: + resource.data.remove([number, name]) + result.actually_removed_something = True + break + if len(resource.data) == 0: + dsm.set_resource_state(resource.name, ResourceState.UNFULFILLED) + result.fruitsEmpty = True + else: + dsm.set_resource_state(resource.name, ResourceState.PARTIALLY_FULFILLED) def QCancelOrder(node: Node, params: QueryStateDict, result: Result): - def _cancel_order( - resource: Resource, dsm: DialogueStateManager, result: Result - ) -> None: - resource.state = ResourceState.CANCELLED - dsm.end_dialogue() - - result.qtype = "QCancelOrder" - result.answer_key = ("Final", "cancelled") - DialogueStateManager.add_callback( - result, lambda r: r.name == "Final", _cancel_order - ) + dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm + dsm.set_resource_state("Final", ResourceState.CANCELLED) + dsm.end_dialogue() def QFruitOptionsQuery(node: Node, params: QueryStateDict, result: Result): @@ -303,38 +289,28 @@ def QFruitOptionsQuery(node: Node, params: QueryStateDict, result: Result): def QYes(node: Node, params: QueryStateDict, result: Result): - def _parse_yes( - resource: Resource, dsm: DialogueStateManager, result: Result - ) -> None: - if "yes_used" not in result and resource.is_fulfilled: - resource.state = ResourceState.CONFIRMED - result.yes_used = True - if resource.name == "DateTime": - for rname in resource.requires: - dsm.get_resource(rname).state = ResourceState.CONFIRMED result.qtype = "QYes" - DialogueStateManager.add_callback( - result, - lambda r: r.name in ("Fruits", "DateTime") and not r.is_confirmed, - _parse_yes, - ) + dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm + resource = dsm.current_resource + if ( + not resource.is_confirmed and resource.name in ("Fruits", "DateTime") + ) and resource.is_fulfilled: + dsm.set_resource_state(resource.name, ResourceState.CONFIRMED) + if isinstance(resource, WrapperResource): + for rname in resource.requires: + dsm.get_resource(rname).state = ResourceState.CONFIRMED def QNo(node: Node, params: QueryStateDict, result: Result): - def _parse_no( - resource: Resource, dsm: DialogueStateManager, result: Result - ) -> None: - if resource.name == "Fruits": - if resource.is_partially_fulfilled: - resource.state = ResourceState.FULFILLED - elif resource.is_fulfilled: - resource.state = ResourceState.PARTIALLY_FULFILLED - result.qtype = "QNo" - DialogueStateManager.add_callback( - result, lambda r: r.name == "Fruits" and not r.is_confirmed, _parse_no - ) + dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm + resource = dsm.current_resource + if resource.name == "Fruits" and not resource.is_confirmed: + if resource.is_partially_fulfilled: + resource.state = ResourceState.FULFILLED + elif resource.is_fulfilled: + resource.state = ResourceState.PARTIALLY_FULFILLED def QNumOfFruit(node: Node, params: QueryStateDict, result: Result): @@ -360,7 +336,7 @@ def QFruit(node: Node, params: QueryStateDict, result: Result): result.fruit = fruit -def _date_callback( +def _add_date( resource: DateResource, dsm: DialogueStateManager, result: Result ) -> None: if dsm.get_resource("Fruits").is_confirmed: @@ -372,8 +348,6 @@ def _date_callback( datetime_resource.state = ResourceState.FULFILLED else: datetime_resource.state = ResourceState.PARTIALLY_FULFILLED - else: - dsm.set_error() def QFruitDate(node: Node, params: QueryStateDict, result: Result) -> None: @@ -393,15 +367,14 @@ def QFruitDate(node: Node, params: QueryStateDict, result: Result) -> None: if m < now.month or (m == now.month and d < now.day): y += 1 result["delivery_date"] = datetime.date(day=d, month=m, year=y) - - DialogueStateManager.add_callback( - result, lambda r: r.name == "Date", _date_callback - ) + dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm + if dsm.current_resource.name == "DateTime": + _add_date(cast(DateResource, dsm.get_resource("Date")), dsm, result) return raise ValueError("No date in {0}".format(str(datenode))) -def _time_callback( +def _add_time( resource: TimeResource, dsm: DialogueStateManager, result: Result ) -> None: if dsm.get_resource("Fruits").is_confirmed: @@ -413,8 +386,6 @@ def _time_callback( datetime_resource.state = ResourceState.FULFILLED else: datetime_resource.state = ResourceState.PARTIALLY_FULFILLED - else: - dsm.set_error() def QFruitTime(node: Node, params: QueryStateDict, result: Result): @@ -426,10 +397,9 @@ def QFruitTime(node: Node, params: QueryStateDict, result: Result): hour, minute, _ = (int(i) for i in aux_str.split(", ")) if hour in range(0, 24) and minute in range(0, 60): result["delivery_time"] = datetime.time(hour, minute) - - DialogueStateManager.add_callback( - result, lambda r: r.name == "Time", _time_callback - ) + dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm + if dsm.current_resource.name == "DateTime": + _add_time(cast(TimeResource, dsm.get_resource("Time")), dsm, result) else: result["parse_error"] = True @@ -442,19 +412,18 @@ def QFruitDateTime(node: Node, params: QueryStateDict, result: Result) -> None: y, m, d, h, min, _ = (i if i != 0 else None for i in json.loads(datetimenode.aux)) if y is None: y = now.year + dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm if d is not None and m is not None: result["delivery_date"] = datetime.date(y, m, d) if result["delivery_date"] < now.date(): result["delivery_date"].year += 1 - DialogueStateManager.add_callback( - result, lambda r: r.name == "Date", _date_callback - ) + if dsm.current_resource.name == "DateTime": + _add_date(cast(DateResource, dsm.get_resource("Date")), dsm, result) if h is not None and min is not None: result["delivery_time"] = datetime.time(h, min) - DialogueStateManager.add_callback( - result, lambda r: r.name == "Time", _time_callback - ) + if dsm.current_resource.name == "DateTime": + _add_time(cast(TimeResource, dsm.get_resource("Time")), dsm, result) def QFruitInfoQuery(node: Node, params: QueryStateDict, result: Result): @@ -471,30 +440,29 @@ def QFruitInfoQuery(node: Node, params: QueryStateDict, result: Result): def sentence(state: QueryStateDict, result: Result) -> None: """Called when sentence processing is complete""" q: Query = state["query"] - dsm = q._dsm + dsm: DialogueStateManager = q.dsm - if dsm.not_in_dialogue() or result.get("parse_error"): + if dsm.not_in_dialogue(): q.set_error("E_QUERY_NOT_UNDERSTOOD") return # Successfully matched a query type try: - dsm.setup_dialogue(_ANSWERING_FUNCTIONS) - if result.qtype == "QFruitInfo": - # Example info handling functionality - # ans = "Ávaxtapöntunin þín er bara flott. " - # f = dsm.get_resource("Fruits") - # ans += str(f.data) - ans = dsm.get_answer() - if not ans: - print("No answer generated") - q.set_error("E_QUERY_NOT_UNDERSTOOD") - return - - q.set_answer(*ans) - return - - ans = dsm.get_answer() + # if result.qtype == "QFruitInfo": + # # Example info handling functionality + # # ans = "Ávaxtapöntunin þín er bara flott. " + # # f = dsm.get_resource("Fruits") + # # ans += str(f.data) + # ans = dsm.get_answer() + # if not ans: + # print("No answer generated") + # q.set_error("E_QUERY_NOT_UNDERSTOOD") + # return + + # q.set_answer(*ans) + # return + + ans = dsm.get_answer(_ANSWERING_FUNCTIONS, result) if not ans: print("No answer generated") q.set_error("E_QUERY_NOT_UNDERSTOOD") diff --git a/queries/theater_module.py b/queries/theater_module.py index 12fc0846..b7e05f15 100644 --- a/queries/theater_module.py +++ b/queries/theater_module.py @@ -35,6 +35,7 @@ from queries.num import number_to_text, numbers_to_ordinal from queries.resources import ( DateResource, + FinalResource, ListResource, NumberResource, Resource, @@ -662,7 +663,7 @@ def _generate_seat_number_answer( def _generate_final_answer( - resource: ListResource, dsm: DialogueStateManager, result: Result + resource: FinalResource, dsm: DialogueStateManager, result: Result ) -> Optional[AnswerTuple]: if resource.is_cancelled: return gen_answer(resource.prompts["cancelled"]) @@ -1033,7 +1034,7 @@ def QNum(node: Node, params: QueryStateDict, result: Result): def QCancel(node: Node, params: QueryStateDict, result: Result): dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm - dsm.set_resource_state(dsm.get_resource("Final").name, ResourceState.CANCELLED) + dsm.set_resource_state("Final", ResourceState.CANCELLED) dsm.end_dialogue() result.qtype = "QCancel" @@ -1078,7 +1079,7 @@ def QNo(node: Node, params: QueryStateDict, result: Result): dsm.set_resource_state(resource.name, ResourceState.UNFULFILLED) if isinstance(resource, WrapperResource): for rname in resource.requires: - dsm.get_resource(rname).state = ResourceState.UNFULFILLED + dsm.set_resource_state(rname, ResourceState.UNFULFILLED) def QStatus(node: Node, params: QueryStateDict, result: Result): From cb7ad618f00840feb511530e5e7165c80e9cf095 Mon Sep 17 00:00:00 2001 From: Logi E Date: Thu, 7 Jul 2022 12:22:51 +0000 Subject: [PATCH 195/371] Hotfix, only initialize dsm when processor matches nonterminals --- query.py | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/query.py b/query.py index 29674b56..d8f17b43 100755 --- a/query.py +++ b/query.py @@ -298,6 +298,18 @@ def process_queries( # But this processor is not interested in any of the nonterminals # in this query's parse forest: don't waste more cycles on it return False + # Check if processor is a dialogue module + dialogue_name: Optional[str] = getattr(processor, "DIALOGUE_NAME", None) + if dialogue_name: + # Processor uses dialogue functionality, + # initialize dialogue state manager + # TODO: ONLY FETCH DATA ONCE if dialogue_data is None: + # Fetch saved dialogue state + dialogue_data = query.client_data(DSM.DIALOGUE_DATA_KEY) + query._dsm = DSM( + dialogue_name, + cast(str, dialogue_data.get(dialogue_name)) if dialogue_data else None, + ) with self.context(session, processor, query=query) as state: for query_tree in self._query_trees: # Is the processor interested in the root nonterminal @@ -643,18 +655,6 @@ def execute_from_tree(self) -> bool: self._error = None self._qtype = None self._dsm = None - # Check if processor is a dialogue module - dialogue_name: Optional[str] = getattr(processor, "DIALOGUE_NAME", None) - if dialogue_name: - # Processor uses dialogue functionality, - # initialize dialogue state manager - if dialogue_data is None: - # Fetch saved dialogue state - dialogue_data = self.client_data(DSM.DIALOGUE_DATA_KEY) - self._dsm = DSM( - dialogue_name, - dialogue_data.get(dialogue_name) if dialogue_data else None, - ) # Process the tree, which has only one sentence, but may # have multiple matching query nonterminals # (children of Query in the grammar) @@ -663,8 +663,7 @@ def execute_from_tree(self) -> bool: # "query" field of the TreeStateDict is populated, # turning it into a QueryStateDict. if self._tree.process_queries(self, self._session, processor): - if dialogue_name: - assert self._dsm is not None + if self._dsm is not None: # Save the dialogue state when a dialogue module query # is successfully processed self.set_client_data( From 8b93fc695e294c96dfac341ebca0561ec11281ab Mon Sep 17 00:00:00 2001 From: Logi E Date: Thu, 7 Jul 2022 12:27:49 +0000 Subject: [PATCH 196/371] Added hotword activation function call in fruitseller --- queries/fruitseller_module.py | 1 + 1 file changed, 1 insertion(+) diff --git a/queries/fruitseller_module.py b/queries/fruitseller_module.py index 8a5913e8..a80d9e63 100644 --- a/queries/fruitseller_module.py +++ b/queries/fruitseller_module.py @@ -234,6 +234,7 @@ def _list_items(items: Any) -> str: def QFruitStartQuery(node: Node, params: QueryStateDict, result: Result): result.qtype = _START_DIALOGUE_QTYPE + cast(QueryStateDict, result.state)["query"].dsm.hotword_activated() def QAddFruitQuery(node: Node, params: QueryStateDict, result: Result): From 0b64874cfd416a965ce3b0003f7bdf667d92a1f0 Mon Sep 17 00:00:00 2001 From: Logi E Date: Thu, 7 Jul 2022 13:33:08 +0000 Subject: [PATCH 197/371] Fixed hotword bug? --- queries/dialogue.py | 30 ++++++++++++++++++------------ query.py | 10 +++++++++- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index d4223cf6..1f1d19d4 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -38,12 +38,7 @@ # TODO: Add try-except blocks where appropriate _TOML_FOLDER_NAME = "dialogues" -_EXPIRATION_TIME = 30 * 60 # 30 minutes (dialogue expires after 30 minutes) - -# Keys for accessing saved client data for dialogues -_RESOURCES_KEY = "resources" -_LAST_INTERACTED_WITH_KEY = "last_interacted_with" -_EXTRAS_KEY = "extras" +_EXPIRATION_TIME = 30 * 60 # a dialogue expires after 30 minutes _FINAL_RESOURCE_NAME = "Final" # Generic resource type @@ -63,17 +58,29 @@ class ResourceGraphItem(TypedDict): + """Type for a node in the resource graph.""" + children: List[Resource] parents: List[Resource] +# Dependency relationship graph type for resources ResourceGraph = Dict[Resource, ResourceGraphItem] class DialogueTOMLStructure(TypedDict): + """Structure of a dialogue TOML file.""" + resources: List[Dict[str, Any]] +# Keys for accessing saved client data for dialogues +# (must match typed dict attributes below) +_RESOURCES_KEY = "resources" +_MODIFIED_KEY = "modified" +_EXTRAS_KEY = "extras" + + class DialogueDBStructure(TypedDict): """ Representation of the dialogue structure, @@ -81,7 +88,7 @@ class DialogueDBStructure(TypedDict): """ resources: Dict[str, Resource] - last_interacted_with: datetime.datetime + modified: datetime.datetime extras: Dict[str, Any] @@ -110,7 +117,7 @@ def __init__(self, dialogue_name: str, saved_state: Optional[str] = None): DialogueDBStructure, json.loads(saved_state, cls=DialogueJSONDecoder) ) time_from_last_interaction = ( - datetime.datetime.now() - self._saved_state[_LAST_INTERACTED_WITH_KEY] + datetime.datetime.now() - self._saved_state[_MODIFIED_KEY] ) # Check that we have saved data for this dialogue and that it is not expired if ( @@ -167,10 +174,10 @@ def _initialize_resources(self, filename: str) -> None: f = file.read() # Read TOML file containing a list of resources for the dialogue obj: DialogueTOMLStructure = tomllib.loads(f) # type: ignore - assert _RESOURCES_KEY in obj + assert _RESOURCES_KEY in obj, f"No resources found in TOML file {f}" # Create resource instances from TOML data and return as a dict for i, resource in enumerate(obj[_RESOURCES_KEY]): - assert "name" in resource + assert "name" in resource, f"Name missing for resource {i+1}" if "type" not in resource: resource["type"] = "Resource" # Create instances of Resource classes (and its subclasses) @@ -224,7 +231,6 @@ def get_answer( ans = self._answering_functions[self._current_resource.name]( self._current_resource, self, result ) - print("GENERATED DATE ANSWERRRRRRRRRRRRRRRRR") return ans # Iterate through resources (inorder traversal) # until one generates an answer @@ -309,7 +315,7 @@ def serialize_data(self) -> Dict[str, str]: ds_json: str = json.dumps( { _RESOURCES_KEY: self._resources, - _LAST_INTERACTED_WITH_KEY: datetime.datetime.now(), + _MODIFIED_KEY: datetime.datetime.now(), _EXTRAS_KEY: self._extras, }, cls=DialogueJSONEncoder, diff --git a/query.py b/query.py index d8f17b43..64a3ba5d 100755 --- a/query.py +++ b/query.py @@ -298,14 +298,22 @@ def process_queries( # But this processor is not interested in any of the nonterminals # in this query's parse forest: don't waste more cycles on it return False - # Check if processor is a dialogue module dialogue_name: Optional[str] = getattr(processor, "DIALOGUE_NAME", None) if dialogue_name: # Processor uses dialogue functionality, # initialize dialogue state manager + hotword_nonterminals: Set[str] = getattr( + processor, "HOTWORD_NONTERMINALS", set() + ) # TODO: ONLY FETCH DATA ONCE if dialogue_data is None: # Fetch saved dialogue state dialogue_data = query.client_data(DSM.DIALOGUE_DATA_KEY) + if self.query_nonterminals.isdisjoint(hotword_nonterminals) and ( + dialogue_data is None or dialogue_data.get(dialogue_name) is None + ): + # Query doesn't contain dialogue hotwords + # and has no saved data for this dialogue + return False query._dsm = DSM( dialogue_name, cast(str, dialogue_data.get(dialogue_name)) if dialogue_data else None, From 2d06a758a6a4d7734fbb882be341eedab52957ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Thu, 7 Jul 2022 15:07:20 +0000 Subject: [PATCH 198/371] added set_answer into dsm and used that to move some specific prompts into the non-terminal functions instead of the answer funcs --- queries/dialogue.py | 5 + queries/theater_module.py | 423 +++++++++++++++++++++++++++----------- 2 files changed, 312 insertions(+), 116 deletions(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index d4223cf6..65350cb4 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -208,6 +208,8 @@ def extras(self) -> Dict[str, Any]: def get_answer( self, answering_functions: AnsweringFunctionMap, result: Any ) -> Optional[AnswerTuple]: + if self._answer_tuple is not None: + return self._answer_tuple self._current_resource = self._find_current_resource() self._answering_functions = answering_functions @@ -249,6 +251,9 @@ def _get_answer_postorder( ) return None + def set_answer(self, answer: AnswerTuple) -> None: + self._answer_tuple = answer + def set_resource_state(self, resource_name: str, state: ResourceState): """ Set the state of a resource. diff --git a/queries/theater_module.py b/queries/theater_module.py index b7e05f15..370b68b9 100644 --- a/queries/theater_module.py +++ b/queries/theater_module.py @@ -337,26 +337,26 @@ class ShowType(TypedDict): def _generate_show_answer( resource: ListResource, dsm: DialogueStateManager, result: Result ) -> Optional[AnswerTuple]: - if (not resource.is_confirmed and result.get("options_info")) or result.get( - "show_options" - ): - shows: list[str] = [] - for show in _SHOWS: - shows.append("\n - " + show["title"]) - ans = resource.prompts["options"] - if len(shows) == 1: - ans = ans.replace("Sýningarnar", "Sýningin", 1).replace("eru", "er", 2) - text_ans = ans.format(options="".join(shows)) - voice_ans = ans.format(options=natlang_seq(shows)).replace("-", "") - return (dict(answer=text_ans), text_ans, voice_ans) - if result.get("no_show_matched"): - return gen_answer(resource.prompts["no_show_matched"]) - if result.get("no_show_matched_data_exists"): - return gen_answer( - resource.prompts["no_show_matched_data_exists"].format( - show=resource.data[0] - ) - ) + # if (not resource.is_confirmed and result.get("options_info")) or result.get( + # "show_options" + # ): + # shows: list[str] = [] + # for show in _SHOWS: + # shows.append("\n - " + show["title"]) + # ans = resource.prompts["options"] + # if len(shows) == 1: + # ans = ans.replace("Sýningarnar", "Sýningin", 1).replace("eru", "er", 2) + # text_ans = ans.format(options="".join(shows)) + # voice_ans = ans.format(options=natlang_seq(shows)).replace("-", "") + # return (dict(answer=text_ans), text_ans, voice_ans) + # if result.get("no_show_matched"): + # return gen_answer(resource.prompts["no_show_matched"]) + # if result.get("no_show_matched_data_exists"): + # return gen_answer( + # resource.prompts["no_show_matched_data_exists"].format( + # show=resource.data[0] + # ) + # ) if resource.is_unfulfilled: return gen_answer(resource.prompts["initial"]) if resource.is_fulfilled: @@ -402,39 +402,39 @@ def _generate_date_answer( index = max(0, len(dates) - 3) extras["page_index"] = index - if (not resource.is_confirmed and result.get("options_info")) or result.get( - "date_options" - ): - options_string = ( - start_string + natlang_seq(dates[index : index + date_number]) - ).replace("dagur", "dagurinn") - text_options_string = start_string + "".join( - text_dates[index : index + date_number] - ) - if len(dates) > 0: - ans = resource.prompts["options"] - if date_number == 1: - ans = ans.replace("eru", "er", 1).replace( - "dagsetningar", "dagsetning", 1 - ) - voice_ans = ans.format( - options=options_string, - date_number=number_to_text(len(dates), gender="kvk"), - ).replace("\n", _BREAK_SSML) - text_ans = ans.format( - options=text_options_string, - date_number=number_to_text(len(dates), gender="kvk"), - ) - - return (dict(answer=text_ans), text_ans, numbers_to_ordinal(voice_ans)) - else: - return gen_answer(resource.prompts["no_date_available"].format(show=title)) - if result.get("no_date_matched"): - return gen_answer(resource.prompts["no_date_matched"]) - if result.get("no_time_matched"): - return gen_answer(resource.prompts["no_time_matched"]) - if result.get("many_matching_times"): - return gen_answer(resource.prompts["many_matching_times"]) + # if (not resource.is_confirmed and result.get("options_info")) or result.get( + # "date_options" + # ): + # options_string = ( + # start_string + natlang_seq(dates[index : index + date_number]) + # ).replace("dagur", "dagurinn") + # text_options_string = start_string + "".join( + # text_dates[index : index + date_number] + # ) + # if len(dates) > 0: + # ans = resource.prompts["options"] + # if date_number == 1: + # ans = ans.replace("eru", "er", 1).replace( + # "dagsetningar", "dagsetning", 1 + # ) + # voice_ans = ans.format( + # options=options_string, + # date_number=number_to_text(len(dates), gender="kvk"), + # ).replace("\n", _BREAK_SSML) + # text_ans = ans.format( + # options=text_options_string, + # date_number=number_to_text(len(dates), gender="kvk"), + # ) + + # return (dict(answer=text_ans), text_ans, numbers_to_ordinal(voice_ans)) + # else: + # return gen_answer(resource.prompts["no_date_available"].format(show=title)) + # if result.get("no_date_matched"): + # return gen_answer(resource.prompts["no_date_matched"]) + # if result.get("no_time_matched"): + # return gen_answer(resource.prompts["no_time_matched"]) + # if result.get("many_matching_times"): + # return gen_answer(resource.prompts["many_matching_times"]) if resource.is_partially_fulfilled: show_date: Optional[datetime.date] = cast( DateResource, dsm.get_resource("ShowDate") @@ -506,8 +506,8 @@ def _generate_date_answer( def _generate_seat_count_answer( resource: NumberResource, dsm: DialogueStateManager, result: Result ) -> Optional[AnswerTuple]: - if result.get("invalid_seat_count"): - return gen_answer(resource.prompts["invalid_seat_count"]) + # if result.get("invalid_seat_count"): + # return gen_answer(resource.prompts["invalid_seat_count"]) if resource.is_unfulfilled: return gen_answer(resource.prompts["initial"]) if resource.is_fulfilled: @@ -543,26 +543,26 @@ def _generate_row_answer( else: checking_row = row seats_in_row = 1 - if (not resource.is_confirmed and result.get("options_info")) or result.get( - "row_options" - ): - ans = resource.prompts["options"] - if len(available_rows) == 1: - ans = ans.replace("eru", "er").replace("Raðir", "Röð") - if seats == 1: - ans = ans.replace("laus", "laust") - text_ans = ans.format(rows=natlang_seq(text_available_rows), seats=seats) - voice_ans = ans.format( - rows=natlang_seq(available_rows), seats=number_to_text(seats) - ) - return (dict(answer=text_ans), text_ans, voice_ans) - if result.get("no_row_matched"): - ans = resource.prompts["no_row_matched"] - if seats == 1: - ans = ans.replace("laus", "laust") - text_ans = ans.format(seats=seats) - voice_ans = ans.format(seats=number_to_text(seats)) - return (dict(answer=text_ans), text_ans, voice_ans) + # if (not resource.is_confirmed and result.get("options_info")) or result.get( + # "row_options" + # ): + # ans = resource.prompts["options"] + # if len(available_rows) == 1: + # ans = ans.replace("eru", "er").replace("Raðir", "Röð") + # if seats == 1: + # ans = ans.replace("laus", "laust") + # text_ans = ans.format(rows=natlang_seq(text_available_rows), seats=seats) + # voice_ans = ans.format( + # rows=natlang_seq(available_rows), seats=number_to_text(seats) + # ) + # return (dict(answer=text_ans), text_ans, voice_ans) + # if result.get("no_row_matched"): + # ans = resource.prompts["no_row_matched"] + # if seats == 1: + # ans = ans.replace("laus", "laust") + # text_ans = ans.format(seats=seats) + # voice_ans = ans.format(seats=number_to_text(seats)) + # return (dict(answer=text_ans), text_ans, voice_ans) if resource.is_unfulfilled: if len(available_rows) == 0: dsm.set_resource_state("ShowDateTime", ResourceState.UNFULFILLED) @@ -605,32 +605,32 @@ def _generate_seat_number_answer( if chosen_row == row: text_available_seats.append(str(seat)) available_seats.append(number_to_text(seat)) - if (not resource.is_confirmed and result.get("options_info")) or result.get( - "seat_options" - ): - ans = resource.prompts["options"] - if len(available_seats) == 1: - ans = ans.replace("Sætin", "Sætið", 1).replace("eru", "er", 2) - text_ans = ans.format(row=chosen_row, options=natlang_seq(text_available_seats)) - voice_ans = ans.format( - row=number_to_text(chosen_row), options=natlang_seq(available_seats) - ) - return (dict(answer=text_ans), text_ans, voice_ans) - if result.get("wrong_number_seats_selected"): - if len(result.get("numbers")) > 1: - chosen_seats = len( - range(result.get("numbers")[0], result.get("numbers")[1] + 1) - ) - else: - chosen_seats = 1 - ans = resource.prompts["wrong_number_seats_selected"] - text_ans = ans.format(chosen_seats=chosen_seats, seats=seats) - voice_ans = ans.format( - chosen_seats=number_to_text(chosen_seats), seats=number_to_text(seats) - ) - return (dict(answer=text_ans), text_ans, voice_ans) - if result.get("seats_unavailable"): - return gen_answer(resource.prompts["seats_unavailable"]) + # if (not resource.is_confirmed and result.get("options_info")) or result.get( + # "seat_options" + # ): + # ans = resource.prompts["options"] + # if len(available_seats) == 1: + # ans = ans.replace("Sætin", "Sætið", 1).replace("eru", "er", 2) + # text_ans = ans.format(row=chosen_row, options=natlang_seq(text_available_seats)) + # voice_ans = ans.format( + # row=number_to_text(chosen_row), options=natlang_seq(available_seats) + # ) + # return (dict(answer=text_ans), text_ans, voice_ans) + # if result.get("wrong_number_seats_selected"): + # if len(result.get("numbers")) > 1: + # chosen_seats = len( + # range(result.get("numbers")[0], result.get("numbers")[1] + 1) + # ) + # else: + # chosen_seats = 1 + # ans = resource.prompts["wrong_number_seats_selected"] + # text_ans = ans.format(chosen_seats=chosen_seats, seats=seats) + # voice_ans = ans.format( + # chosen_seats=number_to_text(chosen_seats), seats=number_to_text(seats) + # ) + # return (dict(answer=text_ans), text_ans, voice_ans) + # if result.get("seats_unavailable"): + # return gen_answer(resource.prompts["seats_unavailable"]) if resource.is_unfulfilled: ans = resource.prompts["initial"] if len(available_seats) == 1: @@ -738,9 +738,17 @@ def QTheaterShowQuery(node: Node, params: QueryStateDict, result: Result) -> Non break if not show_exists: if resource.is_unfulfilled: - result.no_show_matched = True + dsm.set_answer(gen_answer(resource.prompts["no_show_matched"])) + # result.no_show_matched = True if resource.is_fulfilled: - result.no_show_matched_data_exists = True + dsm.set_answer( + gen_answer( + resource.prompts["no_show_matched_data_exists"].format( + show=resource.data[0] + ) + ) + ) + # result.no_show_matched_data_exists = True def _add_date( @@ -766,7 +774,8 @@ def _add_date( if resource.date == date.date(): show_times.append(date.time()) if len(show_times) == 0: - result.no_date_matched = True + dsm.set_answer(gen_answer(datetime_resource.prompts["no_date_matched"])) + # result.no_date_matched = True return if len(show_times) == 1: time_resource.set_time(show_times[0]) @@ -815,7 +824,12 @@ def _add_time( if first_matching_date is None: first_matching_date = date else: - result.many_matching_times = True + dsm.set_answer( + gen_answer( + datetime_resource.prompts["many_matching_times"] + ) + ) + # result.many_matching_times = True return if first_matching_date is not None: date_resource: DateResource = cast( @@ -827,7 +841,8 @@ def _add_time( dsm.set_resource_state(resource.name, ResourceState.FULFILLED) dsm.set_resource_state(datetime_resource.name, ResourceState.FULFILLED) if first_matching_date is None: - result.no_time_matched = True + dsm.set_answer(gen_answer(datetime_resource.prompts["no_time_matched"])) + # result.no_time_matched = True def QTheaterDateTime(node: Node, params: QueryStateDict, result: Result) -> None: @@ -920,14 +935,15 @@ def QTheaterShowSeatCountQuery( ) -> None: dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm if dsm.get_resource("ShowDateTime").is_confirmed: + resource: NumberResource = cast( + NumberResource, dsm.get_resource("ShowSeatCount") + ) if result.number > 0: - resource: NumberResource = cast( - NumberResource, dsm.get_resource("ShowSeatCount") - ) resource.data = result.number dsm.set_resource_state(resource.name, ResourceState.FULFILLED) else: - result.invalid_seat_count = True + dsm.set_answer(gen_answer(resource.prompts["invalid_seat_count"])) + # result.invalid_seat_count = True def QTheaterShowRow(node: Node, params: QueryStateDict, result: Result) -> None: @@ -955,7 +971,13 @@ def QTheaterShowRow(node: Node, params: QueryStateDict, result: Result) -> None: dsm.set_resource_state(resource.name, ResourceState.FULFILLED) else: dsm.set_resource_state(resource.name, ResourceState.UNFULFILLED) - result.no_row_matched = True + ans = resource.prompts["no_row_matched"] + if seats == 1: + ans = ans.replace("laus", "laust") + text_ans = ans.format(seats=seats) + voice_ans = ans.format(seats=number_to_text(seats)) + dsm.set_answer((dict(answer=text_ans), text_ans, voice_ans)) + # result.no_row_matched = True def QTheaterShowSeats(node: Node, params: QueryStateDict, result: Result) -> None: @@ -976,7 +998,21 @@ def QTheaterShowSeats(node: Node, params: QueryStateDict, result: Result) -> Non if len(selected_seats) != number_of_seats: resource.data = [] dsm.set_resource_state(resource.name, ResourceState.UNFULFILLED) + result.wrong_number_seats_selected = True + if len(result.get("numbers")) > 1: + chosen_seats = len( + range(result.get("numbers")[0], result.get("numbers")[1] + 1) + ) + else: + chosen_seats = 1 + ans = resource.prompts["wrong_number_seats_selected"] + text_ans = ans.format(chosen_seats=chosen_seats, seats=number_of_seats) + voice_ans = ans.format( + chosen_seats=number_to_text(chosen_seats), + seats=number_to_text(number_of_seats), + ) + dsm.set_answer((dict(answer=text_ans), text_ans, voice_ans)) return for show in _SHOWS: if show["title"] == title: @@ -987,7 +1023,10 @@ def QTheaterShowSeats(node: Node, params: QueryStateDict, result: Result) -> Non else: resource.data = [] dsm.set_resource_state(resource.name, ResourceState.UNFULFILLED) - result.seats_unavailable = True + dsm.set_answer( + gen_answer(resource.prompts["seats_unavailable"]) + ) + # result.seats_unavailable = True return resource.data = [] for seat in seats: @@ -997,23 +1036,167 @@ def QTheaterShowSeats(node: Node, params: QueryStateDict, result: Result) -> Non def QTheaterGeneralOptions(node: Node, params: QueryStateDict, result: Result) -> None: - result.options_info = True + # result.options_info = True + dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm + resource = dsm.current_resource + if resource.name == "Show": + QTheaterShowOptions(node, params, result) + elif resource.name == "ShowDateTime": + QTheaterDateOptions(node, params, result) + elif resource.name == "ShowSeatRow": + QTheaterRowOptions(node, params, result) + elif resource.name == "ShowSeatOptions": + QTheaterSeatOptions(node, params, result) def QTheaterShowOptions(node: Node, params: QueryStateDict, result: Result) -> None: - result.show_options = True + # result.show_options = True + dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm + resource = dsm.current_resource + if resource.name == "Show": + shows: list[str] = [] + for show in _SHOWS: + shows.append("\n - " + show["title"]) + ans = resource.prompts["options"] + if len(shows) == 1: + ans = ans.replace("Sýningarnar", "Sýningin", 1).replace("eru", "er", 2) + text_ans = ans.format(options="".join(shows)) + voice_ans = ans.format(options=natlang_seq(shows)).replace("-", "") + dsm.set_answer((dict(answer=text_ans), text_ans, voice_ans)) def QTheaterDateOptions(node: Node, params: QueryStateDict, result: Result) -> None: - result.date_options = True + # result.date_options = True + dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm + resource = dsm.current_resource + if resource.name == "ShowDateTime": + title = dsm.get_resource("Show").data[0] + dates: list[str] = [] + text_dates: list[str] = [] + index: int = 0 + extras: Dict[str, Any] = dsm.extras + if "page_index" in extras: + index = extras["page_index"] + for show in _SHOWS: + if show["title"] == title: + print("itering dates") + for date in show["date"]: + with changedlocale(category="LC_TIME"): + text_dates.append(date.strftime("\n - %a %d. %b kl. %H:%M")) + dates.append(date.strftime("\n%A %d. %B klukkan %H:%M")) + date_number: int = 3 if len(dates) >= 3 else len(dates) + start_string: str = ( + "Eftirfarandi dagsetning er í boði:" + if date_number == 1 + else "Næstu tvær dagsetningarnar eru:" + if date_number == 2 + else "Næstu þrjár dagsetningarnar eru:" + ) + print("blaaaaa") + if index == 0: + start_string = start_string.replace("Næstu", "Fyrstu", 1) + if len(dates) < 3: + index = 0 + extras["page_index"] = 0 + if index > len(dates) - 3 and len(dates) > 3: + start_string = "Síðustu þrjár dagsetningarnar eru:\n" + index = max(0, len(dates) - 3) + extras["page_index"] = index + options_string = ( + start_string + natlang_seq(dates[index : index + date_number]) + ).replace("dagur", "dagurinn") + text_options_string = start_string + "".join( + text_dates[index : index + date_number] + ) + if len(dates) > 0: + ans = resource.prompts["options"] + if date_number == 1: + ans = ans.replace("eru", "er", 1).replace( + "dagsetningar", "dagsetning", 1 + ) + voice_ans = ans.format( + options=options_string, + date_number=number_to_text(len(dates), gender="kvk"), + ).replace("\n", _BREAK_SSML) + text_ans = ans.format( + options=text_options_string, + date_number=number_to_text(len(dates), gender="kvk"), + ) + + dsm.set_answer( + (dict(answer=text_ans), text_ans, numbers_to_ordinal(voice_ans)) + ) + else: + dsm.set_answer( + gen_answer(resource.prompts["no_date_available"].format(show=title)) + ) def QTheaterRowOptions(node: Node, params: QueryStateDict, result: Result) -> None: - result.row_options = True + # result.row_options = True + dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm + resource = dsm.current_resource + if resource.name == "ShowSeatRow": + title: str = dsm.get_resource("Show").data[0] + seats: int = dsm.get_resource("ShowSeatCount").data + available_rows: list[str] = [] + text_available_rows: list[str] = [] + for show in _SHOWS: + if show["title"] == title: + checking_row: int = 1 + seats_in_row: int = 0 + row_added: int = 0 + for (row, _) in show["location"]: + if checking_row == row and row != row_added: + seats_in_row += 1 + if seats_in_row >= seats: + available_rows.append(number_to_text(row)) + text_available_rows.append(str(row)) + seats_in_row = 0 + row_added = row + else: + checking_row = row + seats_in_row = 1 + ans = resource.prompts["options"] + if len(available_rows) == 1: + ans = ans.replace("eru", "er").replace("Raðir", "Röð") + if seats == 1: + ans = ans.replace("laus", "laust") + text_ans = ans.format(rows=natlang_seq(text_available_rows), seats=seats) + voice_ans = ans.format( + rows=natlang_seq(available_rows), seats=number_to_text(seats) + ) + dsm.set_answer((dict(answer=text_ans), text_ans, voice_ans)) def QTheaterSeatOptions(node: Node, params: QueryStateDict, result: Result) -> None: result.seat_options = True + dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm + resource = dsm.current_resource + if resource.name == "ShowSeatNumber": + title: str = dsm.get_resource("Show").data[0] + chosen_row: int = dsm.get_resource("ShowSeatRow").data[0] + available_seats: list[str] = [] + text_available_seats: list[str] = [] + for show in _SHOWS: + if show["title"] == title: + for (row, seat) in show["location"]: + if chosen_row == row: + text_available_seats.append(str(seat)) + available_seats.append(number_to_text(seat)) + if (not resource.is_confirmed and result.get("options_info")) or result.get( + "seat_options" + ): + ans = resource.prompts["options"] + if len(available_seats) == 1: + ans = ans.replace("Sætin", "Sætið", 1).replace("eru", "er", 2) + text_ans = ans.format( + row=chosen_row, options=natlang_seq(text_available_seats) + ) + voice_ans = ans.format( + row=number_to_text(chosen_row), options=natlang_seq(available_seats) + ) + dsm.set_answer((dict(answer=text_ans), text_ans, voice_ans)) def QTheaterShowName(node: Node, params: QueryStateDict, result: Result) -> None: @@ -1085,6 +1268,14 @@ def QNo(node: Node, params: QueryStateDict, result: Result): def QStatus(node: Node, params: QueryStateDict, result: Result): # TODO: Handle QStatus again with dsm in query result.qtype = "QStatus" + dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm + dsm.set_answer( + gen_answer( + "Leikhúsmiðapöntunin þín gengur bara vel. {0}".format( + dsm.get_answer(_ANSWERING_FUNCTIONS, result) + ) + ) + ) SHOW_URL = "https://leikhusid.is/wp-json/shows/v1/categories/938" From 91f127498a56dcaf66d638b8652be3caec987bc2 Mon Sep 17 00:00:00 2001 From: Logi E Date: Thu, 7 Jul 2022 16:00:27 +0000 Subject: [PATCH 199/371] Some improvements --- queries/dialogue.py | 41 +++++--- .../{fruitseller_module.py => fruitseller.py} | 77 ++++++++------- queries/{theater_module.py => theater.py} | 97 ++++++++++--------- query.py | 93 +++++++++++++----- tree.py | 3 +- 5 files changed, 185 insertions(+), 126 deletions(-) rename queries/{fruitseller_module.py => fruitseller.py} (88%) rename queries/{theater_module.py => theater.py} (95%) diff --git a/queries/dialogue.py b/queries/dialogue.py index eb05207d..1f6a5d3a 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -57,6 +57,10 @@ ################################ +class ResourceNotFoundError(Exception): + ... + + class ResourceGraphItem(TypedDict): """Type for a node in the resource graph.""" @@ -111,6 +115,8 @@ def __init__(self, dialogue_name: str, saved_state: Optional[str] = None): self._resource_graph: ResourceGraph = {} # Database data for this dialogue, if any self._saved_state: Optional[DialogueDBStructure] = None + # Whether this dialogue is finished (successful/cancelled) or not + self._finished: bool = False if isinstance(saved_state, str): self._saved_state = cast( @@ -144,7 +150,9 @@ def setup_resources(self) -> None: if self._saved_state and _EXTRAS_KEY in self._saved_state: self._extras = self._saved_state.get(_EXTRAS_KEY) or self._extras # Create resource dependency relationship graph + print("SETTING UP RESOURCE GRAPH") self._initialize_resource_graph() + print("FINISHED SETTING UP RESOURCE GRAPH") def _initialize_resource_graph(self) -> None: """ @@ -187,7 +195,9 @@ def _initialize_resources(self, filename: str) -> None: def hotword_activated(self) -> None: self._in_this_dialogue = True + print("STARTING RESOURCE SETUP") self.setup_resources() + print("FINISHED RESOURCE SETUP") def pause_dialogue(self) -> None: ... # TODO @@ -307,25 +317,26 @@ def _find_current_resource(self) -> Resource: print("CURRENT RESOURCE:", curr_res) return curr_res - def end_dialogue(self) -> None: - """Set the dialogue as finished (resources and extras set to empty).""" - self._resources = {} - self._extras = {} + def finish_dialogue(self) -> None: + """Set the dialogue as finished.""" + self._finished = True - def serialize_data(self) -> Dict[str, str]: + def serialize_data(self) -> Dict[str, Optional[str]]: """Serialize the dialogue's data for saving to database""" if self._resources[_FINAL_RESOURCE_NAME].is_confirmed: # When final resource is confirmed, the dialogue is over - self.end_dialogue() - ds_json: str = json.dumps( - { - _RESOURCES_KEY: self._resources, - _MODIFIED_KEY: datetime.datetime.now(), - _EXTRAS_KEY: self._extras, - }, - cls=DialogueJSONEncoder, - ) + self.finish_dialogue() + ds_json: Optional[str] = None + if not self._finished: + ds_json = json.dumps( + { + _RESOURCES_KEY: self._resources, + _MODIFIED_KEY: datetime.datetime.now(), + _EXTRAS_KEY: self._extras, + }, + cls=DialogueJSONEncoder, + ) # Wrap data before saving dialogue state into client data # (due to custom JSON serialization) - cd: Dict[str, str] = {self._dialogue_name: ds_json} + cd: Dict[str, Optional[str]] = {self._dialogue_name: ds_json} return cd diff --git a/queries/fruitseller_module.py b/queries/fruitseller.py similarity index 88% rename from queries/fruitseller_module.py rename to queries/fruitseller.py index a80d9e63..1c71c96f 100644 --- a/queries/fruitseller_module.py +++ b/queries/fruitseller.py @@ -24,7 +24,9 @@ # Indicate that this module wants to handle parse trees for queries, # as opposed to simple literal text strings HANDLE_TREE = True + DIALOGUE_NAME = "fruitseller" +HOTWORD_NONTERMINALS = {"QFruitStartQuery"} # The grammar nonterminals this module wants to handle QUERY_NONTERMINALS = {"QFruitSeller"} @@ -33,33 +35,34 @@ GRAMMAR = """ Query → - QFruitSeller '?'? + QFruitStartQuery > QFruitSeller QFruitSeller → - QFruitStartQuery - | QFruitQuery - | QFruitDateQuery - | QFruitInfoQuery + QFruitQuery '?'? + | QFruitDateQuery '?'? + | QFruitInfoQuery '?'? QFruitInfoQuery → "hver"? "er"? "staðan" "á"? "ávaxtapöntuninni"? QFruitStartQuery → - "ávöxtur" | "postur" | "póstur" - | "ég" "vill" "kaupa"? "ávexti" - | "ég" "vil" "kaupa"? "ávexti" - | "mig" "langar" "að" "kaupa" "ávexti" "hjá"? "þér"? - | "mig" "langar" "að" "panta" "ávexti" "hjá"? "þér"? - | "get" "ég" "keypt" "ávexti" "hjá" "þér" + "ávöxtur" '?'? + | "postur" '?'? + | "póstur" '?'? + | "ég" "vill" "kaupa"? "ávexti" '?'? + | "ég" "vil" "kaupa"? "ávexti" '?'? + | "mig" "langar" "að" "kaupa" "ávexti" "hjá"? "þér"? '?'? + | "mig" "langar" "að" "panta" "ávexti" "hjá"? "þér"? '?'? + | "get" "ég" "keypt" "ávexti" "hjá" "þér" '?'? QFruitQuery → QAddFruitQuery | QRemoveFruitQuery | QChangeFruitQuery | QFruitOptionsQuery - | QYes - | QNo - | QCancelOrder + | QFruitYes + | QFruitNo + | QFruitCancelOrder QAddFruitQuery → "já"? "má"? "ég"? "fá"? QFruitList @@ -107,11 +110,11 @@ | "hvaða" "ávexti" "ertu" "með" | "hvaða" "ávextir" "eru" "í" "boði" -QFruitList → QNumOfFruit QNumOfFruit* +QFruitList → QFruitNumOfFruit QFruitNumOfFruit* -QNumOfFruit → QNum? QFruit "og"? +QFruitNumOfFruit → QFruitNum? QFruit "og"? -QNum → +QFruitNum → # to is a declinable number word ('tveir/tvo/tveim/tveggja') # töl is an undeclinable number word ('sautján') # tala is a number ('17') @@ -119,11 +122,11 @@ QFruit → 'banani' | 'epli' | 'pera' | 'appelsína' -QYes → "já" "já"* | "endilega" | "já" "takk" | "játakk" | "já" "þakka" "þér" "fyrir" | "já" "takk" "kærlega" "fyrir"? | "jább" "takk"? +QFruitYes → "já" "já"* | "endilega" | "já" "takk" | "játakk" | "já" "þakka" "þér" "fyrir" | "já" "takk" "kærlega" "fyrir"? | "jább" "takk"? -QNo → "nei" "takk"? | "nei" "nei"* | "neitakk" | "ómögulega" +QFruitNo → "nei" "takk"? | "nei" "nei"* | "neitakk" | "ómögulega" -QCancelOrder → "ég" "hætti" "við" +QFruitCancelOrder → "ég" "hætti" "við" | "ég" "vil" "hætta" "við" "pöntunina"? | "ég" "vill" "hætta" "við" "pöntunina" @@ -234,12 +237,12 @@ def _list_items(items: Any) -> str: def QFruitStartQuery(node: Node, params: QueryStateDict, result: Result): result.qtype = _START_DIALOGUE_QTYPE - cast(QueryStateDict, result.state)["query"].dsm.hotword_activated() + Query.get_dsm(result).hotword_activated() def QAddFruitQuery(node: Node, params: QueryStateDict, result: Result): result.qtype = "QAddFruitQuery" - dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm + dsm: DialogueStateManager = Query.get_dsm(result) resource: ListResource = cast(ListResource, dsm.get_resource("Fruits")) if resource.data is None: resource.data = [] @@ -260,7 +263,7 @@ def QAddFruitQuery(node: Node, params: QueryStateDict, result: Result): def QRemoveFruitQuery(node: Node, params: QueryStateDict, result: Result): result.qtype = "QRemoveFruitQuery" - dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm + dsm: DialogueStateManager = Query.get_dsm(result) resource: ListResource = cast(ListResource, dsm.get_resource("Fruits")) result.actually_removed_something = False if resource.data is not None: @@ -277,10 +280,10 @@ def QRemoveFruitQuery(node: Node, params: QueryStateDict, result: Result): dsm.set_resource_state(resource.name, ResourceState.PARTIALLY_FULFILLED) -def QCancelOrder(node: Node, params: QueryStateDict, result: Result): - dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm +def QFruitCancelOrder(node: Node, params: QueryStateDict, result: Result): + dsm: DialogueStateManager = Query.get_dsm(result) dsm.set_resource_state("Final", ResourceState.CANCELLED) - dsm.end_dialogue() + dsm.finish_dialogue() def QFruitOptionsQuery(node: Node, params: QueryStateDict, result: Result): @@ -289,10 +292,10 @@ def QFruitOptionsQuery(node: Node, params: QueryStateDict, result: Result): result.fruitOptions = True -def QYes(node: Node, params: QueryStateDict, result: Result): +def QFruitYes(node: Node, params: QueryStateDict, result: Result): - result.qtype = "QYes" - dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm + result.qtype = "QFruitYes" + dsm: DialogueStateManager = Query.get_dsm(result) resource = dsm.current_resource if ( not resource.is_confirmed and resource.name in ("Fruits", "DateTime") @@ -303,9 +306,9 @@ def QYes(node: Node, params: QueryStateDict, result: Result): dsm.get_resource(rname).state = ResourceState.CONFIRMED -def QNo(node: Node, params: QueryStateDict, result: Result): - result.qtype = "QNo" - dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm +def QFruitNo(node: Node, params: QueryStateDict, result: Result): + result.qtype = "QFruitNo" + dsm: DialogueStateManager = Query.get_dsm(result) resource = dsm.current_resource if resource.name == "Fruits" and not resource.is_confirmed: if resource.is_partially_fulfilled: @@ -314,7 +317,7 @@ def QNo(node: Node, params: QueryStateDict, result: Result): resource.state = ResourceState.PARTIALLY_FULFILLED -def QNumOfFruit(node: Node, params: QueryStateDict, result: Result): +def QFruitNumOfFruit(node: Node, params: QueryStateDict, result: Result): if "queryfruits" not in result: result["queryfruits"] = [] if "fruitnumber" not in result: @@ -323,7 +326,7 @@ def QNumOfFruit(node: Node, params: QueryStateDict, result: Result): result.queryfruits.append([result.fruitnumber, result.fruit]) -def QNum(node: Node, params: QueryStateDict, result: Result): +def QFruitNum(node: Node, params: QueryStateDict, result: Result): fruitnumber = int(parse_num(node, result._nominative)) if fruitnumber is not None: result.fruitnumber = fruitnumber @@ -368,7 +371,7 @@ def QFruitDate(node: Node, params: QueryStateDict, result: Result) -> None: if m < now.month or (m == now.month and d < now.day): y += 1 result["delivery_date"] = datetime.date(day=d, month=m, year=y) - dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm + dsm: DialogueStateManager = Query.get_dsm(result) if dsm.current_resource.name == "DateTime": _add_date(cast(DateResource, dsm.get_resource("Date")), dsm, result) return @@ -398,7 +401,7 @@ def QFruitTime(node: Node, params: QueryStateDict, result: Result): hour, minute, _ = (int(i) for i in aux_str.split(", ")) if hour in range(0, 24) and minute in range(0, 60): result["delivery_time"] = datetime.time(hour, minute) - dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm + dsm: DialogueStateManager = Query.get_dsm(result) if dsm.current_resource.name == "DateTime": _add_time(cast(TimeResource, dsm.get_resource("Time")), dsm, result) else: @@ -413,7 +416,7 @@ def QFruitDateTime(node: Node, params: QueryStateDict, result: Result) -> None: y, m, d, h, min, _ = (i if i != 0 else None for i in json.loads(datetimenode.aux)) if y is None: y = now.year - dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm + dsm: DialogueStateManager = Query.get_dsm(result) if d is not None and m is not None: result["delivery_date"] = datetime.date(y, m, d) if result["delivery_date"] < now.date(): diff --git a/queries/theater_module.py b/queries/theater.py similarity index 95% rename from queries/theater_module.py rename to queries/theater.py index 370b68b9..a715c455 100644 --- a/queries/theater_module.py +++ b/queries/theater.py @@ -68,28 +68,29 @@ def help_text(lemma: str) -> str: # This module involves dialogue functionality DIALOGUE_NAME = "theater" +HOTWORD_NONTERMINALS = {"QTheaterHotWord"} # The grammar nonterminals this module wants to handle -QUERY_NONTERMINALS = {"QTheater"} +QUERY_NONTERMINALS = {"QTheater", "QTheaterHotWord"} # The context-free grammar for the queries recognized by this plug-in module GRAMMAR = """ Query → - QTheater + QTheaterHotWord > QTheater QTheater → QTheaterQuery '?'? QTheaterQuery → - QTheaterHotWord | QTheaterDialogue + QTheaterDialogue QTheaterHotWord → - QTheaterNames - | QTheaterEgVil? QTheaterKaupaFaraFaPanta "leikhúsmiða" - | QTheaterEgVil? QTheaterKaupaFaraFaPanta "miða" "í" QTheaterNames - | QTheaterEgVil? QTheaterKaupaFaraFaPanta "miða" "á" QTheaterNames "sýningu" - | QTheaterEgVil? QTheaterKaupaFaraFaPanta QTheaterNames - | QTheaterEgVil? QTheaterKaupaFaraFaPanta "leikhússýningu" + QTheaterNames '?'? + | QTheaterEgVil? QTheaterKaupaFaraFaPanta "leikhúsmiða" '?'? + | QTheaterEgVil? QTheaterKaupaFaraFaPanta "miða" "í" QTheaterNames '?'? + | QTheaterEgVil? QTheaterKaupaFaraFaPanta "miða" "á" QTheaterNames "sýningu" '?'? + | QTheaterEgVil? QTheaterKaupaFaraFaPanta QTheaterNames '?'? + | QTheaterEgVil? QTheaterKaupaFaraFaPanta "leikhússýningu" '?'? QTheaterNames → 'leikhús' @@ -114,10 +115,10 @@ def help_text(lemma: str) -> str: | QTheaterShowSeatCountQuery | QTheaterShowLocationQuery | QTheaterOptions - | QYes - | QNo - | QCancel - | QStatus + | QTheaterYes + | QTheaterNo + | QTheaterCancel + | QTheaterStatus QTheaterOptions → QTheaterGeneralOptions @@ -196,7 +197,7 @@ def help_text(lemma: str) -> str: | "sýningartímana" QTheaterShowSeatCountQuery → - QTheaterEgVil? "fá"? QNum "sæti"? + QTheaterEgVil? "fá"? QTheaterNum "sæti"? QTheaterShowLocationQuery → QTheaterShowRow @@ -213,13 +214,13 @@ def help_text(lemma: str) -> str: | "fá" "sæti" "á" QTheaterRodBekkur → - QTheaterRodBekk? "númer"? QNum - | QNum "bekk" - | QNum "röð" + QTheaterRodBekk? "númer"? QTheaterNum + | QTheaterNum "bekk" + | QTheaterNum "röð" QTheaterShowSeats → - QTheaterEgVil? "sæti"? "númer"? QNum "til"? QNum? - | QTheaterEgVil? "sæti"? "númer"? QNum "og"? QNum? + QTheaterEgVil? "sæti"? "númer"? QTheaterNum "til"? QTheaterNum? + | QTheaterEgVil? "sæti"? "númer"? QTheaterNum "og"? QTheaterNum? QTheaterDateOptions → "hvaða" "dagsetningar" "eru" "í" "boði" @@ -232,20 +233,20 @@ def help_text(lemma: str) -> str: | "mig" "langar" "að" | "mig" "langar" "í" -QNum → +QTheaterNum → # to is a declinable number word ('tveir/tvo/tveim/tveggja') # töl is an undeclinable number word ('sautján') # tala is a number ('17') to | töl | tala -QYes → "já" "já"* | "endilega" | "já" "takk" | "játakk" | "já" "þakka" "þér" "fyrir" | "já" "takk" "kærlega" "fyrir"? | "jább" "takk"? +QTheaterYes → "já" "já"* | "endilega" | "já" "takk" | "játakk" | "já" "þakka" "þér" "fyrir" | "já" "takk" "kærlega" "fyrir"? | "jább" "takk"? -QNo → "nei" "takk"? | "nei" "nei"* | "neitakk" | "ómögulega" +QTheaterNo → "nei" "takk"? | "nei" "nei"* | "neitakk" | "ómögulega" -QCancel → "ég" "hætti" "við" +QTheaterCancel → "ég" "hætti" "við" | QTheaterEgVil "hætta" "við" QTheaterPontun? -QStatus → +QTheaterStatus → "staðan" | "hver" "er" "staðan" "á" QTheaterPontun? | "staðan" @@ -722,12 +723,14 @@ def QTheaterDialogue(node: Node, params: QueryStateDict, result: Result) -> None def QTheaterHotWord(node: Node, params: QueryStateDict, result: Result) -> None: result.qtype = _START_DIALOGUE_QTYPE - cast(QueryStateDict, result.state)["query"].dsm.hotword_activated() + print("ACTIVATING THEATER MODULE") + Query.get_dsm(result).hotword_activated() def QTheaterShowQuery(node: Node, params: QueryStateDict, result: Result) -> None: - dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm + dsm: DialogueStateManager = Query.get_dsm(result) selected_show: str = result.show_name + print("SEARCHING FOR A SHOW:", selected_show) resource: ListResource = cast(ListResource, dsm.get_resource("Show")) show_exists = False for show in _SHOWS: @@ -866,7 +869,7 @@ def QTheaterDateTime(node: Node, params: QueryStateDict, result: Result) -> None result["show_time"] = datetime.time(h, min) result["show_date"] = datetime.date(y, m, d) - dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm + dsm: DialogueStateManager = Query.get_dsm(result) _add_date(cast(DateResource, dsm.get_resource("ShowDate")), dsm, result) _add_time(cast(TimeResource, dsm.get_resource("ShowTime")), dsm, result) @@ -888,7 +891,7 @@ def QTheaterDate(node: Node, params: QueryStateDict, result: Result) -> None: y += 1 result["show_date"] = datetime.date(day=d, month=m, year=y) - dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm + dsm: DialogueStateManager = Query.get_dsm(result) _add_date(cast(DateResource, dsm.get_resource("ShowDate")), dsm, result) return raise ValueError("No date in {0}".format(str(datenode))) @@ -906,12 +909,12 @@ def QTheaterTime(node: Node, params: QueryStateDict, result: Result) -> None: result["show_time"] = datetime.time(hour, minute) - dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm + dsm: DialogueStateManager = Query.get_dsm(result) _add_time(cast(TimeResource, dsm.get_resource("ShowTime")), dsm, result) def QTheaterMoreDates(node: Node, params: QueryStateDict, result: Result) -> None: - dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm + dsm: DialogueStateManager = Query.get_dsm(result) if dsm.current_resource.name == "ShowDate": extras: Dict[str, Any] = dsm.extras if "page_index" in extras: @@ -921,7 +924,7 @@ def QTheaterMoreDates(node: Node, params: QueryStateDict, result: Result) -> Non def QTheaterPreviousDates(node: Node, params: QueryStateDict, result: Result) -> None: - dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm + dsm: DialogueStateManager = Query.get_dsm(result) if dsm.current_resource.name == "ShowDate": extras: Dict[str, Any] = dsm.extras if "page_index" in extras: @@ -933,7 +936,7 @@ def QTheaterPreviousDates(node: Node, params: QueryStateDict, result: Result) -> def QTheaterShowSeatCountQuery( node: Node, params: QueryStateDict, result: Result ) -> None: - dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm + dsm: DialogueStateManager = Query.get_dsm(result) if dsm.get_resource("ShowDateTime").is_confirmed: resource: NumberResource = cast( NumberResource, dsm.get_resource("ShowSeatCount") @@ -947,7 +950,7 @@ def QTheaterShowSeatCountQuery( def QTheaterShowRow(node: Node, params: QueryStateDict, result: Result) -> None: - dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm + dsm: DialogueStateManager = Query.get_dsm(result) if dsm.get_resource("ShowSeatCount").is_confirmed: title: str = dsm.get_resource("Show").data[0] seats: int = dsm.get_resource("ShowSeatCount").data @@ -982,7 +985,7 @@ def QTheaterShowRow(node: Node, params: QueryStateDict, result: Result) -> None: def QTheaterShowSeats(node: Node, params: QueryStateDict, result: Result) -> None: - dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm + dsm: DialogueStateManager = Query.get_dsm(result) if dsm.get_resource("ShowSeatRow").is_confirmed: resource: ListResource = cast(ListResource, dsm.get_resource("ShowSeatNumber")) title: str = dsm.get_resource("Show").data[0] @@ -1207,7 +1210,7 @@ def QTheaterShowName(node: Node, params: QueryStateDict, result: Result) -> None ) -def QNum(node: Node, params: QueryStateDict, result: Result): +def QTheaterNum(node: Node, params: QueryStateDict, result: Result): number: int = int(parse_num(node, result._nominative)) if "numbers" not in result: result["numbers"] = [] @@ -1215,16 +1218,16 @@ def QNum(node: Node, params: QueryStateDict, result: Result): result.number = number -def QCancel(node: Node, params: QueryStateDict, result: Result): - dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm +def QTheaterCancel(node: Node, params: QueryStateDict, result: Result): + dsm: DialogueStateManager = Query.get_dsm(result) dsm.set_resource_state("Final", ResourceState.CANCELLED) - dsm.end_dialogue() + dsm.finish_dialogue() - result.qtype = "QCancel" + result.qtype = "QTheaterCancel" -def QYes(node: Node, params: QueryStateDict, result: Result): - dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm +def QTheaterYes(node: Node, params: QueryStateDict, result: Result): + dsm: DialogueStateManager = Query.get_dsm(result) current_resource = dsm.current_resource if ( not current_resource.is_confirmed @@ -1245,8 +1248,8 @@ def QYes(node: Node, params: QueryStateDict, result: Result): dsm.get_resource(rname).state = ResourceState.CONFIRMED -def QNo(node: Node, params: QueryStateDict, result: Result): - dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm +def QTheaterNo(node: Node, params: QueryStateDict, result: Result): + dsm: DialogueStateManager = Query.get_dsm(result) resource = dsm.current_resource if ( not resource.is_confirmed @@ -1265,9 +1268,9 @@ def QNo(node: Node, params: QueryStateDict, result: Result): dsm.set_resource_state(rname, ResourceState.UNFULFILLED) -def QStatus(node: Node, params: QueryStateDict, result: Result): - # TODO: Handle QStatus again with dsm in query - result.qtype = "QStatus" +def QTheaterStatus(node: Node, params: QueryStateDict, result: Result): + # TODO: Handle QTheaterStatus again with dsm in query + result.qtype = "QTheaterStatus" dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm dsm.set_answer( gen_answer( @@ -1310,7 +1313,7 @@ def sentence(state: QueryStateDict, result: Result) -> None: print("A") # result.shows = _fetch_shows() # dsm.setup_dialogue(_ANSWERING_FUNCTIONS) - # if result.qtype == "QStatus": + # if result.qtype == "QTheaterStatus": # # Example info handling functionality # text = "Leikhúsmiðapöntunin þín gengur bara vel. " # ans = dsm.get_answer() or gen_answer(text) diff --git a/query.py b/query.py index 64a3ba5d..d5eba210 100755 --- a/query.py +++ b/query.py @@ -53,7 +53,7 @@ import random from copy import deepcopy from collections import defaultdict -from queries.dialogue import DialogueStateManager as DSM +from queries.dialogue import DialogueStateManager as DSM, ResourceNotFoundError from settings import Settings @@ -75,7 +75,7 @@ from reynir.grammar import GrammarError from islenska.bindb import BinFilterFunc -from tree import Tree, TreeStateDict, Node +from tree import Tree, TreeStateDict, Node, Result # from nertokenizer import recognize_entities from images import get_image_url @@ -299,25 +299,43 @@ def process_queries( # in this query's parse forest: don't waste more cycles on it return False dialogue_name: Optional[str] = getattr(processor, "DIALOGUE_NAME", None) + print("IN DIALOGUE MODULE:", dialogue_name) if dialogue_name: - # Processor uses dialogue functionality, - # initialize dialogue state manager + # This processor uses dialogue functionality, + # check if it wants to process this query hotword_nonterminals: Set[str] = getattr( processor, "HOTWORD_NONTERMINALS", set() ) - # TODO: ONLY FETCH DATA ONCE if dialogue_data is None: + print("HOTWORD NONTERMINALS:", hotword_nonterminals) + print("QUERY NONTERMINALS:", self.query_nonterminals) + # Dialogue modules must have at least one + # hotword nonterminal for activating dialogue + assert isinstance(hotword_nonterminals, set) + assert len(hotword_nonterminals) > 0 + dialogue_data = cast( + Optional[str], query.all_dialogue_data.get(dialogue_name) + ) + print( + "CHECKING WHETHER PROCESSOR IS INTERESTED IN DIALOGUE:", + not ( + self.query_nonterminals.isdisjoint(hotword_nonterminals) + and dialogue_data is None + ), + ) # Fetch saved dialogue state - dialogue_data = query.client_data(DSM.DIALOGUE_DATA_KEY) - if self.query_nonterminals.isdisjoint(hotword_nonterminals) and ( - dialogue_data is None or dialogue_data.get(dialogue_name) is None + if ( + self.query_nonterminals.isdisjoint(hotword_nonterminals) + and dialogue_data is None ): - # Query doesn't contain dialogue hotwords - # and has no saved data for this dialogue + print("NO DIALOGUE DATA AND NO HOTWORDS MATCHED") + # Query's parse forest doesn't contain hotwords + # and has no saved data for this dialogue, + # not interested in this query return False - query._dsm = DSM( - dialogue_name, - cast(str, dialogue_data.get(dialogue_name)) if dialogue_data else None, - ) + print("STARTING DSM") + # Query matches this dialogue processor, start DialogueStateManager + query.start_dsm(dialogue_name, dialogue_data) + print("FINISHED SETTING UP DSM") with self.context(session, processor, query=query) as state: for query_tree in self._query_trees: # Is the processor interested in the root nonterminal @@ -327,6 +345,8 @@ def process_queries( self.process_sentence(state, query_tree) if query.has_answer(): # The processor successfully answered the query: We're done + # Also save any changes to dialogue data, if needed + query.update_dialogue_data() return True return False @@ -421,8 +441,9 @@ def __init__( # Query context, which is None until fetched via self.fetch_context() # This should be a dict that can be represented in JSON self._context: Optional[ContextDict] = None - # Dialogue state manager, used for dialogue modules + # Dialogue state manager and dialogue data, used for dialogue modules self._dsm: Optional[DSM] = None + self._all_dialogue_data: Optional[ClientDataDict] = None def _preprocess_query_string(self, q: str) -> str: """Preprocess the query string prior to further analysis""" @@ -657,7 +678,6 @@ def execute_from_tree(self) -> bool: if self._tree is None: self.set_error("E_QUERY_NOT_PARSED") return False - dialogue_data: Optional[ClientDataDict] = None # For storing all dialogue data # Try each tree processor in turn, in priority order (highest priority first) for processor in self._tree_processors: self._error = None @@ -670,18 +690,12 @@ def execute_from_tree(self) -> bool: # Note that passing query=self here means that the # "query" field of the TreeStateDict is populated, # turning it into a QueryStateDict. + print("TRYING PROCESSOR:", processor.__name__) if self._tree.process_queries(self, self._session, processor): - if self._dsm is not None: - # Save the dialogue state when a dialogue module query - # is successfully processed - self.set_client_data( - DSM.DIALOGUE_DATA_KEY, - cast(ClientDataDict, self._dsm.serialize_data()), - update_in_place=True, - ) # This processor found an answer, which is already stored # in the Query object: return True return True + print("FAILED PROCESSOR:", processor.__name__) except Exception as e: logging.error( f"Exception in execute_from_tree('{processor.__name__}') " @@ -878,11 +892,40 @@ def client_version(self) -> Optional[str]: """Return client version string, e.g. "1.0.3" """ return self._client_version + @staticmethod + def get_dsm(result: Result) -> DSM: + """Fetch DialogueStateManager instance from result object""" + dsm = cast(QueryStateDict, result.state)["query"]._dsm + assert dsm is not None, "get_dsm called in non-dialogue state" + return dsm + @property def dsm(self) -> DSM: - assert self._dsm is not None + assert self._dsm is not None, "dsm property used in non-dialogue state" return self._dsm + def start_dsm(self, dialogue_name: str, dialogue_data: Optional[str]) -> None: + """Start a new DialogueStateManager instance""" + self._dsm = DSM(dialogue_name, dialogue_data) + + @property + def all_dialogue_data(self) -> ClientDataDict: + if self._all_dialogue_data is None: + # Fetch dialogue data for the given client, or set it to an empty dict + self._all_dialogue_data = self.client_data(DSM.DIALOGUE_DATA_KEY) or dict() + return self._all_dialogue_data + + def update_dialogue_data(self) -> None: + """Update the dialogue data for the given client if a dialogue module was used""" + if self._dsm is not None: + # Save the dialogue state when a dialogue module query + # is successfully processed + self.set_client_data( + DSM.DIALOGUE_DATA_KEY, + cast(ClientDataDict, self._dsm.serialize_data()), + update_in_place=True, + ) + def response(self) -> Optional[ResponseType]: """Return the detailed query answer""" return self._response diff --git a/tree.py b/tree.py index 2df758c5..b148ca9d 100755 --- a/tree.py +++ b/tree.py @@ -42,10 +42,10 @@ Callable, Iterator, NamedTuple, - Type, cast, ) +from typing_extensions import TypedDict from types import ModuleType import json @@ -63,7 +63,6 @@ from reynir.binparser import BIN_Token from reynir.simpletree import SimpleTree, SimpleTreeBuilder from reynir.cache import LRU_Cache -from typing_extensions import TypedDict class TreeStateDict(TypedDict, total=False): From 3f298065aa57b8a7f1c4497be42385436ac9188b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Thu, 7 Jul 2022 16:22:19 +0000 Subject: [PATCH 200/371] Changed dsm declarations in a few non-terminal funcs to the new way to get them from query --- queries/theater.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/queries/theater.py b/queries/theater.py index a715c455..4e311b16 100644 --- a/queries/theater.py +++ b/queries/theater.py @@ -1040,7 +1040,7 @@ def QTheaterShowSeats(node: Node, params: QueryStateDict, result: Result) -> Non def QTheaterGeneralOptions(node: Node, params: QueryStateDict, result: Result) -> None: # result.options_info = True - dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm + dsm: DialogueStateManager = Query.get_dsm(result) resource = dsm.current_resource if resource.name == "Show": QTheaterShowOptions(node, params, result) @@ -1054,7 +1054,7 @@ def QTheaterGeneralOptions(node: Node, params: QueryStateDict, result: Result) - def QTheaterShowOptions(node: Node, params: QueryStateDict, result: Result) -> None: # result.show_options = True - dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm + dsm: DialogueStateManager = Query.get_dsm(result) resource = dsm.current_resource if resource.name == "Show": shows: list[str] = [] @@ -1070,7 +1070,7 @@ def QTheaterShowOptions(node: Node, params: QueryStateDict, result: Result) -> N def QTheaterDateOptions(node: Node, params: QueryStateDict, result: Result) -> None: # result.date_options = True - dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm + dsm: DialogueStateManager = Query.get_dsm(result) resource = dsm.current_resource if resource.name == "ShowDateTime": title = dsm.get_resource("Show").data[0] @@ -1137,7 +1137,7 @@ def QTheaterDateOptions(node: Node, params: QueryStateDict, result: Result) -> N def QTheaterRowOptions(node: Node, params: QueryStateDict, result: Result) -> None: # result.row_options = True - dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm + dsm: DialogueStateManager = Query.get_dsm(result) resource = dsm.current_resource if resource.name == "ShowSeatRow": title: str = dsm.get_resource("Show").data[0] @@ -1174,7 +1174,7 @@ def QTheaterRowOptions(node: Node, params: QueryStateDict, result: Result) -> No def QTheaterSeatOptions(node: Node, params: QueryStateDict, result: Result) -> None: result.seat_options = True - dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm + dsm: DialogueStateManager = Query.get_dsm(result) resource = dsm.current_resource if resource.name == "ShowSeatNumber": title: str = dsm.get_resource("Show").data[0] @@ -1271,7 +1271,7 @@ def QTheaterNo(node: Node, params: QueryStateDict, result: Result): def QTheaterStatus(node: Node, params: QueryStateDict, result: Result): # TODO: Handle QTheaterStatus again with dsm in query result.qtype = "QTheaterStatus" - dsm: DialogueStateManager = cast(QueryStateDict, result.state)["query"].dsm + dsm: DialogueStateManager = Query.get_dsm(result) dsm.set_answer( gen_answer( "Leikhúsmiðapöntunin þín gengur bara vel. {0}".format( From 76c421c99e608f43a436f5be6d4958d227d56e7f Mon Sep 17 00:00:00 2001 From: Logi E Date: Thu, 7 Jul 2022 16:36:22 +0000 Subject: [PATCH 201/371] Fixed hotword nonterminal bug and removed > instead of | in grammar --- queries/dialogues/fruitseller.toml | 4 +++- queries/fruitseller.py | 4 ++-- queries/theater.py | 16 ++++++++-------- queries/{disabled => }/wiki.py | 0 4 files changed, 13 insertions(+), 11 deletions(-) rename queries/{disabled => }/wiki.py (100%) diff --git a/queries/dialogues/fruitseller.toml b/queries/dialogues/fruitseller.toml index cbccec04..0f7aeecf 100644 --- a/queries/dialogues/fruitseller.toml +++ b/queries/dialogues/fruitseller.toml @@ -11,10 +11,12 @@ prompts.repeat = "Pöntunin samanstendur af {list_items}. Verður það eitthva [[resources]] name = "Date" type = "DateResource" +requires = ["Fruits"] [[resources]] name = "Time" type = "TimeResource" +requires = ["Fruits"] [[resources]] name = "DateTime" @@ -39,6 +41,6 @@ prompts.confirm = "Afhending pöntunar er {date_time}. Viltu staðfesta afhendin [[resources]] name = "Final" type = "FinalResource" -requires = ["Fruits", "DateTime"] +requires = ["DateTime"] prompts.final = "Pöntunin þín er {fruits} og verður afhent {date_time}." prompts.cancelled = "Móttekið, hætti við pöntunina." diff --git a/queries/fruitseller.py b/queries/fruitseller.py index 1c71c96f..837ede2b 100644 --- a/queries/fruitseller.py +++ b/queries/fruitseller.py @@ -29,13 +29,13 @@ HOTWORD_NONTERMINALS = {"QFruitStartQuery"} # The grammar nonterminals this module wants to handle -QUERY_NONTERMINALS = {"QFruitSeller"} +QUERY_NONTERMINALS = {"QFruitSeller"}.union(HOTWORD_NONTERMINALS) # The context-free grammar for the queries recognized by this plug-in module GRAMMAR = """ Query → - QFruitStartQuery > QFruitSeller + QFruitStartQuery | QFruitSeller QFruitSeller → QFruitQuery '?'? diff --git a/queries/theater.py b/queries/theater.py index a715c455..fa8799d8 100644 --- a/queries/theater.py +++ b/queries/theater.py @@ -71,13 +71,13 @@ def help_text(lemma: str) -> str: HOTWORD_NONTERMINALS = {"QTheaterHotWord"} # The grammar nonterminals this module wants to handle -QUERY_NONTERMINALS = {"QTheater", "QTheaterHotWord"} +QUERY_NONTERMINALS = {"QTheater"}.union(HOTWORD_NONTERMINALS) # The context-free grammar for the queries recognized by this plug-in module GRAMMAR = """ Query → - QTheaterHotWord > QTheater + QTheaterHotWord | QTheater QTheater → QTheaterQuery '?'? @@ -1281,14 +1281,14 @@ def QTheaterStatus(node: Node, params: QueryStateDict, result: Result): ) -SHOW_URL = "https://leikhusid.is/wp-json/shows/v1/categories/938" +# SHOW_URL = "https://leikhusid.is/wp-json/shows/v1/categories/938" -def _fetch_shows() -> Any: - resp = query_json_api(SHOW_URL) - if resp: - assert isinstance(resp, list) - return [s["title"] for s in resp] +# def _fetch_shows() -> Any: +# resp = query_json_api(SHOW_URL) +# if resp: +# assert isinstance(resp, list) +# return [s["title"] for s in resp] _ANSWERING_FUNCTIONS: AnsweringFunctionMap = { diff --git a/queries/disabled/wiki.py b/queries/wiki.py similarity index 100% rename from queries/disabled/wiki.py rename to queries/wiki.py From b48bc9db14db31485be511f2545e85aa03367a2a Mon Sep 17 00:00:00 2001 From: Logi E Date: Thu, 7 Jul 2022 16:42:22 +0000 Subject: [PATCH 202/371] re-added try-except --- queries/dialogue.py | 5 ++++- query.py | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index 1f6a5d3a..12b0fb32 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -216,7 +216,10 @@ def current_resource(self) -> Resource: return self._current_resource def get_resource(self, name: str) -> Resource: - return self._resources[name] + try: + return self._resources[name] + except KeyError: + raise ResourceNotFoundError(f"Resource {name} not found") @property def extras(self) -> Dict[str, Any]: diff --git a/query.py b/query.py index d5eba210..1f7ab1f6 100755 --- a/query.py +++ b/query.py @@ -342,7 +342,10 @@ def process_queries( # of this query tree? if query_tree.string_self() in processor_query_types: # Yes: hand the query tree over to the processor - self.process_sentence(state, query_tree) + try: + self.process_sentence(state, query_tree) + except ResourceNotFoundError: + pass if query.has_answer(): # The processor successfully answered the query: We're done # Also save any changes to dialogue data, if needed From f655f468923de887691ee01e2952d3596a950e09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Thu, 7 Jul 2022 16:57:54 +0000 Subject: [PATCH 203/371] =?UTF-8?q?Fixed=20options=20non=20terminal=20func?= =?UTF-8?q?s=20to=20accept=20specific=20queries=20like=20hva=C3=B0a=20s?= =?UTF-8?q?=C3=BDningar=20eru=20=C3=AD=20bo=C3=B0i?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- queries/theater.py | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/queries/theater.py b/queries/theater.py index a93560c8..9b39c2e6 100644 --- a/queries/theater.py +++ b/queries/theater.py @@ -1039,7 +1039,7 @@ def QTheaterShowSeats(node: Node, params: QueryStateDict, result: Result) -> Non def QTheaterGeneralOptions(node: Node, params: QueryStateDict, result: Result) -> None: - # result.options_info = True + result.options_info = True dsm: DialogueStateManager = Query.get_dsm(result) resource = dsm.current_resource if resource.name == "Show": @@ -1055,7 +1055,10 @@ def QTheaterGeneralOptions(node: Node, params: QueryStateDict, result: Result) - def QTheaterShowOptions(node: Node, params: QueryStateDict, result: Result) -> None: # result.show_options = True dsm: DialogueStateManager = Query.get_dsm(result) - resource = dsm.current_resource + if result.get("options_info"): + resource = dsm.current_resource + else: + resource = dsm.get_resource("Show") if resource.name == "Show": shows: list[str] = [] for show in _SHOWS: @@ -1071,7 +1074,10 @@ def QTheaterShowOptions(node: Node, params: QueryStateDict, result: Result) -> N def QTheaterDateOptions(node: Node, params: QueryStateDict, result: Result) -> None: # result.date_options = True dsm: DialogueStateManager = Query.get_dsm(result) - resource = dsm.current_resource + if result.get("options_info"): + resource = dsm.current_resource + else: + resource = dsm.get_resource("ShowDateTime") if resource.name == "ShowDateTime": title = dsm.get_resource("Show").data[0] dates: list[str] = [] @@ -1138,7 +1144,10 @@ def QTheaterDateOptions(node: Node, params: QueryStateDict, result: Result) -> N def QTheaterRowOptions(node: Node, params: QueryStateDict, result: Result) -> None: # result.row_options = True dsm: DialogueStateManager = Query.get_dsm(result) - resource = dsm.current_resource + if result.get("options_info"): + resource = dsm.current_resource + else: + resource = dsm.get_resource("ShowSeatRow") if resource.name == "ShowSeatRow": title: str = dsm.get_resource("Show").data[0] seats: int = dsm.get_resource("ShowSeatCount").data @@ -1175,7 +1184,10 @@ def QTheaterRowOptions(node: Node, params: QueryStateDict, result: Result) -> No def QTheaterSeatOptions(node: Node, params: QueryStateDict, result: Result) -> None: result.seat_options = True dsm: DialogueStateManager = Query.get_dsm(result) - resource = dsm.current_resource + if result.get("options_info"): + resource = dsm.current_resource + else: + resource = dsm.get_resource("ShowSeatNumber") if resource.name == "ShowSeatNumber": title: str = dsm.get_resource("Show").data[0] chosen_row: int = dsm.get_resource("ShowSeatRow").data[0] @@ -1272,13 +1284,12 @@ def QTheaterStatus(node: Node, params: QueryStateDict, result: Result): # TODO: Handle QTheaterStatus again with dsm in query result.qtype = "QTheaterStatus" dsm: DialogueStateManager = Query.get_dsm(result) - dsm.set_answer( - gen_answer( - "Leikhúsmiðapöntunin þín gengur bara vel. {0}".format( - dsm.get_answer(_ANSWERING_FUNCTIONS, result) - ) - ) - ) + at = dsm.get_answer(_ANSWERING_FUNCTIONS, result) + if at: + (_, ans, voice) = at + ans = "Leikhúsmiðapöntunin þín gengur bara vel. " + ans + voice = "Leikhúsmiðapöntunin þín gengur bara vel. " + voice + dsm.set_answer((dict(answer=ans), ans, voice)) # SHOW_URL = "https://leikhusid.is/wp-json/shows/v1/categories/938" From 6ef78c1a1c943dc4f45b181013b86e6915ee3755 Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Thu, 7 Jul 2022 17:18:58 +0000 Subject: [PATCH 204/371] grammar complete for initial command set --- queries/grammars/iot_hue.grammar | 4 + queries/grammars/iot_speakers.grammar | 147 +++++++++++++------------ queries/iot_hue.py | 26 +++-- queries/iot_speakers.py | 148 ++++++++++++++------------ 4 files changed, 179 insertions(+), 146 deletions(-) diff --git a/queries/grammars/iot_hue.grammar b/queries/grammars/iot_hue.grammar index 99443577..56656c10 100644 --- a/queries/grammars/iot_hue.grammar +++ b/queries/grammars/iot_hue.grammar @@ -318,3 +318,7 @@ QIoTBrightnessOrSettingWord/fall -> QIoTSettingWord/fall -> 'stilling'/fall + +# Catching hotwords from iot_speakers +QIoTLightsBanwords -> + QIoTSpeakerHotwords/nf \ No newline at end of file diff --git a/queries/grammars/iot_speakers.grammar b/queries/grammars/iot_speakers.grammar index dea96a3b..611d59de 100644 --- a/queries/grammars/iot_speakers.grammar +++ b/queries/grammars/iot_speakers.grammar @@ -1,6 +1,7 @@ # TODO: Fix music hardcoding # TODO: Check whether to force determinate form # TODO: "Settu á útvarpið", which station to play? +# TODO: Refactor make and let "Gerðu tónlistina hærri í hátalaranum (í eldhúsinu)"-type sentences /þgf = þgf /ef = ef @@ -14,10 +15,8 @@ QIoTSpeaker → QIoTSpeakerQuery → QIoTSpeakerMakeVerb QIoTSpeakerMakeRest | QIoTSpeakerSetVerb QIoTSpeakerSetRest - # | QIoTSpeakerChangeVerb QIoTSpeakerChangeRest | QIoTSpeakerLetVerb QIoTSpeakerLetRest - | QIoTSpeakerTurnOnVerb QIoTSpeakerTurnOnRest - | QIoTSpeakerTurnOffVerb QIoTSpeakerTurnOffRest + | QIoTSpeakerTurnOnOrOffVerb QIoTSpeakerTurnOrOffOnRest | QIoTSpeakerPlayOrPauseVerb QIoTSpeakerPlayOrPauseRest | QIoTSpeakerIncreaseOrDecreaseVerb QIoTSpeakerIncreaseOrDecreaseRest | QIoTSpeakerRadioStationName @@ -36,8 +35,13 @@ QIoTSpeakerChangeVerb → QIoTSpeakerLetVerb → 'láta:so'_bh +QIoTSpeakerTurnOnOrOffVerb -> + QIoTSpeakerTurnOnVerb + | QIoTSpeakerTurnOffVerb + QIoTSpeakerTurnOnVerb → 'kveikja:so'_bh + | "kveiktu" QIoTSpeakerTurnOffVerb → 'slökkva:so'_bh @@ -68,65 +72,33 @@ QIoTSpeakerDecreaseVerb → | 'minnka:so'_bh QIoTSpeakerMakeRest → - QIoTSpeakerMusicPhrase/þf QIoTSpeakerHvar? QIoTSpeakerComparative/nf + QIoTSpeakerMusicOrSoundPhrase/þf QIoTSpeakerComparative/nf QIoTSpeakerHvar | QIoTSpeakerMusicOrSoundPhrase/þf QIoTSpeakerHvar? QIoTSpeakerComparative/nf - | QIoTSpeakerMusicOrSoundPhrase/þf QIoTSpeakerComparative/nf QIoTSpeakerHvar? - # | QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigMake - # | QCHANGESubject/þf QCHANGEHvernigMake QCHANGEHvar? - # | QCHANGEHvar? QCHANGESubject/þf QCHANGEHvernigMake - # | QCHANGEHvar? QCHANGEHvernigMake QCHANGESubject/þf - # | QCHANGEHvernigMake QCHANGESubject/þf QCHANGEHvar? - # | QCHANGEHvernigMake QCHANGEHvar? QCHANGESubject/þf - -# TODO: Add support for "stilltu rauðan lit á ljósið í eldhúsinu" + | QIoTSpeakerMusicWord/þf QIoTSpeakerComparative/nf QIoTSpeakerHvarWithSpeaker + | QIoTSpeakerSoundWord/þf QIoTSpeakerComparative/nf QIoTSpeakerHvarWithAppliance + QIoTSpeakerSetRest → - QIoTSpeakerApplianceWord/þf? QIoTSpeakerAHvad QIoTSpeakerHvar? + QIoTSpeakerApplianceWord/þf? QIoTSpeakerAHvad QIoTSpeakerHvar | QIoTSpeakerApplianceWord/þf? QIoTSpeakerHvar? QIoTSpeakerAHvad - # QCHANGESubject/þf QCHANGEHvar? QCHANGEHvernigSet - # | QCHANGESubject/þf QCHANGEHvernigSet QCHANGEHvar? - # | QCHANGEHvar? QCHANGESubject/þf QCHANGEHvernigSet - # | QCHANGEHvar? QCHANGEHvernigSet QCHANGESubject/þf - # | QCHANGEHvernigSet QCHANGESubject/þf QCHANGEHvar? - # | QCHANGEHvernigSet QCHANGEHvar? QCHANGESubject/þf - -# QIoTSpeakerChangeRest → - # QCHANGESubjectOne/þgf QCHANGEHvar? QCHANGEHvernigChange - # | QCHANGESubjectOne/þgf QCHANGEHvernigChange QCHANGEHvar? - # | QCHANGEHvar? QCHANGESubjectOne/þgf QCHANGEHvernigChange - # | QCHANGEHvar? QCHANGEHvernigChange QCHANGESubjectOne/þgf - # | QCHANGEHvernigChange QCHANGESubjectOne/þgf QCHANGEHvar? - # | QCHANGEHvernigChange QCHANGEHvar? QCHANGESubjectOne/þgf QIoTSpeakerLetRest → - QIoTSpeakerBeOrBecome QIoTSpeakerMusicWord/nf QIoTSpeakerHvar? - | QIoTSpeakerMusicOrSoundPhrase/þf QIoTSpeakerHvar? QIoTSpeakerBeOrBecome QIoTSpeakerComparative/nf + QIoTSpeakerBeOrBecome QIoTSpeakerMusicWord/nf QIoTSpeakerHvarWithSpeaker? + | QIoTSpeakerMusicOrSoundPhrase/þf QIoTSpeakerHvar QIoTSpeakerBeOrBecome QIoTSpeakerComparative/nf | QIoTSpeakerMusicOrSoundPhrase/þf QIoTSpeakerBeOrBecome QIoTSpeakerComparative/nf QIoTSpeakerHvar? + | QIoTSpeakerMusicWord/þf QIoTSpeakerBeOrBecome QIoTSpeakerComparative/nf QIoTSpeakerHvarWithSpeaker + | QIoTSpeakerSoundWord/þf QIoTSpeakerBeOrBecome QIoTSpeakerComparative/nf QIoTSpeakerHvarWithAppliance -# TODO: Find out why they conjugate this incorrectly "tónlist" is in þgf here, not þf -QIoTSpeakerTurnOnRest → - QIoTSpeakerAHverju QIoTSpeakerHvar? - # | QCHANGEHvar? QCHANGEAHverju - -# Would be good to add "slökktu á rauða litnum" functionality -QIoTSpeakerTurnOffRest → - # QCHANGETurnOffLightsRest - "á" QIoTSpeakerMusicWord/þgf QIoTSpeakerHvar? +QIoTSpeakerTurnOrOffOnRest → + QIoTSpeakerAHverju QIoTSpeakerHvarWithSpeaker? -QIoTSpeakerPlayO → - QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? - | QIoTSpeakerNewRadio/þf? QIoTSpeakerHvar? +QIoTSpeakerPlayOrPauseRest → + QIoTSpeakerMusicWord/þf QIoTSpeakerHvarWithSpeaker? + | QIoTSpeakerNewRadio/þf? QIoTSpeakerHvarWithSpeaker? -# TODO: Make the subject categorization cleaner QIoTSpeakerIncreaseOrDecreaseRest → - # QCHANGELightSubject/þf QCHANGEHvar? - # | QCHANGEBrightnessSubject/þf QCHANGEHvar? - QIoTSpeakerMusicWord/þf QIoTSpeakerHvar? - | QIoTSpeakerSoundPhrase/þf QIoTSpeakerHvar? + QIoTSpeakerMusicOrSoundPhrase/þf QIoTSpeakerHvar? | "í" QIoTSpeakerMusicOrApplianceWord/þgf QIoTSpeakerHvar? - -# Increase specificity -# QIoTSpeakerMusicWord → -# 'tónlist' + | "í" QIoTSpeakerRadioStationName QIoTSpeakerHvar? QIoTSpeakerComparative/fall → QIoTSpeakerMoreOrHigher/fall @@ -140,6 +112,7 @@ QIoTSpeakerAHvad → QIoTSpeakerAHverju → "á" QIoTSpeakerMusicOrApplianceWord/þgf | "á" QIoTSpeakerNewRadio/þgf + | "á" QIoTSpeakerMusicOrRadioWord/þgf "í" QIoTSpeakerSpeakerWord/þgf QIoTSpeakerNewSetting/fall → QIoTSpeakerNewRadio/fall @@ -148,8 +121,6 @@ QIoTSpeakerNewSetting/fall → QIoTSpeakerNewRadio/fall → QIoTSpeakerRadioStationWord/fall? QIoTSpeakerRadioStationName - # | QIoTSpeakerRadioStationWord/fall? "bylgjunni" - # | QIoTSpeakerRadioStationWord/fall? QIoTSpeakerRadioStationNameIndeclinable QIoTSpeakerNewPlay/fall → "play" @@ -157,8 +128,8 @@ QIoTSpeakerNewPlay/fall → | "pley" QIoTSpeakerNewPause/fall → - 'pása:no'/fall - | 'stopp:no'/fall + 'pása'/fall + | 'stopp'/fall QIoTSpeakerMusicOrSoundPhrase/fall → QIoTSpeakerMusicPhrase/fall @@ -169,10 +140,10 @@ QIoTSpeakerMusicPhrase/fall → | QIoTSpeakerMusicWord/fall "í" QIoTSpeakerApplianceWord/þgf QIoTSpeakerSoundPhrase/fall → - QIoTSpeakerVolumeOrNoiseWord/fall? QIoTSpeakerMusicOrApplianceWord/ef? - | QIoTSpeakerVolumeOrNoiseWord/fall "í" QIoTSpeakerMusicOrApplianceWord/þgf? + QIoTSpeakerSoundWord/fall? QIoTSpeakerMusicOrApplianceWord/ef? + | QIoTSpeakerSoundWord/fall "í" QIoTSpeakerMusicOrApplianceWord/þgf? -QIoTSpeakerVolumeOrNoiseWord/fall → +QIoTSpeakerSoundWord/fall → QIoTSpeakerVolumeWord/fall | QIoTSpeakerNoiseWord/fall @@ -181,18 +152,18 @@ QIoTSpeakerMusicOrApplianceWord/fall → | QIoTSpeakerApplianceWord/fall QIoTSpeakerRadioStationWord/fall → - 'útvarpsstöð:no'/fall + 'útvarpsstöð'/fall QIoTSpeakerMusicWord/fall → 'tónlist'/fall QIoTSpeakerVolumeWord/fall → - 'hljóð:no'/fall - | 'hljóðstyrkur:no'/fall + 'hljóð'/fall + | 'hljóðstyrkur'/fall QIoTSpeakerNoiseWord/fall → - 'læti:no'/fall - | 'hávaði:no'/fall + 'læti'/fall + | 'hávaði'/fall QIoTSpeakerApplianceWord/fall → QIoTSpeakerSpeakerWord/fall @@ -203,10 +174,10 @@ QIoTSpeakerMusicOrRadioWord/fall → | QIoTSpeakerRadioWord/fall QIoTSpeakerSpeakerWord/fall → - 'hátalari:no'/fall + 'hátalari'/fall QIoTSpeakerRadioWord/fall → - 'útvarp:no'/fall + 'útvarp'/fall QIoTSpeakerMoreOrHigher/fall → 'mikill:lo'_mst/fall @@ -218,8 +189,17 @@ QIoTSpeakerLessOrLower/fall → | 'dimmur:lo'_mst/fall | 'lágur:lo'_mst/fall +QIoTSpeakerHvarWithAppliance → + "í" QIoTSpeakerApplianceWord/þgf QIoTSpeakerHvar + > QIoTSpeakerHvar + +QIoTSpeakerHvarWithSpeaker -> + "í" QIoTSpeakerSpeakerWord/þgf QIoTSpeakerHvar + > QIoTSpeakerHvar + QIoTSpeakerHvar → QIoTSpeakerLocationPreposition QIoTSpeakerGroupName/þgf + > QIoTSpeakerAllGroups QIoTSpeakerLocationPreposition → QIoTSpeakerLocationPrepositionFirstPart? QIoTSpeakerLocationPrepositionSecondPart @@ -241,6 +221,20 @@ QIoTSpeakerGroupName/fall → no/fall | sérnafn/fall +QIoTSpeakerAllGroups -> + QIoTSpeakerLocationPreposition QIoTSpeakerHouse/þgf + | QIoTSpeakerEverywhere + +QIoTSpeakerHouse/fall -> + 'allur:fn'_et_hk/fall? 'hús'_et/fall + +QIoTSpeakerEverywhere -> + "alls_staðar" + | "alstaðar" + | "allstaðar" + | "allsstaðar" + | "alsstaðar" + QIoTSpeakerBeOrBecome → QIoTSpeakerBe | QIoTSpeakerBecome @@ -270,7 +264,7 @@ QIoTSpeakerRadioStationName → | QIoTSpeakerApparatid | QIoTSpeakerFmExtra | QIoTSpeakerUtvarpSudurland - | QIoTSpeakerFlashback + | QIoTSpeakerFlashbackWord | QIoTSpeaker70sFlashback | QIoTSpeaker80sFlashback | QIoTSpeaker90sFlashback @@ -414,13 +408,18 @@ QIoTSpeakerUtvarpSudurland → | "útvarps"? "suðurlands" QIoTSpeaker70sFlashback → - QIoTSpeaker70s "flassbakk" + QIoTSpeaker70s QIoTSpeakerFlashbackWord QIoTSpeaker80sFlashback → - QIoTSpeaker80s "flassbakk" + QIoTSpeaker80s QIoTSpeakerFlashbackWord QIoTSpeaker90sFlashback → - QIoTSpeaker90s "flassbakk" + QIoTSpeaker90s QIoTSpeakerFlashbackWord + +QIoTSpeakerFlashbackWord -> + "flassbakk" + | "flass" "bakk" + | "bakk" QIoTSpeaker70s → "seventís" @@ -428,10 +427,18 @@ QIoTSpeaker70s → QIoTSpeaker80s → "eitís" + | "Eydís" | "eydís" + | "Eidís" | "eidís" | "eighties" QIoTSpeaker90s → "næntís" - | "nineties" \ No newline at end of file + | "nineties" + +# Hotwords to block other similarly phrased query modules from catching queries belonging to iot_speakers +QIoTSpeakerHotwords/fall -> + QIoTSpeakerMusicOrApplianceWord/fall + | QIoTSpeakerSoundWord/fall + | QIoTSpeakerRadioStationName \ No newline at end of file diff --git a/queries/iot_hue.py b/queries/iot_hue.py index 27771ee4..561b74f1 100755 --- a/queries/iot_hue.py +++ b/queries/iot_hue.py @@ -117,6 +117,9 @@ def help_text(lemma: str) -> str: "iot_hue", color_names=" | ".join(f"'{color}:lo'" for color in _COLORS.keys()) ) +def QIoTQuery(node: Node, params: QueryStateDict, result: Result) -> None: + result.qtype = _IoT_QTYPE + def QIoTColorWord(node: Node, params: QueryStateDict, result: Result) -> None: result.changing_color = True @@ -130,9 +133,6 @@ def QIoTBrightnessWord(node: Node, params: QueryStateDict, result: Result) -> No result.changing_brightness = True -def QIoTQuery(node: Node, params: QueryStateDict, result: Result) -> None: - result.qtype = _IoT_QTYPE - def QIoTTurnOnLightsRest(node: Node, params: QueryStateDict, result: Result) -> None: result.action = "turn_on" @@ -246,6 +246,10 @@ def QIoTLightName(node: Node, params: QueryStateDict, result: Result) -> None: result["light_name"] = result._indefinite +def QIoTLightsBanwords(node: Node, params: QueryStateDict, result: Result) -> None: + result.abort = True + + # Convert color name into hue # Taken from home.py _COLOR_NAME_TO_CIE: Mapping[str, float] = { @@ -304,14 +308,16 @@ def QIoTLightName(node: Node, params: QueryStateDict, result: Result) -> None: def sentence(state: QueryStateDict, result: Result) -> None: """Called when sentence processing is complete""" q: Query = state["query"] - lemmas = set( - i[0].root(state, result.params) - for i in result.enum_descendants(lambda x: isinstance(x, TerminalNode)) - ) - if not _SPEAKER_WORDS.isdisjoint(lemmas): - print("matched with music word list") + if result.get("abort"): q.set_error("E_QUERY_NOT_UNDERSTOOD") - return + # lemmas = set( + # i[0].root(state, result.params) + # for i in result.enum_descendants(lambda x: isinstance(x, TerminalNode)) + # ) + # if not _SPEAKER_WORDS.isdisjoint(lemmas): + # print("matched with music word list") + # q.set_error("E_QUERY_NOT_UNDERSTOOD") + # return changing_color = result.get("changing_color", False) changing_scene = result.get("changing_scene", False) changing_brightness = result.get("changing_brightness", False) diff --git a/queries/iot_speakers.py b/queries/iot_speakers.py index f06a7507..db409b10 100644 --- a/queries/iot_speakers.py +++ b/queries/iot_speakers.py @@ -110,156 +110,169 @@ def help_text(lemma: str) -> str: def QIoTSpeaker(node: Node, params: QueryStateDict, result: Result) -> None: - print("QTYPE") result.qtype = _IoT_QTYPE +def QIoTSpeakerTurnOnVerb(node: Node, params: QueryStateDict, result: Result) -> None: + result.qkey = "turn_on" + + +def QIoTSpeakerTurnOffVerb(node: Node, params: QueryStateDict, result: Result) -> None: + result.qkey = "turn_off" + + +def QIoTSpeakerPlayVerb(node: Node, params: QueryStateDict, result: Result) -> None: + result.qkey = "turn_on" + + +def QIoTSpeakerPauseVerb(node: Node, params: QueryStateDict, result: Result) -> None: + result.qkey = "turn_off" + + def QIoTSpeakerIncreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: - result["qkey"] = "increase_volume" + result.qkey = "increase_volume" def QIoTSpeakerDecreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: - result["qkey"] = "decrease_volume" + result.qkey = "decrease_volume" -def QIoTSpeakerGroupName(node: Node, params: QueryStateDict, result: Result) -> None: - result["group_name"] = result._indefinite +def QIoTSpeakerMoreOrHigher(node: Node, params: QueryStateDict, result: Result) -> None: + result.qkey = "increase_volume" + + +def QIoTSpeakerLessOrLower(node: Node, params: QueryStateDict, result: Result) -> None: + result.qkey = "decrease_volume" -def QIoTMusicWord(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTSpeakerMusicWord(node: Node, params: QueryStateDict, result: Result) -> None: result.target = "music" - print("music") -def QIoTSpeakerTurnOnVerb(node: Node, params: QueryStateDict, result: Result) -> None: - result["qkey"] = "play_music" +def QIoTSpeakerSpeakerWord(node: Node, params: QueryStateDict, result: Result) -> None: + result.target = "speaker" -def QIoTSpeakerPauseVerb(node: Node, params: QueryStateDict, result: Result) -> None: - print("PAUSE") - result["qkey"] = "pause_music" +def QIoTSpeakerRadioWord(node: Node, params: QueryStateDict, result: Result) -> None: + result.target = "radio" -def QIoTSpeakerPlayVerb(node: Node, params: QueryStateDict, result: Result) -> None: - result["qkey"] = "play_music" +def QIoTSpeakerGroupName(node: Node, params: QueryStateDict, result: Result) -> None: + result.group_name = result._indefinite def QIoTSpeakerRas1(node: Node, params: QueryStateDict, result: Result) -> None: - result["qkey"] = "radio" - result["station"] = "Rás 1" + result.target = "radio" + result.station = "Rás 1" def QIoTSpeakerRas2(node: Node, params: QueryStateDict, result: Result) -> None: - result["qkey"] = "radio" - result["station"] = "Rás 2" + result.target = "radio" + result.station = "Rás 2" def QIoTSpeakerRondo(node: Node, params: QueryStateDict, result: Result) -> None: - result["qkey"] = "radio" - result["station"] = "Rondó" + result.target = "radio" + result.station = "Rondó" def QIoTSpeakerBylgjan(node: Node, params: QueryStateDict, result: Result) -> None: - result["qkey"] = "radio" - result["station"] = "Bylgjan" + result.target = "radio" + result.station = "Bylgjan" def QIoTSpeakerFm957(node: Node, params: QueryStateDict, result: Result) -> None: - result["qkey"] = "radio" - result["station"] = "FM957" + result.target = "radio" + result.station = "FM957" def QIoTSpeakerUtvarpSaga(node: Node, params: QueryStateDict, result: Result) -> None: - result["qkey"] = "radio" - result["station"] = "Útvarp Saga" + result.target = "radio" + result.station = "Útvarp Saga" def QIoTSpeakerGullbylgjan(node: Node, params: QueryStateDict, result: Result) -> None: - result["qkey"] = "radio" - result["station"] = "Gullbylgjan" + result.target = "radio" + result.station = "Gullbylgjan" def QIoTSpeakerLettbylgjan(node: Node, params: QueryStateDict, result: Result) -> None: - result["qkey"] = "radio" - result["station"] = "Léttbylgjan" + result.target = "radio" + result.station = "Léttbylgjan" def QIoTSpeakerXid(node: Node, params: QueryStateDict, result: Result) -> None: - result["qkey"] = "radio" - result["station"] = "X977" + result.target = "radio" + result.station = "X977" def QIoTSpeakerKissfm(node: Node, params: QueryStateDict, result: Result) -> None: - result["qkey"] = "radio" - result["station"] = "KissFM" + result.target = "radio" + result.station = "KissFM" def QIoTSpeakerFlassback(node: Node, params: QueryStateDict, result: Result) -> None: - result["qkey"] = "radio" - result["station"] = "Flashback" + result.target = "radio" + result.station = "Flashback" def QIoTSpeakerRetro(node: Node, params: QueryStateDict, result: Result) -> None: - result["qkey"] = "radio" - result["station"] = "Retro" + result.target = "radio" + result.station = "Retro" def QIoTSpeakerUtvarp101(node: Node, params: QueryStateDict, result: Result) -> None: - result["qkey"] = "radio" - result["station"] = "Útvarp 101" + result.target = "radio" + result.station = "Útvarp 101" def QIoTSpeakerK100(node: Node, params: QueryStateDict, result: Result) -> None: - result["qkey"] = "radio" - result["station"] = "K100" + result.target = "radio" + result.station = "K100" def QIoTSpeakerIslenskaBylgjan( node: Node, params: QueryStateDict, result: Result ) -> None: - result["qkey"] = "radio" - result["station"] = "Íslenska Bylgjan" + result.target = "radio" + result.station = "Íslenska Bylgjan" -def QIoT80sBylgjan(node: Node, params: QueryStateDict, result: Result) -> None: - result["qkey"] = "radio" - result["station"] = "80s Bylgjan" +def QIoTSpeaker80sBylgjan(node: Node, params: QueryStateDict, result: Result) -> None: + result.target = "radio" + result.station = "80s Bylgjan" def QIoTSpeakerApparatid(node: Node, params: QueryStateDict, result: Result) -> None: - result["qkey"] = "radio" - result["station"] = "Apparatið" + result.target = "radio" + result.station = "Apparatið" def QIoTSpeakerFmExtra(node: Node, params: QueryStateDict, result: Result) -> None: - result["qkey"] = "radio" - result["station"] = "FM Extra" + result.target = "radio" + result.station = "FM Extra" def QIoTSpeaker70sFlashback(node: Node, params: QueryStateDict, result: Result) -> None: - result["qkey"] = "radio" - result["station"] = "70s Flashback" + result.target = "radio" + result.station = "70s Flashback" def QIoTSpeaker80sFlashback(node: Node, params: QueryStateDict, result: Result) -> None: - result["qkey"] = "radio" - result["station"] = "80s Flashback" + result.target = "radio" + result.station = "80s Flashback" def QIoTSpeaker90sFlashback(node: Node, params: QueryStateDict, result: Result) -> None: - result["qkey"] = "radio" - result["station"] = "90s Flashback" + result.target = "radio" + result.station = "90s Flashback" def QIoTSpeakerUtvarpSudurland( node: Node, params: QueryStateDict, result: Result ) -> None: - result["qkey"] = "radio" - result["station"] = "Útvarp Suðurland" - - -def QIoTSpeakerGroupName(node: Node, params: QueryStateDict, result: Result) -> None: - result["group_name"] = result._indefinite + result.target = "radio" + result.station = "Útvarp Suðurland" def call_sonos_client(sonos_client, result): @@ -272,13 +285,12 @@ def call_sonos_client(sonos_client, result): else: response = getattr(sonos_client, handler_func)() return response - return # Map of query keys to handler functions and the corresponding answer string for Embla _HANDLER_MAP = { - "play_music": ["toggle_play", "Ég kveikti á tónlist"], - "pause_music": ["toggle_pause", "Ég slökkti á tónlist"], + "turn_on": ["toggle_play", "Ég kveikti á tónlist"], + "turn_off": ["toggle_pause", "Ég slökkti á tónlist"], "increase_volume": ["increase_volume", "Ég hækkaði í tónlistinni"], "decrease_volume": ["decrease_volume", "Ég lækkaði í tónlistinni"], "radio": ["play_radio_stream", "Ég setti á útvarpstöðina"], @@ -289,6 +301,10 @@ def sentence(state: QueryStateDict, result: Result) -> None: """Called when sentence processing is complete""" print("sentence") q: Query = state["query"] + if "qkey" not in result: + result.qkey = "turn_on" + if result.qkey == "turn_on" and result.target == "radio": + result.qkey = "radio" if "qtype" in result and "qkey" in result: print("IF QTYPE AND QKEY") try: From 6f2f69ae2811c8c53597fd13c3251501d2825655 Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Thu, 7 Jul 2022 18:45:23 +0000 Subject: [PATCH 205/371] SmartThings connect process probably useless :( --- queries/iot_connect.py | 18 +++++-- queries/iot_speakers.py | 10 ++++ queries/smartthings.py | 104 ++++++++++++++++++++++++++++++++++++++++ queries/sonos.py | 67 +++++++++++++++++--------- routes/api.py | 27 +++++++++++ 5 files changed, 200 insertions(+), 26 deletions(-) create mode 100644 queries/smartthings.py diff --git a/queries/iot_connect.py b/queries/iot_connect.py index 2d399df4..11a50968 100644 --- a/queries/iot_connect.py +++ b/queries/iot_connect.py @@ -160,13 +160,23 @@ def sentence(state: QueryStateDict, result: Result) -> None: return elif result.qtype == "connect_hub": - js = read_jsfile("IoT_Embla/Smart_Things/st_connecthub.js") - js += f"syncConnectHub('{client_id}','{host}');" - answer = "Smart Things miðstöðin hefur verið tengd" + + smartthings_key = read_api_key("SmartThingsKey") + answer = "Skráðu þig inn hjá SmartThings" voice_answer, response = answer, dict(answer=answer) q.set_answer(response, answer, voice_answer) - q.set_command(js) + # Redirect the user to a Sonos login screen, which will then forward the neccessary credentials to the connect_sonos.api found in api.py + q.set_url( + f"https://graph.api.smartthings.com/oauth/confirm_access?response_type=code&scope=devices&client_id={smartthings_key}&redirect_uri=http://{host}/connect_smartthings.api&state={client_id}" + ) return + # js = read_jsfile("IoT_Embla/Smart_Things/st_connecthub.js") + # js += f"syncConnectHub('{client_id}','{host}');" + # answer = "Smart Things miðstöðin hefur verið tengd" + # voice_answer, response = answer, dict(answer=answer) + # q.set_answer(response, answer, voice_answer) + # q.set_command(js) + # return elif result.qtype == "connect_speaker": sonos_key = read_api_key("SonosKey") diff --git a/queries/iot_speakers.py b/queries/iot_speakers.py index f06a7507..9b856a96 100644 --- a/queries/iot_speakers.py +++ b/queries/iot_speakers.py @@ -262,6 +262,14 @@ def QIoTSpeakerGroupName(node: Node, params: QueryStateDict, result: Result) -> result["group_name"] = result._indefinite +def QIoTSpeakerNext(node: Node, params: QueryStateDict, result: Result) -> None: + result["qkey"] = "next_song" + + +def QIoTSpeakerPrevious(node: Node, params: QueryStateDict, result: Result) -> None: + result["qkey"] = "prev_song" + + def call_sonos_client(sonos_client, result): """Call the appropriate function in the SonosClient based on the result""" handler_func = _HANDLER_MAP[result.qkey][0] @@ -282,6 +290,8 @@ def call_sonos_client(sonos_client, result): "increase_volume": ["increase_volume", "Ég hækkaði í tónlistinni"], "decrease_volume": ["decrease_volume", "Ég lækkaði í tónlistinni"], "radio": ["play_radio_stream", "Ég setti á útvarpstöðina"], + "next_song": ["next_song", "Skal gert. Næsta lag."], + "prev_song": ["prev_song", "Skal gert. Fyrra lag."], } diff --git a/queries/smartthings.py b/queries/smartthings.py new file mode 100644 index 00000000..25b90b87 --- /dev/null +++ b/queries/smartthings.py @@ -0,0 +1,104 @@ +from inspect import getargs +import requests +from datetime import datetime, timedelta +import flask +import random + +from util import read_api_key +from queries import query_json_api, post_to_json_api +from query import Query +from typing import Dict + +import json + + +class SmartThingsClient: + def __init__( + self, + device_data: Dict[str, str], + client_id: str, + ): + self._client_id = client_id + self._device_data = device_data + self._smartthings_encoded_credentials = read_api_key( + "SmartThingsEncodedCredentials" + ) + self._code = self._device_data["smartthings"]["credentials"]["code"] + print("code :", self._code) + self._timestamp = datetime.now() + print("device data :", self._device_data) + try: + self._access_token = self._device_data["smartthings"]["credentials"][ + "access_token" + ] + except (KeyError, TypeError): + self._create_token() + # self._check_token_expiration() + # self._households = self._get_households() + # self._household_id = self._households[0]["id"] + # self._groups = self._get_groups() + # self._players = self._get_players() + # self._group_id = self._get_group_id() + # self._store_smartthings_data_and_credentials() + self._store_credentials() + + def _create_token(self): + + url = "https://api.smartthings.com/v1/oauth/token" + + payload = f"code={self._code}&redirect_uri=http%3A%2F%2F192.168.1.69%3A5000%2Fconnect_smartthings.api&grant_type=authorization_code" + headers = { + "Content-Type": "application/x-www-form-urlencoded", + "Authorization": f"Basic {self._smartthings_encoded_credentials}", + } + + response = post_to_json_api(url, payload, headers) + self._access_token = response.get("access_token") + return response + + def _store_credentials(self): + print("_store_smartthings_cred") + # data_dict = self._create_sonos_data_dict() + cred_dict = self._create_cred_dict() + smartthings_dict = {} + smartthings_dict["smartthings"] = {"credentials": cred_dict} + self._store_data(smartthings_dict) + + def _create_cred_dict(self): + print("_create_smartthings_cred_dict") + cred_dict = {} + cred_dict.update( + { + "access_token": self._access_token, + "timestamp": str(datetime.now()), + } + ) + return cred_dict + + def _store_data(self, data): + Query.store_query_data(self._client_id, "iot_hubs", data, update_in_place=True) + + def set_color(self): + + url = "https://api.smartthings.com/v1/devices/7d47b44f-057c-4320-9777-3d1eadca106e/commands" + + payload = json.dumps( + { + "commands": [ + { + "component": "main", + "capability": "colorControl", + "command": "setColor", + "arguments": [[100, 50]], + } + ] + } + ) + headers = { + "Authorization": f"Bearer {self._access_token}", + "Content-Type": "application/json", + } + + response = requests.request("POST", url, headers=headers, data=payload) + + print(response.text) diff --git a/queries/sonos.py b/queries/sonos.py index e4f3f9c7..38c41c5f 100644 --- a/queries/sonos.py +++ b/queries/sonos.py @@ -112,6 +112,7 @@ def __init__( self._household_id = self._households[0]["id"] self._groups = self._get_groups() self._players = self._get_players() + self._group_id = self._get_group_id() self._store_sonos_data_and_credentials() """ @@ -357,8 +358,8 @@ def _create_playerdict_for_db(self, players: list): def _create_or_join_session(self): print("_create_or_join_session") - group_id = self._get_group_id() - url = f"https://api.ws.sonos.com/control/api/v1/groups/{group_id}/playbackSession/joinOrCreate" + # group_id = self._get_group_id() + url = f"https://api.ws.sonos.com/control/api/v1/groups/{self._group_id}/playbackSession/joinOrCreate" payload = json.dumps({"appId": "com.mideind.embla", "appContext": "embla123"}) headers = { @@ -401,8 +402,8 @@ def play_radio_stream(self, radio_url: str): def increase_volume(self): print("increase_volume") - group_id = self._get_group_id() - url = f"https://api.ws.sonos.com/control/api/v1/groups/{group_id}/groupVolume/relative" + # group_id = self._get_group_id() + url = f"https://api.ws.sonos.com/control/api/v1/groups/{self._group_id}/groupVolume/relative" payload = json.dumps({"volumeDelta": 10}) headers = { @@ -436,9 +437,9 @@ def toggle_play(self): Toggles play/pause of a group """ print("toggle playpause") - group_id = self._get_group_id() + # group_id = self._get_group_id() print("exited group_id") - url = f"https://api.ws.sonos.com/control/api/v1/groups/{group_id}/playback/play" + url = f"https://api.ws.sonos.com/control/api/v1/groups/{self._group_id}/playback/play" headers = { "Content-Type": "application/json", "Authorization": f"Bearer {self._access_token}", @@ -457,11 +458,9 @@ def toggle_pause(self): Toggles play/pause of a group """ print("toggle playpause") - group_id = self._get_group_id() + # group_id = self._get_group_id() print("exited group_id") - url = ( - f"https://api.ws.sonos.com/control/api/v1/groups/{group_id}/playback/pause" - ) + url = f"https://api.ws.sonos.com/control/api/v1/groups/{self._group_id}/playback/pause" headers = { "Content-Type": "application/json", "Authorization": f"Bearer {self._access_token}", @@ -521,20 +520,44 @@ def play_chime(self): "Authorization": f"Bearer {self._access_token}", } - response = requests.request("POST", url, headers=headers, data=payload) + response = post_to_json_api(url, payload, headers) + + print(response) - print(response.text) + def next_song(self): + url = f"https://api.ws.sonos.com/control/api/v1/groups/{self._group_id}/playback/skipToNextTrack" - def _refresh_data(self, function): - print("refresh data") - print("device_data: ", self._device_data) - # self._device_data["sonos"]["data"] = None - # print("device_data after deletion: ", self._device_data) - self._households = self._get_households() - self._groups = self._get_groups() - self._players = self._get_players() - # self._store_sonos_data_and_credentials() - getattr(self, function)() + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {self._access_token}", + } + + response = post_to_json_api(url, headers=headers) + + print(response) + + def prev_song(self): + url = f"https://api.ws.sonos.com/control/api/v1/groups/{self._group_id}/playback/skipToPreviousTrack" + + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {self._access_token}", + } + + response = post_to_json_api(url, headers=headers) + + print(response) + + # def _refresh_data(self, function): + # print("refresh data") + # print("device_data: ", self._device_data) + # # self._device_data["sonos"]["data"] = None + # # print("device_data after deletion: ", self._device_data) + # self._households = self._get_households() + # self._groups = self._get_groups() + # self._players = self._get_players() + # # self._store_sonos_data_and_credentials() + # getattr(self, function)() # def get_groups_and_players(self): # """ diff --git a/routes/api.py b/routes/api.py index dc9128ec..381a8da0 100755 --- a/routes/api.py +++ b/routes/api.py @@ -52,6 +52,7 @@ ) from util import read_api_key, icelandic_asciify from queries.sonos import SonosClient +from queries.smartthings import SmartThingsClient from . import routes, better_jsonify, text_from_request, bool_from_request from . import MAX_URL_LENGTH, MAX_UUID_LENGTH @@ -753,6 +754,32 @@ def sonos_code(version: int = 1) -> Response: return better_jsonify(valid=False, errmsg="Error registering sonos code.") +@routes.route("/connect_smartthings.api", methods=["GET"]) +@routes.route("/connect_smartthings.api/v", methods=["GET", "POST"]) +def smartthings_code(version: int = 1) -> Response: + """ + API endpoint to connect to Sonos speakers + """ + print("smartthings code") + args = request.args + client_id = args.get("state") + code = args.get("code") + code = { + "smartthings": {"credentials": {"code": code}} + } # create a dictonary with the code + if client_id and code: + success = QueryObject.store_query_data( + client_id, "iot_hubs", code, update_in_place=True + ) + if success: + device_data = code + smartthings_client = SmartThingsClient(device_data, client_id) + # device_data = code + # Create an instance of the SonosClient class. This will automatically create the rest of the credentials needed. + return better_jsonify(valid=True, msg="Registered smartthings code") + return better_jsonify(valid=False, errmsg="Error registering smartthings code.") + + # def sonos_code2(version: int = 1) -> Response: # print("sonos code") # args = request.args From ddc37d311dfa8f125edaafb96f1a1ca129af5db9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Fri, 8 Jul 2022 09:15:08 +0000 Subject: [PATCH 206/371] Removed commented out code in answering funcs in theater --- queries/theater.py | 102 --------------------------------------------- 1 file changed, 102 deletions(-) diff --git a/queries/theater.py b/queries/theater.py index 9b39c2e6..05e30365 100644 --- a/queries/theater.py +++ b/queries/theater.py @@ -338,26 +338,6 @@ class ShowType(TypedDict): def _generate_show_answer( resource: ListResource, dsm: DialogueStateManager, result: Result ) -> Optional[AnswerTuple]: - # if (not resource.is_confirmed and result.get("options_info")) or result.get( - # "show_options" - # ): - # shows: list[str] = [] - # for show in _SHOWS: - # shows.append("\n - " + show["title"]) - # ans = resource.prompts["options"] - # if len(shows) == 1: - # ans = ans.replace("Sýningarnar", "Sýningin", 1).replace("eru", "er", 2) - # text_ans = ans.format(options="".join(shows)) - # voice_ans = ans.format(options=natlang_seq(shows)).replace("-", "") - # return (dict(answer=text_ans), text_ans, voice_ans) - # if result.get("no_show_matched"): - # return gen_answer(resource.prompts["no_show_matched"]) - # if result.get("no_show_matched_data_exists"): - # return gen_answer( - # resource.prompts["no_show_matched_data_exists"].format( - # show=resource.data[0] - # ) - # ) if resource.is_unfulfilled: return gen_answer(resource.prompts["initial"]) if resource.is_fulfilled: @@ -402,40 +382,6 @@ def _generate_date_answer( start_string = "Síðustu þrjár dagsetningarnar eru:\n" index = max(0, len(dates) - 3) extras["page_index"] = index - - # if (not resource.is_confirmed and result.get("options_info")) or result.get( - # "date_options" - # ): - # options_string = ( - # start_string + natlang_seq(dates[index : index + date_number]) - # ).replace("dagur", "dagurinn") - # text_options_string = start_string + "".join( - # text_dates[index : index + date_number] - # ) - # if len(dates) > 0: - # ans = resource.prompts["options"] - # if date_number == 1: - # ans = ans.replace("eru", "er", 1).replace( - # "dagsetningar", "dagsetning", 1 - # ) - # voice_ans = ans.format( - # options=options_string, - # date_number=number_to_text(len(dates), gender="kvk"), - # ).replace("\n", _BREAK_SSML) - # text_ans = ans.format( - # options=text_options_string, - # date_number=number_to_text(len(dates), gender="kvk"), - # ) - - # return (dict(answer=text_ans), text_ans, numbers_to_ordinal(voice_ans)) - # else: - # return gen_answer(resource.prompts["no_date_available"].format(show=title)) - # if result.get("no_date_matched"): - # return gen_answer(resource.prompts["no_date_matched"]) - # if result.get("no_time_matched"): - # return gen_answer(resource.prompts["no_time_matched"]) - # if result.get("many_matching_times"): - # return gen_answer(resource.prompts["many_matching_times"]) if resource.is_partially_fulfilled: show_date: Optional[datetime.date] = cast( DateResource, dsm.get_resource("ShowDate") @@ -507,8 +453,6 @@ def _generate_date_answer( def _generate_seat_count_answer( resource: NumberResource, dsm: DialogueStateManager, result: Result ) -> Optional[AnswerTuple]: - # if result.get("invalid_seat_count"): - # return gen_answer(resource.prompts["invalid_seat_count"]) if resource.is_unfulfilled: return gen_answer(resource.prompts["initial"]) if resource.is_fulfilled: @@ -544,26 +488,6 @@ def _generate_row_answer( else: checking_row = row seats_in_row = 1 - # if (not resource.is_confirmed and result.get("options_info")) or result.get( - # "row_options" - # ): - # ans = resource.prompts["options"] - # if len(available_rows) == 1: - # ans = ans.replace("eru", "er").replace("Raðir", "Röð") - # if seats == 1: - # ans = ans.replace("laus", "laust") - # text_ans = ans.format(rows=natlang_seq(text_available_rows), seats=seats) - # voice_ans = ans.format( - # rows=natlang_seq(available_rows), seats=number_to_text(seats) - # ) - # return (dict(answer=text_ans), text_ans, voice_ans) - # if result.get("no_row_matched"): - # ans = resource.prompts["no_row_matched"] - # if seats == 1: - # ans = ans.replace("laus", "laust") - # text_ans = ans.format(seats=seats) - # voice_ans = ans.format(seats=number_to_text(seats)) - # return (dict(answer=text_ans), text_ans, voice_ans) if resource.is_unfulfilled: if len(available_rows) == 0: dsm.set_resource_state("ShowDateTime", ResourceState.UNFULFILLED) @@ -606,32 +530,6 @@ def _generate_seat_number_answer( if chosen_row == row: text_available_seats.append(str(seat)) available_seats.append(number_to_text(seat)) - # if (not resource.is_confirmed and result.get("options_info")) or result.get( - # "seat_options" - # ): - # ans = resource.prompts["options"] - # if len(available_seats) == 1: - # ans = ans.replace("Sætin", "Sætið", 1).replace("eru", "er", 2) - # text_ans = ans.format(row=chosen_row, options=natlang_seq(text_available_seats)) - # voice_ans = ans.format( - # row=number_to_text(chosen_row), options=natlang_seq(available_seats) - # ) - # return (dict(answer=text_ans), text_ans, voice_ans) - # if result.get("wrong_number_seats_selected"): - # if len(result.get("numbers")) > 1: - # chosen_seats = len( - # range(result.get("numbers")[0], result.get("numbers")[1] + 1) - # ) - # else: - # chosen_seats = 1 - # ans = resource.prompts["wrong_number_seats_selected"] - # text_ans = ans.format(chosen_seats=chosen_seats, seats=seats) - # voice_ans = ans.format( - # chosen_seats=number_to_text(chosen_seats), seats=number_to_text(seats) - # ) - # return (dict(answer=text_ans), text_ans, voice_ans) - # if result.get("seats_unavailable"): - # return gen_answer(resource.prompts["seats_unavailable"]) if resource.is_unfulfilled: ans = resource.prompts["initial"] if len(available_seats) == 1: From 9bff57bbce75f83cff6376384974c6ed4ed99bb1 Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Fri, 8 Jul 2022 10:55:28 +0000 Subject: [PATCH 207/371] sentence refactor iot_speakers --- queries/iot_hue.py | 726 ++++++++++++++++++++-------------------- queries/iot_speakers.py | 59 ++-- queries/sonos.py | 4 +- 3 files changed, 398 insertions(+), 391 deletions(-) diff --git a/queries/iot_hue.py b/queries/iot_hue.py index 561b74f1..ae644111 100755 --- a/queries/iot_hue.py +++ b/queries/iot_hue.py @@ -1,400 +1,400 @@ -""" - - Greynir: Natural language processing for Icelandic - - Randomness query response module - - Copyright (C) 2022 Miðeind ehf. - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see http://www.gnu.org/licenses/. - - This query module handles queries related to the generation - of random numbers, e.g. "Kastaðu tengingi", "Nefndu tölu milli 5 og 10", etc. +# """ + +# Greynir: Natural language processing for Icelandic + +# Randomness query response module + +# Copyright (C) 2022 Miðeind ehf. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. + +# This query module handles queries related to the generation +# of random numbers, e.g. "Kastaðu tengingi", "Nefndu tölu milli 5 og 10", etc. -""" +# """ -# TODO: add "láttu", "hafðu", "litaðu", "kveiktu" functionality. -# TODO: make the objects of sentences more modular, so that the same structure doesn't need to be written for each action -# TODO: ditto the previous comment. make the initial non-terminals general and go into specifics at the terminal level instead. -# TODO: substituion klósett, baðherbergi hugmyndÆ senda lista i javascript og profa i röð -# TODO: Embla stores old javascript code cached which has caused errors -# TODO: Cut down javascript sent to Embla -# TODO: Two specified groups or lights. -# TODO: No specified location -# TODO: Fix scene issues -# TODO: Turning on lights without using "turn on" -# TODO: Add functionality for robot-like commands "ljós í eldhúsinu", "rauður í eldhúsinu" +# # TODO: add "láttu", "hafðu", "litaðu", "kveiktu" functionality. +# # TODO: make the objects of sentences more modular, so that the same structure doesn't need to be written for each action +# # TODO: ditto the previous comment. make the initial non-terminals general and go into specifics at the terminal level instead. +# # TODO: substituion klósett, baðherbergi hugmyndÆ senda lista i javascript og profa i röð +# # TODO: Embla stores old javascript code cached which has caused errors +# # TODO: Cut down javascript sent to Embla +# # TODO: Two specified groups or lights. +# # TODO: No specified location +# # TODO: Fix scene issues +# # TODO: Turning on lights without using "turn on" +# # TODO: Add functionality for robot-like commands "ljós í eldhúsinu", "rauður í eldhúsinu" -from typing import Dict, Mapping, Optional, cast, FrozenSet -from typing_extensions import TypedDict +# from typing import Dict, Mapping, Optional, cast, FrozenSet +# from typing_extensions import TypedDict -import logging -import random -import json -import flask +# import logging +# import random +# import json +# import flask -from reynir.lemmatize import simple_lemmatize +# from reynir.lemmatize import simple_lemmatize -from query import Query, QueryStateDict, AnswerTuple -from queries import gen_answer, read_jsfile, read_grammar_file -from tree import Result, Node, TerminalNode +# from query import Query, QueryStateDict, AnswerTuple +# from queries import gen_answer, read_jsfile, read_grammar_file +# from tree import Result, Node, TerminalNode -class SmartLights(TypedDict): - selected_light: str - philips_hue: Dict[str, str] +# class SmartLights(TypedDict): +# selected_light: str +# philips_hue: Dict[str, str] -class DeviceData(TypedDict): - smartlights: SmartLights +# class DeviceData(TypedDict): +# smartlights: SmartLights -_IoT_QTYPE = "IoT" +# _IoT_QTYPE = "IoT" -TOPIC_LEMMAS = [ - "ljós", - "kveikja", - "litur", - "birta", - "hækka", - "stemmning", - "sena", - "stemming", - "stemning", -] +# TOPIC_LEMMAS = [ +# "ljós", +# "kveikja", +# "litur", +# "birta", +# "hækka", +# "stemmning", +# "sena", +# "stemming", +# "stemning", +# ] -def help_text(lemma: str) -> str: - """Help text to return when query.py is unable to parse a query but - one of the above lemmas is found in it""" - return "Ég skil þig ef þú segir til dæmis: {0}.".format( - random.choice( - ( - "Kveiktu á ljósunum inni í eldhúsi", - "Slökktu á leslampanum", - "Breyttu lit lýsingarinnar í stofunni í bláan", - "Gerðu ljósið í borðstofunni bjartara", - "Stilltu á bjartasta niðri í kjallara", - ) - ) - ) +# def help_text(lemma: str) -> str: +# """Help text to return when query.py is unable to parse a query but +# one of the above lemmas is found in it""" +# return "Ég skil þig ef þú segir til dæmis: {0}.".format( +# random.choice( +# ( +# "Kveiktu á ljósunum inni í eldhúsi", +# "Slökktu á leslampanum", +# "Breyttu lit lýsingarinnar í stofunni í bláan", +# "Gerðu ljósið í borðstofunni bjartara", +# "Stilltu á bjartasta niðri í kjallara", +# ) +# ) +# ) -_COLORS = { - "gulur": 60 * 65535 / 360, - "rauður": 360 * 65535 / 360, - "grænn": 120 * 65535 / 360, - "blár": 240 * 65535 / 360, - "ljósblár": 180 * 65535 / 360, - "bleikur": 300 * 65535 / 360, - "hvítur": [], - "fjólublár": [], - "brúnn": [], - "appelsínugulur": [], -} +# _COLORS = { +# "gulur": 60 * 65535 / 360, +# "rauður": 360 * 65535 / 360, +# "grænn": 120 * 65535 / 360, +# "blár": 240 * 65535 / 360, +# "ljósblár": 180 * 65535 / 360, +# "bleikur": 300 * 65535 / 360, +# "hvítur": [], +# "fjólublár": [], +# "brúnn": [], +# "appelsínugulur": [], +# } -# This module wants to handle parse trees for queries -HANDLE_TREE = True +# # This module wants to handle parse trees for queries +# HANDLE_TREE = True -# The grammar nonterminals this module wants to handle -QUERY_NONTERMINALS = {"QIoT"} +# # The grammar nonterminals this module wants to handle +# QUERY_NONTERMINALS = {"QIoT"} -# The context-free grammar for the queries recognized by this plug-in module -# GRAMMAR = read_grammar_file("iot_hue") +# # The context-free grammar for the queries recognized by this plug-in module +# # GRAMMAR = read_grammar_file("iot_hue") -GRAMMAR = read_grammar_file( - "iot_hue", color_names=" | ".join(f"'{color}:lo'" for color in _COLORS.keys()) -) +# GRAMMAR = read_grammar_file( +# "iot_hue", color_names=" | ".join(f"'{color}:lo'" for color in _COLORS.keys()) +# ) -def QIoTQuery(node: Node, params: QueryStateDict, result: Result) -> None: - result.qtype = _IoT_QTYPE +# def QIoTQuery(node: Node, params: QueryStateDict, result: Result) -> None: +# result.qtype = _IoT_QTYPE -def QIoTColorWord(node: Node, params: QueryStateDict, result: Result) -> None: - result.changing_color = True - - -def QIoTSceneWord(node: Node, params: QueryStateDict, result: Result) -> None: - result.changing_scene = True +# def QIoTColorWord(node: Node, params: QueryStateDict, result: Result) -> None: +# result.changing_color = True + + +# def QIoTSceneWord(node: Node, params: QueryStateDict, result: Result) -> None: +# result.changing_scene = True -def QIoTBrightnessWord(node: Node, params: QueryStateDict, result: Result) -> None: - result.changing_brightness = True +# def QIoTBrightnessWord(node: Node, params: QueryStateDict, result: Result) -> None: +# result.changing_brightness = True -def QIoTTurnOnLightsRest(node: Node, params: QueryStateDict, result: Result) -> None: - result.action = "turn_on" - if "hue_obj" not in result: - result["hue_obj"] = {"on": True} - else: - result["hue_obj"]["on"] = True +# def QIoTTurnOnLightsRest(node: Node, params: QueryStateDict, result: Result) -> None: +# result.action = "turn_on" +# if "hue_obj" not in result: +# result["hue_obj"] = {"on": True} +# else: +# result["hue_obj"]["on"] = True -def QIoTTurnOffLightsRest(node: Node, params: QueryStateDict, result: Result) -> None: - result.action = "turn_off" - if "hue_obj" not in result: - result["hue_obj"] = {"on": False} - else: - result["hue_obj"]["on"] = False +# def QIoTTurnOffLightsRest(node: Node, params: QueryStateDict, result: Result) -> None: +# result.action = "turn_off" +# if "hue_obj" not in result: +# result["hue_obj"] = {"on": False} +# else: +# result["hue_obj"]["on"] = False -def QIoTNewColor(node: Node, params: QueryStateDict, result: Result) -> None: - result.action = "set_color" - print(result.color_name) - color_hue = _COLORS.get(result.color_name, None) - print(color_hue) - if color_hue is not None: - if "hue_obj" not in result: - result["hue_obj"] = {"on": True, "hue": int(color_hue)} - else: - result["hue_obj"]["hue"] = int(color_hue) - result["hue_obj"]["on"] = True +# def QIoTNewColor(node: Node, params: QueryStateDict, result: Result) -> None: +# result.action = "set_color" +# print(result.color_name) +# color_hue = _COLORS.get(result.color_name, None) +# print(color_hue) +# if color_hue is not None: +# if "hue_obj" not in result: +# result["hue_obj"] = {"on": True, "hue": int(color_hue)} +# else: +# result["hue_obj"]["hue"] = int(color_hue) +# result["hue_obj"]["on"] = True -def QIoTMoreBrighterOrHigher( - node: Node, params: QueryStateDict, result: Result -) -> None: - result.action = "increase_brightness" - if "hue_obj" not in result: - result["hue_obj"] = {"on": True, "bri_inc": 64} - else: - result["hue_obj"]["bri_inc"] = 64 - result["hue_obj"]["on"] = True +# def QIoTMoreBrighterOrHigher( +# node: Node, params: QueryStateDict, result: Result +# ) -> None: +# result.action = "increase_brightness" +# if "hue_obj" not in result: +# result["hue_obj"] = {"on": True, "bri_inc": 64} +# else: +# result["hue_obj"]["bri_inc"] = 64 +# result["hue_obj"]["on"] = True - -def QIoTLessDarkerOrLower(node: Node, params: QueryStateDict, result: Result) -> None: - result.action = "decrease_brightness" - if "hue_obj" not in result: - result["hue_obj"] = {"bri_inc": -64} - else: - result["hue_obj"]["bri_inc"] = -64 - - -def QIoTIncreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: - result.action = "increase_brightness" - if "hue_obj" not in result: - result["hue_obj"] = {"on": True, "bri_inc": 64} - else: - result["hue_obj"]["bri_inc"] = 64 - result["hue_obj"]["on"] = True - - -def QIoTDecreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: - result.action = "decrease_brightness" - if "hue_obj" not in result: - result["hue_obj"] = {"bri_inc": -64} - else: - result["hue_obj"]["bri_inc"] = -64 - - -def QIoTBrightest(node: Node, params: QueryStateDict, result: Result) -> None: - result.action = "decrease_brightness" - if "hue_obj" not in result: - result["hue_obj"] = {"bri": 255} - else: - result["hue_obj"]["bri"] = 255 - - -def QIoTDarkest(node: Node, params: QueryStateDict, result: Result) -> None: - result.action = "decrease_brightness" - if "hue_obj" not in result: - result["hue_obj"] = {"bri": 0} - else: - result["hue_obj"]["bri"] = 0 - - -def QIoTNewScene(node: Node, params: QueryStateDict, result: Result) -> None: - result.action = "set_scene" - scene_name = result.get("scene_name", None) - print("scene: " + scene_name) - if scene_name is not None: - if "hue_obj" not in result: - result["hue_obj"] = {"on": True, "scene": scene_name} - else: - result["hue_obj"]["scene"] = scene_name - result["hue_obj"]["on"] = True - - -def QIoTColorName(node: Node, params: QueryStateDict, result: Result) -> None: - result["color_name"] = ( - node.first_child(lambda x: True).string_self().strip("'").split(":")[0] - ) - - -def QIoTSceneName(node: Node, params: QueryStateDict, result: Result) -> None: - result["scene_name"] = result._indefinite - print("scene: " + result.get("scene_name", None)) - - -def QIoTGroupName(node: Node, params: QueryStateDict, result: Result) -> None: - result["group_name"] = result._indefinite - - -def QIoTLightName(node: Node, params: QueryStateDict, result: Result) -> None: - result["light_name"] = result._indefinite - - -def QIoTLightsBanwords(node: Node, params: QueryStateDict, result: Result) -> None: - result.abort = True - - -# Convert color name into hue -# Taken from home.py -_COLOR_NAME_TO_CIE: Mapping[str, float] = { - "gulur": 60 * 65535 / 360, - "grænn": 120 * 65535 / 360, - "ljósblár": 180 * 65535 / 360, - "blár": 240 * 65535 / 360, - "bleikur": 300 * 65535 / 360, - "rauður": 360 * 65535 / 360, - # "Rauð": 360 * 65535 / 360, -} - -_SPEAKER_WORDS: FrozenSet[str] = frozenset( - ( - "tónlist", - "hátalari", - "bylgja", - "útvarp", - "útvarpsstöð", - "útvarp saga", - "gullbylgja", - "x-ið", - "léttbylgjan", - "rás 1", - "rás 2", - "rondo", - "rondó", - "fm 957", - "fm957", - "fm-957", - "k-100", - "k 100", - "kk 100", - "k hundrað", - "kk hundrað", - "x977", - "x 977", - "x-977", - "x-ið 977", - "x-ið", - "retro", - "kiss fm", - "flassbakk", - "flassbakk fm", - "útvarp hundraðið", - "útvarp 101", - "útvarp hundraðogeinn", - "útvarp hundrað og einn", - "útvarp hundrað einn", - "útvarp hundrað 1", - "útvarp", - ) -) - - -def sentence(state: QueryStateDict, result: Result) -> None: - """Called when sentence processing is complete""" - q: Query = state["query"] - if result.get("abort"): - q.set_error("E_QUERY_NOT_UNDERSTOOD") - # lemmas = set( - # i[0].root(state, result.params) - # for i in result.enum_descendants(lambda x: isinstance(x, TerminalNode)) - # ) - # if not _SPEAKER_WORDS.isdisjoint(lemmas): - # print("matched with music word list") - # q.set_error("E_QUERY_NOT_UNDERSTOOD") - # return - changing_color = result.get("changing_color", False) - changing_scene = result.get("changing_scene", False) - changing_brightness = result.get("changing_brightness", False) - print("error?", sum((changing_color, changing_scene, changing_brightness)) > 1) - if ( - sum((changing_color, changing_scene, changing_brightness)) > 1 - or "qtype" not in result - ): - q.set_error("E_QUERY_NOT_UNDERSTOOD") - return - - q.set_qtype(result.qtype) - - smartdevice_type = "iot_lights" - client_id = str(q.client_id) - print("client_id:", client_id) - - # Fetch relevant data from the device_data table to perform an action on the lights - device_data = cast(Optional[DeviceData], q.client_data(smartdevice_type)) - print("location :", q.location) - print("device data :", device_data) - - selected_light: Optional[str] = None - print("selected light:", selected_light) - hue_credentials: Optional[Dict[str, str]] = None - - if device_data is not None: - dev = device_data - assert dev is not None - light = dev.get("philips_hue") - hue_credentials = light.get("credentials") - bridge_ip = hue_credentials.get("ip_address") - username = hue_credentials.get("username") - - if not device_data or not hue_credentials: - answer = "Það vantar að tengja Philips Hub-inn." - q.set_answer(*gen_answer(answer)) - return - - # Successfully matched a query type - print("bridge_ip: ", bridge_ip) - print("username: ", username) - print("selected light :", selected_light) - print("hue credentials :", hue_credentials) - - try: - # kalla í javascripts stuff - light_or_group_name = result.get("light_name", result.get("group_name", "")) - color_name = result.get("color_name", "") - print("GROUP NAME:", light_or_group_name) - print("COLOR NAME:", color_name) - print(result.hue_obj) - q.set_answer( - *gen_answer( - "ég var að kveikja ljósin! " - # + group_name - # + " " - # + color_name - # + " " - # + result.action - # + " " - # + str(result.hue_obj.get("hue", "enginn litur")) - ) - ) - js = ( - read_jsfile("IoT_Embla/fuse.js") - + f"var BRIDGE_IP = '{bridge_ip}';var USERNAME = '{username}';" - + read_jsfile("IoT_Embla/Philips_Hue/fuse_search.js") - + read_jsfile("IoT_Embla/Philips_Hue/lights.js") - + read_jsfile("IoT_Embla/Philips_Hue/set_lights.js") - ) - js += f"setLights('{light_or_group_name}', '{json.dumps(result.hue_obj)}');" - q.set_command(js) - except Exception as e: - logging.warning("Exception while processing random query: {0}".format(e)) - q.set_error("E_EXCEPTION: {0}".format(e)) - raise - - -# f"var BRIDGE_IP = '192.168.1.68';var USERNAME = 'p3obluiXT13IbHMpp4X63ZvZnpNRdbqqMt723gy2';" + +# def QIoTLessDarkerOrLower(node: Node, params: QueryStateDict, result: Result) -> None: +# result.action = "decrease_brightness" +# if "hue_obj" not in result: +# result["hue_obj"] = {"bri_inc": -64} +# else: +# result["hue_obj"]["bri_inc"] = -64 + + +# def QIoTIncreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: +# result.action = "increase_brightness" +# if "hue_obj" not in result: +# result["hue_obj"] = {"on": True, "bri_inc": 64} +# else: +# result["hue_obj"]["bri_inc"] = 64 +# result["hue_obj"]["on"] = True + + +# def QIoTDecreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: +# result.action = "decrease_brightness" +# if "hue_obj" not in result: +# result["hue_obj"] = {"bri_inc": -64} +# else: +# result["hue_obj"]["bri_inc"] = -64 + + +# def QIoTBrightest(node: Node, params: QueryStateDict, result: Result) -> None: +# result.action = "decrease_brightness" +# if "hue_obj" not in result: +# result["hue_obj"] = {"bri": 255} +# else: +# result["hue_obj"]["bri"] = 255 + + +# def QIoTDarkest(node: Node, params: QueryStateDict, result: Result) -> None: +# result.action = "decrease_brightness" +# if "hue_obj" not in result: +# result["hue_obj"] = {"bri": 0} +# else: +# result["hue_obj"]["bri"] = 0 + + +# def QIoTNewScene(node: Node, params: QueryStateDict, result: Result) -> None: +# result.action = "set_scene" +# scene_name = result.get("scene_name", None) +# print("scene: " + scene_name) +# if scene_name is not None: +# if "hue_obj" not in result: +# result["hue_obj"] = {"on": True, "scene": scene_name} +# else: +# result["hue_obj"]["scene"] = scene_name +# result["hue_obj"]["on"] = True + + +# def QIoTColorName(node: Node, params: QueryStateDict, result: Result) -> None: +# result["color_name"] = ( +# node.first_child(lambda x: True).string_self().strip("'").split(":")[0] +# ) + + +# def QIoTSceneName(node: Node, params: QueryStateDict, result: Result) -> None: +# result["scene_name"] = result._indefinite +# print("scene: " + result.get("scene_name", None)) + + +# def QIoTGroupName(node: Node, params: QueryStateDict, result: Result) -> None: +# result["group_name"] = result._indefinite + + +# def QIoTLightName(node: Node, params: QueryStateDict, result: Result) -> None: +# result["light_name"] = result._indefinite + + +# def QIoTLightsBanwords(node: Node, params: QueryStateDict, result: Result) -> None: +# result.abort = True + + +# # Convert color name into hue +# # Taken from home.py +# _COLOR_NAME_TO_CIE: Mapping[str, float] = { +# "gulur": 60 * 65535 / 360, +# "grænn": 120 * 65535 / 360, +# "ljósblár": 180 * 65535 / 360, +# "blár": 240 * 65535 / 360, +# "bleikur": 300 * 65535 / 360, +# "rauður": 360 * 65535 / 360, +# # "Rauð": 360 * 65535 / 360, +# } + +# _SPEAKER_WORDS: FrozenSet[str] = frozenset( +# ( +# "tónlist", +# "hátalari", +# "bylgja", +# "útvarp", +# "útvarpsstöð", +# "útvarp saga", +# "gullbylgja", +# "x-ið", +# "léttbylgjan", +# "rás 1", +# "rás 2", +# "rondo", +# "rondó", +# "fm 957", +# "fm957", +# "fm-957", +# "k-100", +# "k 100", +# "kk 100", +# "k hundrað", +# "kk hundrað", +# "x977", +# "x 977", +# "x-977", +# "x-ið 977", +# "x-ið", +# "retro", +# "kiss fm", +# "flassbakk", +# "flassbakk fm", +# "útvarp hundraðið", +# "útvarp 101", +# "útvarp hundraðogeinn", +# "útvarp hundrað og einn", +# "útvarp hundrað einn", +# "útvarp hundrað 1", +# "útvarp", +# ) +# ) + + +# def sentence(state: QueryStateDict, result: Result) -> None: +# """Called when sentence processing is complete""" +# q: Query = state["query"] +# if result.get("abort"): +# q.set_error("E_QUERY_NOT_UNDERSTOOD") +# # lemmas = set( +# # i[0].root(state, result.params) +# # for i in result.enum_descendants(lambda x: isinstance(x, TerminalNode)) +# # ) +# # if not _SPEAKER_WORDS.isdisjoint(lemmas): +# # print("matched with music word list") +# # q.set_error("E_QUERY_NOT_UNDERSTOOD") +# # return +# changing_color = result.get("changing_color", False) +# changing_scene = result.get("changing_scene", False) +# changing_brightness = result.get("changing_brightness", False) +# print("error?", sum((changing_color, changing_scene, changing_brightness)) > 1) +# if ( +# sum((changing_color, changing_scene, changing_brightness)) > 1 +# or "qtype" not in result +# ): +# q.set_error("E_QUERY_NOT_UNDERSTOOD") +# return + +# q.set_qtype(result.qtype) + +# smartdevice_type = "iot_lights" +# client_id = str(q.client_id) +# print("client_id:", client_id) + +# # Fetch relevant data from the device_data table to perform an action on the lights +# device_data = cast(Optional[DeviceData], q.client_data(smartdevice_type)) +# print("location :", q.location) +# print("device data :", device_data) + +# selected_light: Optional[str] = None +# print("selected light:", selected_light) +# hue_credentials: Optional[Dict[str, str]] = None + +# if device_data is not None: +# dev = device_data +# assert dev is not None +# light = dev.get("philips_hue") +# hue_credentials = light.get("credentials") +# bridge_ip = hue_credentials.get("ip_address") +# username = hue_credentials.get("username") + +# if not device_data or not hue_credentials: +# answer = "Það vantar að tengja Philips Hub-inn." +# q.set_answer(*gen_answer(answer)) +# return + +# # Successfully matched a query type +# print("bridge_ip: ", bridge_ip) +# print("username: ", username) +# print("selected light :", selected_light) +# print("hue credentials :", hue_credentials) + +# try: +# # kalla í javascripts stuff +# light_or_group_name = result.get("light_name", result.get("group_name", "")) +# color_name = result.get("color_name", "") +# print("GROUP NAME:", light_or_group_name) +# print("COLOR NAME:", color_name) +# print(result.hue_obj) +# q.set_answer( +# *gen_answer( +# "ég var að kveikja ljósin! " +# # + group_name +# # + " " +# # + color_name +# # + " " +# # + result.action +# # + " " +# # + str(result.hue_obj.get("hue", "enginn litur")) +# ) +# ) +# js = ( +# read_jsfile("IoT_Embla/fuse.js") +# + f"var BRIDGE_IP = '{bridge_ip}';var USERNAME = '{username}';" +# + read_jsfile("IoT_Embla/Philips_Hue/fuse_search.js") +# + read_jsfile("IoT_Embla/Philips_Hue/lights.js") +# + read_jsfile("IoT_Embla/Philips_Hue/set_lights.js") +# ) +# js += f"setLights('{light_or_group_name}', '{json.dumps(result.hue_obj)}');" +# q.set_command(js) +# except Exception as e: +# logging.warning("Exception while processing random query: {0}".format(e)) +# q.set_error("E_EXCEPTION: {0}".format(e)) +# raise + + +# # f"var BRIDGE_IP = '192.168.1.68';var USERNAME = 'p3obluiXT13IbHMpp4X63ZvZnpNRdbqqMt723gy2';" diff --git a/queries/iot_speakers.py b/queries/iot_speakers.py index db409b10..3735345e 100644 --- a/queries/iot_speakers.py +++ b/queries/iot_speakers.py @@ -275,28 +275,6 @@ def QIoTSpeakerUtvarpSudurland( result.station = "Útvarp Suðurland" -def call_sonos_client(sonos_client, result): - """Call the appropriate function in the SonosClient based on the result""" - handler_func = _HANDLER_MAP[result.qkey][0] - if result.get("station") is not None: - radio_url = _RADIO_STREAMS.get(f"{result.station}") - response = getattr(sonos_client, handler_func)(radio_url) - return response - else: - response = getattr(sonos_client, handler_func)() - return response - - -# Map of query keys to handler functions and the corresponding answer string for Embla -_HANDLER_MAP = { - "turn_on": ["toggle_play", "Ég kveikti á tónlist"], - "turn_off": ["toggle_pause", "Ég slökkti á tónlist"], - "increase_volume": ["increase_volume", "Ég hækkaði í tónlistinni"], - "decrease_volume": ["decrease_volume", "Ég lækkaði í tónlistinni"], - "radio": ["play_radio_stream", "Ég setti á útvarpstöðina"], -} - - def sentence(state: QueryStateDict, result: Result) -> None: """Called when sentence processing is complete""" print("sentence") @@ -305,7 +283,7 @@ def sentence(state: QueryStateDict, result: Result) -> None: result.qkey = "turn_on" if result.qkey == "turn_on" and result.target == "radio": result.qkey = "radio" - if "qtype" in result and "qkey" in result: + if "qtype" in result: print("IF QTYPE AND QKEY") try: q.set_qtype(result.qtype) @@ -316,13 +294,42 @@ def sentence(state: QueryStateDict, result: Result) -> None: device_data, q.client_id, group_name=result.get("group_name") ) print("JUST AFTER SONOS CLIENT") - response = call_sonos_client(sonos_client, result) + # Map of query keys to handler functions and the corresponding answer string for Embla + radio_url = _RADIO_STREAMS.get(result.get("station")) + handler_map = { + "turn_on": [ + sonos_client.toggle_play, + [], + "Ég kveikti á tónlist", + ], + "turn_off": [ + sonos_client.toggle_pause, + [], + "Ég slökkti á tónlist", + ], + "increase_volume": [ + sonos_client.increase_volume, + [], + "Ég hækkaði í tónlistinni", + ], + "decrease_volume": [ + sonos_client.decrease_volume, + [], + "Ég lækkaði í tónlistinni", + ], + "radio": [ + sonos_client.play_radio_stream, + [radio_url], + "Ég setti á útvarpstöðina", + ], + } + handler, args, answer = handler_map.get(result.qkey) + response = handler(*args) if response == "Group not found": text_ans = f"Herbergið '{result.group_name}' fannst ekki. Vinsamlegast athugaðu í Sonos appinu hvort nafnið sé rétt." else: - handler_answer = _HANDLER_MAP[result.qkey][1] + handler_answer = answer text_ans = handler_answer - answer = ( dict(answer=text_ans), text_ans, diff --git a/queries/sonos.py b/queries/sonos.py index 5e969ceb..15ea25d2 100644 --- a/queries/sonos.py +++ b/queries/sonos.py @@ -78,7 +78,7 @@ from util import read_api_key from queries import query_json_api, post_to_json_api from query import Query -from typing import Dict +from typing import Dict, Optional import json @@ -377,7 +377,7 @@ def _create_or_join_session(self): ------------------------------------- PUBLIC METHODS -------------------------------------------------------------------------------- """ - def play_radio_stream(self, radio_url: str): + def play_radio_stream(self, radio_url): #: Optional[str] = self._device_data.get] print("play radio stream") session_id = self._create_or_join_session() From 845174c28d47ebd14c7f1879d99c2bbd96dea455 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Fri, 8 Jul 2022 11:27:19 +0000 Subject: [PATCH 208/371] Created _add_available_rows_to_result since the functionality was being done twice. Added comments to answering funcs. --- queries/theater.py | 97 ++++++++++++++++++++++++++++------------------ 1 file changed, 60 insertions(+), 37 deletions(-) diff --git a/queries/theater.py b/queries/theater.py index 05e30365..3adaa1a0 100644 --- a/queries/theater.py +++ b/queries/theater.py @@ -364,7 +364,7 @@ def _generate_date_answer( with changedlocale(category="LC_TIME"): text_dates.append(date.strftime("\n - %a %d. %b kl. %H:%M")) dates.append(date.strftime("\n%A %d. %B klukkan %H:%M")) - date_number: int = 3 if len(dates) >= 3 else len(dates) + date_number: int = 3 if len(dates) >= 3 else len(dates) # nr dates to be shown start_string: str = ( "Eftirfarandi dagsetning er í boði:" if date_number == 1 @@ -373,15 +373,19 @@ def _generate_date_answer( else "Næstu þrjár dagsetningarnar eru:" ) print("blaaaaa") + # First dates to be shown if index == 0: start_string = start_string.replace("Næstu", "Fyrstu", 1) + # Less than 3 dates to be shown if len(dates) < 3: index = 0 extras["page_index"] = 0 + # Last dates to be shown if index > len(dates) - 3 and len(dates) > 3: start_string = "Síðustu þrjár dagsetningarnar eru:\n" index = max(0, len(dates) - 3) extras["page_index"] = index + # Date is there, not time. Answer with available times if resource.is_partially_fulfilled: show_date: Optional[datetime.date] = cast( DateResource, dsm.get_resource("ShowDate") @@ -401,12 +405,11 @@ def _generate_date_answer( ) text_ans = ans.format(times="".join((show_times))) ans = gen_answer( - resource.prompts["multiple_times_for_date"] - .format(times=natlang_seq(show_times)) - .replace("dagur", "dagurinn") + ans.format(times=natlang_seq(show_times)).replace("dagur", "dagurinn") ) return (dict(answer=text_ans), text_ans, numbers_to_ordinal(voice_ans)) print("UNFULFILLLED?") + # No date selected, list available dates if resource.is_unfulfilled: print("UNFULFILLLED") if len(dates) > 0: @@ -434,6 +437,7 @@ def _generate_date_answer( return (dict(answer=text_ans), text_ans, numbers_to_ordinal(voice_ans)) else: return gen_answer(resource.prompts["no_date_available"].format(show=title)) + # Date and time selected, answer with confirmation if resource.is_fulfilled: date = dsm.get_resource("ShowDate").data time = dsm.get_resource("ShowTime").data @@ -455,6 +459,7 @@ def _generate_seat_count_answer( ) -> Optional[AnswerTuple]: if resource.is_unfulfilled: return gen_answer(resource.prompts["initial"]) + # Seat count is selected, answer with confirmation if resource.is_fulfilled: ans = resource.prompts["confirm"] nr_seats: int = resource.data @@ -470,25 +475,13 @@ def _generate_row_answer( ) -> Optional[AnswerTuple]: title: str = dsm.get_resource("Show").data[0] seats: int = dsm.get_resource("ShowSeatCount").data - available_rows: list[str] = [] - text_available_rows: list[str] = [] - for show in _SHOWS: - if show["title"] == title: - checking_row: int = 1 - seats_in_row: int = 0 - row_added: int = 0 - for (row, _) in show["location"]: - if checking_row == row and row != row_added: - seats_in_row += 1 - if seats_in_row >= seats: - available_rows.append(number_to_text(row)) - text_available_rows.append(str(row)) - seats_in_row = 0 - row_added = row - else: - checking_row = row - seats_in_row = 1 + # get available rows for selected show + if not result.get("available_rows"): + _add_available_rows_to_result(dsm, title, seats, result) + available_rows: list[str] = result["available_rows"] + text_available_rows: list[str] = result["text_available_rows"] if resource.is_unfulfilled: + # No rows available for selected seat count if len(available_rows) == 0: dsm.set_resource_state("ShowDateTime", ResourceState.UNFULFILLED) dsm.extras["page_index"] = 0 @@ -498,6 +491,7 @@ def _generate_row_answer( text_ans = ans.format(seats=seats) voice_ans = ans.format(seats=number_to_text(seats)) return (dict(answer=text_ans), text_ans, voice_ans) + # Returning initial answer with the available rows ans = resource.prompts["initial"] if len(available_rows) == 1: ans = ans.replace("röðum", "röð") @@ -508,6 +502,7 @@ def _generate_row_answer( seats=number_to_text(seats), seat_rows=natlang_seq(available_rows) ) return (dict(answer=text_ans), text_ans, voice_ans) + # Row has been selected, answer with confirm prompt if resource.is_fulfilled: row = dsm.get_resource("ShowSeatRow").data[0] ans = resource.prompts["confirm"] @@ -530,6 +525,7 @@ def _generate_seat_number_answer( if chosen_row == row: text_available_seats.append(str(seat)) available_seats.append(number_to_text(seat)) + # No seat selected, list available seats if resource.is_unfulfilled: ans = resource.prompts["initial"] if len(available_seats) == 1: @@ -539,6 +535,7 @@ def _generate_seat_number_answer( seats=natlang_seq(available_seats), row=number_to_text(chosen_row) ) return (dict(answer=text_ans), text_ans, voice_ans) + # Seat has been selected, answer with confirmation if resource.is_fulfilled: chosen_seats_voice_string: str = "" chosen_seats_text_string: str = "" @@ -847,29 +844,56 @@ def QTheaterShowSeatCountQuery( # result.invalid_seat_count = True +def _add_available_rows_to_result( + dsm: DialogueStateManager, title: str, seats: int, result: Result +) -> None: + available_rows: list[str] = [] + text_available_rows: list[str] = [] + for show in _SHOWS: + if show["title"] == title: + checking_row: int = 1 + seats_in_row: int = 0 + row_added: int = 0 + for (row, _) in show["location"]: + if checking_row == row and row != row_added: + seats_in_row += 1 + if seats_in_row >= seats: + available_rows.append(number_to_text(row)) + text_available_rows.append(str(row)) + seats_in_row = 0 + row_added = row + else: + checking_row = row + seats_in_row = 1 + result.available_rows = available_rows + result.text_available_rows = text_available_rows + + def QTheaterShowRow(node: Node, params: QueryStateDict, result: Result) -> None: dsm: DialogueStateManager = Query.get_dsm(result) if dsm.get_resource("ShowSeatCount").is_confirmed: title: str = dsm.get_resource("Show").data[0] seats: int = dsm.get_resource("ShowSeatCount").data resource: ListResource = cast(ListResource, dsm.get_resource("ShowSeatRow")) - available_rows: list[int] = [] - for show in _SHOWS: - if show["title"] == title: - checking_row: int = 1 - seats_in_row: int = 0 - for (row, _) in show["location"]: - if checking_row == row: - seats_in_row += 1 - if seats_in_row >= seats: - available_rows.append(row) - seats_in_row = 0 - else: - checking_row = row - seats_in_row = 1 + + # Adding rows to result + _add_available_rows_to_result(dsm, title, seats, result) + available_rows = result.available_rows + # No rows available + if len(available_rows) == 0: + dsm.set_resource_state("ShowDateTime", ResourceState.UNFULFILLED) + dsm.extras["page_index"] = 0 + ans = resource.prompts["not_enough_seats"] + if seats == 1: + ans = ans.replace("laus", "laust") + text_ans = ans.format(seats=seats) + voice_ans = ans.format(seats=number_to_text(seats)) + dsm.set_answer((dict(answer=text_ans), text_ans, voice_ans)) + # Available row chosen if result.number in available_rows: resource.data = [result.number] dsm.set_resource_state(resource.name, ResourceState.FULFILLED) + # Incorrect row chosen else: dsm.set_resource_state(resource.name, ResourceState.UNFULFILLED) ans = resource.prompts["no_row_matched"] @@ -878,7 +902,6 @@ def QTheaterShowRow(node: Node, params: QueryStateDict, result: Result) -> None: text_ans = ans.format(seats=seats) voice_ans = ans.format(seats=number_to_text(seats)) dsm.set_answer((dict(answer=text_ans), text_ans, voice_ans)) - # result.no_row_matched = True def QTheaterShowSeats(node: Node, params: QueryStateDict, result: Result) -> None: From c1eb833df2151b3d922cffdf194c8f79809957f6 Mon Sep 17 00:00:00 2001 From: Logi E Date: Fri, 8 Jul 2022 11:27:52 +0000 Subject: [PATCH 209/371] added simple price and length queries to theater --- queries/theater.py | 171 ++++++++++++++------------------------------- 1 file changed, 53 insertions(+), 118 deletions(-) diff --git a/queries/theater.py b/queries/theater.py index a93560c8..4d48801f 100644 --- a/queries/theater.py +++ b/queries/theater.py @@ -31,8 +31,8 @@ from settings import changedlocale from query import Query, QueryStateDict from tree import Result, Node, TerminalNode -from queries import AnswerTuple, gen_answer, natlang_seq, parse_num, query_json_api -from queries.num import number_to_text, numbers_to_ordinal +from queries import AnswerTuple, gen_answer, natlang_seq, parse_num, time_period_desc +from queries.num import number_to_text, numbers_to_ordinal, numbers_to_text from queries.resources import ( DateResource, FinalResource, @@ -107,13 +107,15 @@ def help_text(lemma: str) -> str: | "fá" | "panta" -QTheaterDialogue → +QTheaterDialogue → QTheaterShowQuery | QTheaterShowDateQuery | QTheaterMoreDates | QTheaterPreviousDates | QTheaterShowSeatCountQuery | QTheaterShowLocationQuery + | QTheaterShowPrice + | QTheaterShowLength | QTheaterOptions | QTheaterYes | QTheaterNo @@ -162,6 +164,14 @@ def help_text(lemma: str) -> str: QTheaterShowName → Nl +QTheaterShowPrice → + "hvað" "kostar" "einn"? 'miði' + | "hvað" "kostar" "1"? 'miði' + | "hvað" "kostar" "einn"? 'miðinn' "á" "sýninguna" + +QTheaterShowLength → + "hvað" "er" "sýningin" "löng" + QTheaterShowDateQuery → QTheaterEgVil? "fara"? "á"? 'sýning'? QTheaterShowDate @@ -272,6 +282,8 @@ def help_text(lemma: str) -> str: class ShowType(TypedDict): title: str + price: int + show_length: int date: List[datetime.datetime] location: List[Tuple[int, int]] @@ -279,6 +291,8 @@ class ShowType(TypedDict): _SHOWS: List[ShowType] = [ { "title": "Emil í Kattholti", + "price": 5900, + "show_length": 150, "date": [ datetime.datetime(2022, 8, 27, 13, 0), datetime.datetime(2022, 8, 28, 13, 0), @@ -305,6 +319,8 @@ class ShowType(TypedDict): }, { "title": "Lína Langsokkur", + "price": 3900, + "show_length": 90, "date": [ datetime.datetime(2022, 8, 27, 13, 0), datetime.datetime(2022, 8, 28, 13, 0), @@ -338,26 +354,6 @@ class ShowType(TypedDict): def _generate_show_answer( resource: ListResource, dsm: DialogueStateManager, result: Result ) -> Optional[AnswerTuple]: - # if (not resource.is_confirmed and result.get("options_info")) or result.get( - # "show_options" - # ): - # shows: list[str] = [] - # for show in _SHOWS: - # shows.append("\n - " + show["title"]) - # ans = resource.prompts["options"] - # if len(shows) == 1: - # ans = ans.replace("Sýningarnar", "Sýningin", 1).replace("eru", "er", 2) - # text_ans = ans.format(options="".join(shows)) - # voice_ans = ans.format(options=natlang_seq(shows)).replace("-", "") - # return (dict(answer=text_ans), text_ans, voice_ans) - # if result.get("no_show_matched"): - # return gen_answer(resource.prompts["no_show_matched"]) - # if result.get("no_show_matched_data_exists"): - # return gen_answer( - # resource.prompts["no_show_matched_data_exists"].format( - # show=resource.data[0] - # ) - # ) if resource.is_unfulfilled: return gen_answer(resource.prompts["initial"]) if resource.is_fulfilled: @@ -392,7 +388,6 @@ def _generate_date_answer( if date_number == 2 else "Næstu þrjár dagsetningarnar eru:" ) - print("blaaaaa") if index == 0: start_string = start_string.replace("Næstu", "Fyrstu", 1) if len(dates) < 3: @@ -402,40 +397,6 @@ def _generate_date_answer( start_string = "Síðustu þrjár dagsetningarnar eru:\n" index = max(0, len(dates) - 3) extras["page_index"] = index - - # if (not resource.is_confirmed and result.get("options_info")) or result.get( - # "date_options" - # ): - # options_string = ( - # start_string + natlang_seq(dates[index : index + date_number]) - # ).replace("dagur", "dagurinn") - # text_options_string = start_string + "".join( - # text_dates[index : index + date_number] - # ) - # if len(dates) > 0: - # ans = resource.prompts["options"] - # if date_number == 1: - # ans = ans.replace("eru", "er", 1).replace( - # "dagsetningar", "dagsetning", 1 - # ) - # voice_ans = ans.format( - # options=options_string, - # date_number=number_to_text(len(dates), gender="kvk"), - # ).replace("\n", _BREAK_SSML) - # text_ans = ans.format( - # options=text_options_string, - # date_number=number_to_text(len(dates), gender="kvk"), - # ) - - # return (dict(answer=text_ans), text_ans, numbers_to_ordinal(voice_ans)) - # else: - # return gen_answer(resource.prompts["no_date_available"].format(show=title)) - # if result.get("no_date_matched"): - # return gen_answer(resource.prompts["no_date_matched"]) - # if result.get("no_time_matched"): - # return gen_answer(resource.prompts["no_time_matched"]) - # if result.get("many_matching_times"): - # return gen_answer(resource.prompts["many_matching_times"]) if resource.is_partially_fulfilled: show_date: Optional[datetime.date] = cast( DateResource, dsm.get_resource("ShowDate") @@ -460,9 +421,8 @@ def _generate_date_answer( .replace("dagur", "dagurinn") ) return (dict(answer=text_ans), text_ans, numbers_to_ordinal(voice_ans)) - print("UNFULFILLLED?") + print("UNFULFILLLED?", resource.is_unfulfilled) if resource.is_unfulfilled: - print("UNFULFILLLED") if len(dates) > 0: ans = resource.prompts["initial"] if date_number == 1: @@ -507,8 +467,6 @@ def _generate_date_answer( def _generate_seat_count_answer( resource: NumberResource, dsm: DialogueStateManager, result: Result ) -> Optional[AnswerTuple]: - # if result.get("invalid_seat_count"): - # return gen_answer(resource.prompts["invalid_seat_count"]) if resource.is_unfulfilled: return gen_answer(resource.prompts["initial"]) if resource.is_fulfilled: @@ -544,26 +502,6 @@ def _generate_row_answer( else: checking_row = row seats_in_row = 1 - # if (not resource.is_confirmed and result.get("options_info")) or result.get( - # "row_options" - # ): - # ans = resource.prompts["options"] - # if len(available_rows) == 1: - # ans = ans.replace("eru", "er").replace("Raðir", "Röð") - # if seats == 1: - # ans = ans.replace("laus", "laust") - # text_ans = ans.format(rows=natlang_seq(text_available_rows), seats=seats) - # voice_ans = ans.format( - # rows=natlang_seq(available_rows), seats=number_to_text(seats) - # ) - # return (dict(answer=text_ans), text_ans, voice_ans) - # if result.get("no_row_matched"): - # ans = resource.prompts["no_row_matched"] - # if seats == 1: - # ans = ans.replace("laus", "laust") - # text_ans = ans.format(seats=seats) - # voice_ans = ans.format(seats=number_to_text(seats)) - # return (dict(answer=text_ans), text_ans, voice_ans) if resource.is_unfulfilled: if len(available_rows) == 0: dsm.set_resource_state("ShowDateTime", ResourceState.UNFULFILLED) @@ -606,32 +544,6 @@ def _generate_seat_number_answer( if chosen_row == row: text_available_seats.append(str(seat)) available_seats.append(number_to_text(seat)) - # if (not resource.is_confirmed and result.get("options_info")) or result.get( - # "seat_options" - # ): - # ans = resource.prompts["options"] - # if len(available_seats) == 1: - # ans = ans.replace("Sætin", "Sætið", 1).replace("eru", "er", 2) - # text_ans = ans.format(row=chosen_row, options=natlang_seq(text_available_seats)) - # voice_ans = ans.format( - # row=number_to_text(chosen_row), options=natlang_seq(available_seats) - # ) - # return (dict(answer=text_ans), text_ans, voice_ans) - # if result.get("wrong_number_seats_selected"): - # if len(result.get("numbers")) > 1: - # chosen_seats = len( - # range(result.get("numbers")[0], result.get("numbers")[1] + 1) - # ) - # else: - # chosen_seats = 1 - # ans = resource.prompts["wrong_number_seats_selected"] - # text_ans = ans.format(chosen_seats=chosen_seats, seats=seats) - # voice_ans = ans.format( - # chosen_seats=number_to_text(chosen_seats), seats=number_to_text(seats) - # ) - # return (dict(answer=text_ans), text_ans, voice_ans) - # if result.get("seats_unavailable"): - # return gen_answer(resource.prompts["seats_unavailable"]) if resource.is_unfulfilled: ans = resource.prompts["initial"] if len(available_seats) == 1: @@ -734,7 +646,7 @@ def QTheaterShowQuery(node: Node, params: QueryStateDict, result: Result) -> Non resource: ListResource = cast(ListResource, dsm.get_resource("Show")) show_exists = False for show in _SHOWS: - if show["title"] == selected_show: + if show["title"].lower() == selected_show.lower(): resource.data = [show["title"]] dsm.set_resource_state(resource.name, ResourceState.FULFILLED) show_exists = True @@ -753,6 +665,29 @@ def QTheaterShowQuery(node: Node, params: QueryStateDict, result: Result) -> Non ) # result.no_show_matched_data_exists = True +def QTheaterShowPrice(node: Node, params: QueryStateDict, result: Result) -> None: + dsm = Query.get_dsm(result) + show = dsm.get_resource("Show") + if show.is_confirmed: + show = [s for s in _SHOWS if s["title"].lower() == show.data[0].lower()][0] + price = show["price"] + ans = f"Verðið fyrir einn miða á sýninguna {show['title']} er {price} kr." + + dsm.set_answer(({"answer": ans}, ans, numbers_to_text(ans, gender="kvk").replace("kr", "krónur"))) + else: + dsm.set_answer(gen_answer("Þú hefur ekki valið sýningu.")) + +def QTheaterShowLength(node: Node, params: QueryStateDict, result: Result) -> None: + dsm = Query.get_dsm(result) + show = dsm.get_resource("Show") + if show.is_confirmed: + show = [s for s in _SHOWS if s["title"].lower() == show.data[0].lower()][0] + length = show["show_length"] + ans = f"Lengd sýningarinnar {show['title']} er {time_period_desc(length*60)}." + + dsm.set_answer(({"answer": ans}, ans, numbers_to_text(ans, gender="kvk"))) + else: + dsm.set_answer(gen_answer("Þú hefur ekki valið sýningu.")) def _add_date( resource: DateResource, dsm: DialogueStateManager, result: Result @@ -761,7 +696,7 @@ def _add_date( if dsm.get_resource("Show").is_confirmed: show_title: str = dsm.get_resource("Show").data[0] for show in _SHOWS: - if show["title"] == show_title: + if show["title"].lower() == show_title.lower(): for date in show["date"]: if result["show_date"] == date.date(): resource.set_date(date.date()) @@ -772,7 +707,7 @@ def _add_date( datetime_resource: Resource = dsm.get_resource("ShowDateTime") show_times: list[datetime.time] = [] for show in _SHOWS: - if show["title"] == show_title: + if show["title"].lower() == show_title.lower(): for date in show["date"]: if resource.date == date.date(): show_times.append(date.time()) @@ -803,7 +738,7 @@ def _add_time( first_matching_date: Optional[datetime.datetime] = None if date_resource.is_fulfilled: for show in _SHOWS: - if show["title"] == show_title: + if show["title"].lower() == show_title.lower(): for date in show["date"]: if ( date_resource.date == date.date() @@ -821,7 +756,7 @@ def _add_time( result.wrong_show_time = True else: for show in _SHOWS: - if show["title"] == show_title: + if show["title"].lower() == show_title.lower(): for date in show["date"]: if result["show_time"] == date.time(): if first_matching_date is None: @@ -957,7 +892,7 @@ def QTheaterShowRow(node: Node, params: QueryStateDict, result: Result) -> None: resource: ListResource = cast(ListResource, dsm.get_resource("ShowSeatRow")) available_rows: list[int] = [] for show in _SHOWS: - if show["title"] == title: + if show["title"].lower() == title.lower(): checking_row: int = 1 seats_in_row: int = 0 for (row, _) in show["location"]: @@ -1018,7 +953,7 @@ def QTheaterShowSeats(node: Node, params: QueryStateDict, result: Result) -> Non dsm.set_answer((dict(answer=text_ans), text_ans, voice_ans)) return for show in _SHOWS: - if show["title"] == title: + if show["title"].lower() == title.lower(): seats: list[int] = [] for seat in selected_seats: if (row, seat) in show["location"]: @@ -1081,7 +1016,7 @@ def QTheaterDateOptions(node: Node, params: QueryStateDict, result: Result) -> N if "page_index" in extras: index = extras["page_index"] for show in _SHOWS: - if show["title"] == title: + if show["title"].lower() == title.lower(): print("itering dates") for date in show["date"]: with changedlocale(category="LC_TIME"): @@ -1145,7 +1080,7 @@ def QTheaterRowOptions(node: Node, params: QueryStateDict, result: Result) -> No available_rows: list[str] = [] text_available_rows: list[str] = [] for show in _SHOWS: - if show["title"] == title: + if show["title"].lower() == title.lower(): checking_row: int = 1 seats_in_row: int = 0 row_added: int = 0 @@ -1182,7 +1117,7 @@ def QTheaterSeatOptions(node: Node, params: QueryStateDict, result: Result) -> N available_seats: list[str] = [] text_available_seats: list[str] = [] for show in _SHOWS: - if show["title"] == title: + if show["title"].lower() == title.lower(): for (row, seat) in show["location"]: if chosen_row == row: text_available_seats.append(str(seat)) From 5fc4cc49bf52d4743518a7090c330968f1fdaa62 Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Fri, 8 Jul 2022 11:45:11 +0000 Subject: [PATCH 210/371] Sonos store last radio url --- queries/sonos.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/queries/sonos.py b/queries/sonos.py index 15ea25d2..a9b13943 100644 --- a/queries/sonos.py +++ b/queries/sonos.py @@ -377,12 +377,17 @@ def _create_or_join_session(self): ------------------------------------- PUBLIC METHODS -------------------------------------------------------------------------------- """ - def play_radio_stream(self, radio_url): #: Optional[str] = self._device_data.get] + def play_radio_stream(self, radio_url): #: Optional[str] = self._device_data.get] print("play radio stream") session_id = self._create_or_join_session() + if radio_url is None: + try: + radio_url = self._device_data["sonos"]["data"]["last_radio_url"] + except KeyError: + radio_url = "http://netradio.ruv.is/rondo.mp3" url = f"https://api.ws.sonos.com/control/api/v1//playbackSessions/{session_id}/playbackSession/loadStreamUrl?" - + print("RADIO URL :", radio_url) payload = json.dumps( { "streamUrl": f"{radio_url}", @@ -399,6 +404,8 @@ def play_radio_stream(self, radio_url): #: Optional[str] = self._device_data.get response = post_to_json_api(url, payload, headers) if response is None: return "Group not found" + data_dict = {"sonos": {"data": {"last_radio_url": radio_url}}} + self._store_data(data_dict) print(response.get("text")) def increase_volume(self): From 4d934952b3d790a05b9b49e272fa1647245a32f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Fri, 8 Jul 2022 11:48:04 +0000 Subject: [PATCH 211/371] Fixed chosen row not being accepted if it was correct --- queries/theater.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/queries/theater.py b/queries/theater.py index 20f3d4d3..51979a7d 100644 --- a/queries/theater.py +++ b/queries/theater.py @@ -666,6 +666,7 @@ def QTheaterShowQuery(node: Node, params: QueryStateDict, result: Result) -> Non ) # result.no_show_matched_data_exists = True + def QTheaterShowPrice(node: Node, params: QueryStateDict, result: Result) -> None: dsm = Query.get_dsm(result) show = dsm.get_resource("Show") @@ -674,10 +675,17 @@ def QTheaterShowPrice(node: Node, params: QueryStateDict, result: Result) -> Non price = show["price"] ans = f"Verðið fyrir einn miða á sýninguna {show['title']} er {price} kr." - dsm.set_answer(({"answer": ans}, ans, numbers_to_text(ans, gender="kvk").replace("kr", "krónur"))) + dsm.set_answer( + ( + {"answer": ans}, + ans, + numbers_to_text(ans, gender="kvk").replace("kr", "krónur"), + ) + ) else: dsm.set_answer(gen_answer("Þú hefur ekki valið sýningu.")) + def QTheaterShowLength(node: Node, params: QueryStateDict, result: Result) -> None: dsm = Query.get_dsm(result) show = dsm.get_resource("Show") @@ -690,6 +698,7 @@ def QTheaterShowLength(node: Node, params: QueryStateDict, result: Result) -> No else: dsm.set_answer(gen_answer("Þú hefur ekki valið sýningu.")) + def _add_date( resource: DateResource, dsm: DialogueStateManager, result: Result ) -> None: @@ -919,7 +928,7 @@ def QTheaterShowRow(node: Node, params: QueryStateDict, result: Result) -> None: # Adding rows to result _add_available_rows_to_result(dsm, title, seats, result) - available_rows = result.available_rows + available_rows: list[int] = [int(i) for i in result.text_available_rows] # No rows available if len(available_rows) == 0: dsm.set_resource_state("ShowDateTime", ResourceState.UNFULFILLED) @@ -930,8 +939,11 @@ def QTheaterShowRow(node: Node, params: QueryStateDict, result: Result) -> None: text_ans = ans.format(seats=seats) voice_ans = ans.format(seats=number_to_text(seats)) dsm.set_answer((dict(answer=text_ans), text_ans, voice_ans)) + print("Available rows: ", available_rows) + print("Result number: ", result.number) # Available row chosen if result.number in available_rows: + print("Chosen row: ", result.number) resource.data = [result.number] dsm.set_resource_state(resource.name, ResourceState.FULFILLED) # Incorrect row chosen From d73dc29bad376e3a9d485b68bcfda88c09211c68 Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Fri, 8 Jul 2022 11:51:37 +0000 Subject: [PATCH 212/371] Added fast forward and rewind grammar also fixed some hotword issues as a result --- queries/grammars/iot_hue.grammar | 15 +- queries/grammars/iot_speakers.grammar | 34 +- queries/iot_hue.py | 625 +++++++++++++------------- queries/iot_speakers.py | 22 +- 4 files changed, 373 insertions(+), 323 deletions(-) diff --git a/queries/grammars/iot_hue.grammar b/queries/grammars/iot_hue.grammar index 56656c10..2466e5e2 100644 --- a/queries/grammars/iot_hue.grammar +++ b/queries/grammars/iot_hue.grammar @@ -186,17 +186,20 @@ QIoTLocationPrepositionSecondPart -> "á" | "í" QIoTGroupName/fall -> - no/fall + QIoTLightsBanwords/fall + > no/fall QIoTLightName/fall -> - no/fall + QIoTLightsBanwords/fall + > no/fall QIoTColorName -> {color_names} QIoTSceneName -> - no - | lo + QIoTLightsBanwords/fall + > no + > lo QIoTAnnadAndlag -> QIoTNewSetting/nf @@ -320,5 +323,5 @@ QIoTSettingWord/fall -> 'stilling'/fall # Catching hotwords from iot_speakers -QIoTLightsBanwords -> - QIoTSpeakerHotwords/nf \ No newline at end of file +QIoTLightsBanwords/fall -> + QIoTSpeakerHotwords/fall \ No newline at end of file diff --git a/queries/grammars/iot_speakers.grammar b/queries/grammars/iot_speakers.grammar index 611d59de..bf6cb58b 100644 --- a/queries/grammars/iot_speakers.grammar +++ b/queries/grammars/iot_speakers.grammar @@ -12,6 +12,7 @@ Query → QIoTSpeaker → QIoTSpeakerQuery +# TODO: Flesh out skip queries QIoTSpeakerQuery → QIoTSpeakerMakeVerb QIoTSpeakerMakeRest | QIoTSpeakerSetVerb QIoTSpeakerSetRest @@ -19,7 +20,8 @@ QIoTSpeakerQuery → | QIoTSpeakerTurnOnOrOffVerb QIoTSpeakerTurnOrOffOnRest | QIoTSpeakerPlayOrPauseVerb QIoTSpeakerPlayOrPauseRest | QIoTSpeakerIncreaseOrDecreaseVerb QIoTSpeakerIncreaseOrDecreaseRest - | QIoTSpeakerRadioStationName + | QIoTSpeakerSkipVerb + | QIoTSpeakerNewSetting/fall QIoTSpeakerMakeVerb → 'gera:so'_bh @@ -71,6 +73,9 @@ QIoTSpeakerDecreaseVerb → 'lækka:so'_bh | 'minnka:so'_bh +QIoTSpeakerSkipVerb -> + 'skippa:so'_bh + QIoTSpeakerMakeRest → QIoTSpeakerMusicOrSoundPhrase/þf QIoTSpeakerComparative/nf QIoTSpeakerHvar | QIoTSpeakerMusicOrSoundPhrase/þf QIoTSpeakerHvar? QIoTSpeakerComparative/nf @@ -93,7 +98,8 @@ QIoTSpeakerTurnOrOffOnRest → QIoTSpeakerPlayOrPauseRest → QIoTSpeakerMusicWord/þf QIoTSpeakerHvarWithSpeaker? - | QIoTSpeakerNewRadio/þf? QIoTSpeakerHvarWithSpeaker? + | QIoTSpeakerNewRadio/þf QIoTSpeakerHvarWithSpeaker? + | QIoTSpeakerNewNextOrPrevious/þf QIoTSpeakerHvarWithSpeaker? QIoTSpeakerIncreaseOrDecreaseRest → QIoTSpeakerMusicOrSoundPhrase/þf QIoTSpeakerHvar? @@ -113,15 +119,20 @@ QIoTSpeakerAHverju → "á" QIoTSpeakerMusicOrApplianceWord/þgf | "á" QIoTSpeakerNewRadio/þgf | "á" QIoTSpeakerMusicOrRadioWord/þgf "í" QIoTSpeakerSpeakerWord/þgf + | "á" QIoTSpeakerNewNextOrPrevious/fall QIoTSpeakerNewSetting/fall → QIoTSpeakerNewRadio/fall - | QIoTSpeakerNewPlay/fall - | QIoTSpeakerNewPause/fall + | QIoTSpeakerNewPlayOrPause/fall + | QIoTSpeakerNewNextOrPrevious/fall QIoTSpeakerNewRadio/fall → QIoTSpeakerRadioStationWord/fall? QIoTSpeakerRadioStationName +QIoTSpeakerNewPlayOrPause/fall -> + QIoTSpeakerNewPlay/fall + | QIoTSpeakerNewPause/fall + QIoTSpeakerNewPlay/fall → "play" | "plei" @@ -131,6 +142,17 @@ QIoTSpeakerNewPause/fall → 'pása'/fall | 'stopp'/fall +QIoTSpeakerNewNextOrPrevious/fall -> + QIoTSpeakerNewNext/fall + | QIoTSpeakerNewPrevious/fall + +QIoTSpeakerNewNext/fall -> + 'næstur:lo'_kvk/fall 'lag:hk'_et/fall + +QIoTSpeakerNewPrevious/fall -> + 'seinastur:lo'_kvk/fall 'lag:hk'_et/fall + | 'síðastur:lo'_kvk/fall 'lag:hk'_et/fall + QIoTSpeakerMusicOrSoundPhrase/fall → QIoTSpeakerMusicPhrase/fall | QIoTSpeakerSoundPhrase/fall @@ -437,8 +459,10 @@ QIoTSpeaker90s → "næntís" | "nineties" +# TODO: Fix the song hotword # Hotwords to block other similarly phrased query modules from catching queries belonging to iot_speakers QIoTSpeakerHotwords/fall -> QIoTSpeakerMusicOrApplianceWord/fall | QIoTSpeakerSoundWord/fall - | QIoTSpeakerRadioStationName \ No newline at end of file + | QIoTSpeakerRadioStationName + | 'lag:no' \ No newline at end of file diff --git a/queries/iot_hue.py b/queries/iot_hue.py index ae644111..f9553680 100755 --- a/queries/iot_hue.py +++ b/queries/iot_hue.py @@ -1,266 +1,267 @@ -# """ - -# Greynir: Natural language processing for Icelandic - -# Randomness query response module - -# Copyright (C) 2022 Miðeind ehf. - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see http://www.gnu.org/licenses/. - -# This query module handles queries related to the generation -# of random numbers, e.g. "Kastaðu tengingi", "Nefndu tölu milli 5 og 10", etc. - -# """ - -# # TODO: add "láttu", "hafðu", "litaðu", "kveiktu" functionality. -# # TODO: make the objects of sentences more modular, so that the same structure doesn't need to be written for each action -# # TODO: ditto the previous comment. make the initial non-terminals general and go into specifics at the terminal level instead. -# # TODO: substituion klósett, baðherbergi hugmyndÆ senda lista i javascript og profa i röð -# # TODO: Embla stores old javascript code cached which has caused errors -# # TODO: Cut down javascript sent to Embla -# # TODO: Two specified groups or lights. -# # TODO: No specified location -# # TODO: Fix scene issues -# # TODO: Turning on lights without using "turn on" -# # TODO: Add functionality for robot-like commands "ljós í eldhúsinu", "rauður í eldhúsinu" - -# from typing import Dict, Mapping, Optional, cast, FrozenSet -# from typing_extensions import TypedDict - -# import logging -# import random -# import json -# import flask - -# from reynir.lemmatize import simple_lemmatize - -# from query import Query, QueryStateDict, AnswerTuple -# from queries import gen_answer, read_jsfile, read_grammar_file -# from tree import Result, Node, TerminalNode - - -# class SmartLights(TypedDict): -# selected_light: str -# philips_hue: Dict[str, str] - - -# class DeviceData(TypedDict): -# smartlights: SmartLights - - -# _IoT_QTYPE = "IoT" - -# TOPIC_LEMMAS = [ -# "ljós", -# "kveikja", -# "litur", -# "birta", -# "hækka", -# "stemmning", -# "sena", -# "stemming", -# "stemning", -# ] - - -# def help_text(lemma: str) -> str: -# """Help text to return when query.py is unable to parse a query but -# one of the above lemmas is found in it""" -# return "Ég skil þig ef þú segir til dæmis: {0}.".format( -# random.choice( -# ( -# "Kveiktu á ljósunum inni í eldhúsi", -# "Slökktu á leslampanum", -# "Breyttu lit lýsingarinnar í stofunni í bláan", -# "Gerðu ljósið í borðstofunni bjartara", -# "Stilltu á bjartasta niðri í kjallara", -# ) -# ) -# ) +""" + + Greynir: Natural language processing for Icelandic + + Randomness query response module + + Copyright (C) 2022 Miðeind ehf. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see http://www.gnu.org/licenses/. + This query module handles queries related to the generation + of random numbers, e.g. "Kastaðu tengingi", "Nefndu tölu milli 5 og 10", etc. -# _COLORS = { -# "gulur": 60 * 65535 / 360, -# "rauður": 360 * 65535 / 360, -# "grænn": 120 * 65535 / 360, -# "blár": 240 * 65535 / 360, -# "ljósblár": 180 * 65535 / 360, -# "bleikur": 300 * 65535 / 360, -# "hvítur": [], -# "fjólublár": [], -# "brúnn": [], -# "appelsínugulur": [], -# } +""" +# TODO: add "láttu", "hafðu", "litaðu", "kveiktu" functionality. +# TODO: make the objects of sentences more modular, so that the same structure doesn't need to be written for each action +# TODO: ditto the previous comment. make the initial non-terminals general and go into specifics at the terminal level instead. +# TODO: substituion klósett, baðherbergi hugmyndÆ senda lista i javascript og profa i röð +# TODO: Embla stores old javascript code cached which has caused errors +# TODO: Cut down javascript sent to Embla +# TODO: Two specified groups or lights. +# TODO: No specified location +# TODO: Fix scene issues +# TODO: Turning on lights without using "turn on" +# TODO: Add functionality for robot-like commands "ljós í eldhúsinu", "rauður í eldhúsinu" -# # This module wants to handle parse trees for queries -# HANDLE_TREE = True +from typing import Dict, Mapping, Optional, cast, FrozenSet +from typing_extensions import TypedDict -# # The grammar nonterminals this module wants to handle -# QUERY_NONTERMINALS = {"QIoT"} +import logging +import random +import json +import flask -# # The context-free grammar for the queries recognized by this plug-in module -# # GRAMMAR = read_grammar_file("iot_hue") +from reynir.lemmatize import simple_lemmatize -# GRAMMAR = read_grammar_file( -# "iot_hue", color_names=" | ".join(f"'{color}:lo'" for color in _COLORS.keys()) -# ) +from query import Query, QueryStateDict, AnswerTuple +from queries import gen_answer, read_jsfile, read_grammar_file +from tree import Result, Node, TerminalNode -# def QIoTQuery(node: Node, params: QueryStateDict, result: Result) -> None: -# result.qtype = _IoT_QTYPE +class SmartLights(TypedDict): + selected_light: str + philips_hue: Dict[str, str] -# def QIoTColorWord(node: Node, params: QueryStateDict, result: Result) -> None: -# result.changing_color = True +class DeviceData(TypedDict): + smartlights: SmartLights -# def QIoTSceneWord(node: Node, params: QueryStateDict, result: Result) -> None: -# result.changing_scene = True +_IoT_QTYPE = "IoT" -# def QIoTBrightnessWord(node: Node, params: QueryStateDict, result: Result) -> None: -# result.changing_brightness = True +TOPIC_LEMMAS = [ + "ljós", + "kveikja", + "litur", + "birta", + "hækka", + "stemmning", + "sena", + "stemming", + "stemning", +] +def help_text(lemma: str) -> str: + """Help text to return when query.py is unable to parse a query but + one of the above lemmas is found in it""" + return "Ég skil þig ef þú segir til dæmis: {0}.".format( + random.choice( + ( + "Kveiktu á ljósunum inni í eldhúsi", + "Slökktu á leslampanum", + "Breyttu lit lýsingarinnar í stofunni í bláan", + "Gerðu ljósið í borðstofunni bjartara", + "Stilltu á bjartasta niðri í kjallara", + ) + ) + ) -# def QIoTTurnOnLightsRest(node: Node, params: QueryStateDict, result: Result) -> None: -# result.action = "turn_on" -# if "hue_obj" not in result: -# result["hue_obj"] = {"on": True} -# else: -# result["hue_obj"]["on"] = True +_COLORS = { + "gulur": 60 * 65535 / 360, + "rauður": 360 * 65535 / 360, + "grænn": 120 * 65535 / 360, + "blár": 240 * 65535 / 360, + "ljósblár": 180 * 65535 / 360, + "bleikur": 300 * 65535 / 360, + "hvítur": [], + "fjólublár": [], + "brúnn": [], + "appelsínugulur": [], +} -# def QIoTTurnOffLightsRest(node: Node, params: QueryStateDict, result: Result) -> None: -# result.action = "turn_off" -# if "hue_obj" not in result: -# result["hue_obj"] = {"on": False} -# else: -# result["hue_obj"]["on"] = False +# This module wants to handle parse trees for queries +HANDLE_TREE = True -# def QIoTNewColor(node: Node, params: QueryStateDict, result: Result) -> None: -# result.action = "set_color" -# print(result.color_name) -# color_hue = _COLORS.get(result.color_name, None) -# print(color_hue) -# if color_hue is not None: -# if "hue_obj" not in result: -# result["hue_obj"] = {"on": True, "hue": int(color_hue)} -# else: -# result["hue_obj"]["hue"] = int(color_hue) -# result["hue_obj"]["on"] = True +# The grammar nonterminals this module wants to handle +QUERY_NONTERMINALS = {"QIoT"} +# The context-free grammar for the queries recognized by this plug-in module +# GRAMMAR = read_grammar_file("iot_hue") -# def QIoTMoreBrighterOrHigher( -# node: Node, params: QueryStateDict, result: Result -# ) -> None: -# result.action = "increase_brightness" -# if "hue_obj" not in result: -# result["hue_obj"] = {"on": True, "bri_inc": 64} -# else: -# result["hue_obj"]["bri_inc"] = 64 -# result["hue_obj"]["on"] = True +GRAMMAR = read_grammar_file( + "iot_hue", color_names=" | ".join(f"'{color}:lo'" for color in _COLORS.keys()) +) -# def QIoTLessDarkerOrLower(node: Node, params: QueryStateDict, result: Result) -> None: -# result.action = "decrease_brightness" -# if "hue_obj" not in result: -# result["hue_obj"] = {"bri_inc": -64} -# else: -# result["hue_obj"]["bri_inc"] = -64 +def QIoTQuery(node: Node, params: QueryStateDict, result: Result) -> None: + result.qtype = _IoT_QTYPE -# def QIoTIncreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: -# result.action = "increase_brightness" -# if "hue_obj" not in result: -# result["hue_obj"] = {"on": True, "bri_inc": 64} -# else: -# result["hue_obj"]["bri_inc"] = 64 -# result["hue_obj"]["on"] = True +def QIoTColorWord(node: Node, params: QueryStateDict, result: Result) -> None: + result.changing_color = True + + +def QIoTSceneWord(node: Node, params: QueryStateDict, result: Result) -> None: + result.changing_scene = True + +def QIoTBrightnessWord(node: Node, params: QueryStateDict, result: Result) -> None: + result.changing_brightness = True -# def QIoTDecreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: -# result.action = "decrease_brightness" -# if "hue_obj" not in result: -# result["hue_obj"] = {"bri_inc": -64} -# else: -# result["hue_obj"]["bri_inc"] = -64 +def QIoTTurnOnLightsRest(node: Node, params: QueryStateDict, result: Result) -> None: + result.action = "turn_on" + if "hue_obj" not in result: + result["hue_obj"] = {"on": True} + else: + result["hue_obj"]["on"] = True -# def QIoTBrightest(node: Node, params: QueryStateDict, result: Result) -> None: -# result.action = "decrease_brightness" -# if "hue_obj" not in result: -# result["hue_obj"] = {"bri": 255} -# else: -# result["hue_obj"]["bri"] = 255 +def QIoTTurnOffLightsRest(node: Node, params: QueryStateDict, result: Result) -> None: + result.action = "turn_off" + if "hue_obj" not in result: + result["hue_obj"] = {"on": False} + else: + result["hue_obj"]["on"] = False -# def QIoTDarkest(node: Node, params: QueryStateDict, result: Result) -> None: -# result.action = "decrease_brightness" -# if "hue_obj" not in result: -# result["hue_obj"] = {"bri": 0} -# else: -# result["hue_obj"]["bri"] = 0 + +def QIoTNewColor(node: Node, params: QueryStateDict, result: Result) -> None: + result.action = "set_color" + print(result.color_name) + color_hue = _COLORS.get(result.color_name, None) + print(color_hue) + if color_hue is not None: + if "hue_obj" not in result: + result["hue_obj"] = {"on": True, "hue": int(color_hue)} + else: + result["hue_obj"]["hue"] = int(color_hue) + result["hue_obj"]["on"] = True -# def QIoTNewScene(node: Node, params: QueryStateDict, result: Result) -> None: -# result.action = "set_scene" -# scene_name = result.get("scene_name", None) -# print("scene: " + scene_name) -# if scene_name is not None: -# if "hue_obj" not in result: -# result["hue_obj"] = {"on": True, "scene": scene_name} -# else: -# result["hue_obj"]["scene"] = scene_name -# result["hue_obj"]["on"] = True - - -# def QIoTColorName(node: Node, params: QueryStateDict, result: Result) -> None: -# result["color_name"] = ( -# node.first_child(lambda x: True).string_self().strip("'").split(":")[0] -# ) +def QIoTMoreBrighterOrHigher( + node: Node, params: QueryStateDict, result: Result +) -> None: + result.action = "increase_brightness" + if "hue_obj" not in result: + result["hue_obj"] = {"on": True, "bri_inc": 64} + else: + result["hue_obj"]["bri_inc"] = 64 + result["hue_obj"]["on"] = True + + +def QIoTLessDarkerOrLower(node: Node, params: QueryStateDict, result: Result) -> None: + result.action = "decrease_brightness" + if "hue_obj" not in result: + result["hue_obj"] = {"bri_inc": -64} + else: + result["hue_obj"]["bri_inc"] = -64 + + +def QIoTIncreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: + result.action = "increase_brightness" + if "hue_obj" not in result: + result["hue_obj"] = {"on": True, "bri_inc": 64} + else: + result["hue_obj"]["bri_inc"] = 64 + result["hue_obj"]["on"] = True + + +def QIoTDecreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: + result.action = "decrease_brightness" + if "hue_obj" not in result: + result["hue_obj"] = {"bri_inc": -64} + else: + result["hue_obj"]["bri_inc"] = -64 + + +def QIoTBrightest(node: Node, params: QueryStateDict, result: Result) -> None: + result.action = "decrease_brightness" + if "hue_obj" not in result: + result["hue_obj"] = {"bri": 255} + else: + result["hue_obj"]["bri"] = 255 + + +def QIoTDarkest(node: Node, params: QueryStateDict, result: Result) -> None: + result.action = "decrease_brightness" + if "hue_obj" not in result: + result["hue_obj"] = {"bri": 0} + else: + result["hue_obj"]["bri"] = 0 + + +def QIoTNewScene(node: Node, params: QueryStateDict, result: Result) -> None: + result.action = "set_scene" + scene_name = result.get("scene_name", None) + print("scene: " + scene_name) + if scene_name is not None: + if "hue_obj" not in result: + result["hue_obj"] = {"on": True, "scene": scene_name} + else: + result["hue_obj"]["scene"] = scene_name + result["hue_obj"]["on"] = True + +def QIoTColorName(node: Node, params: QueryStateDict, result: Result) -> None: + result["color_name"] = ( + node.first_child(lambda x: True).string_self().strip("'").split(":")[0] + ) -# def QIoTSceneName(node: Node, params: QueryStateDict, result: Result) -> None: -# result["scene_name"] = result._indefinite -# print("scene: " + result.get("scene_name", None)) +def QIoTSceneName(node: Node, params: QueryStateDict, result: Result) -> None: + result["scene_name"] = result._indefinite + print("scene: " + result.get("scene_name", None)) -# def QIoTGroupName(node: Node, params: QueryStateDict, result: Result) -> None: -# result["group_name"] = result._indefinite +def QIoTGroupName(node: Node, params: QueryStateDict, result: Result) -> None: + result["group_name"] = result._indefinite -# def QIoTLightName(node: Node, params: QueryStateDict, result: Result) -> None: -# result["light_name"] = result._indefinite +def QIoTLightName(node: Node, params: QueryStateDict, result: Result) -> None: + result["light_name"] = result._indefinite -# def QIoTLightsBanwords(node: Node, params: QueryStateDict, result: Result) -> None: -# result.abort = True +def QIoTSpeakerHotwords(node: Node, params: QueryStateDict, result: Result) -> None: + print("lights banwords") + result.abort = True -# # Convert color name into hue -# # Taken from home.py -# _COLOR_NAME_TO_CIE: Mapping[str, float] = { -# "gulur": 60 * 65535 / 360, -# "grænn": 120 * 65535 / 360, -# "ljósblár": 180 * 65535 / 360, -# "blár": 240 * 65535 / 360, -# "bleikur": 300 * 65535 / 360, -# "rauður": 360 * 65535 / 360, -# # "Rauð": 360 * 65535 / 360, -# } + +# Convert color name into hue +# Taken from home.py +_COLOR_NAME_TO_CIE: Mapping[str, float] = { + "gulur": 60 * 65535 / 360, + "grænn": 120 * 65535 / 360, + "ljósblár": 180 * 65535 / 360, + "blár": 240 * 65535 / 360, + "bleikur": 300 * 65535 / 360, + "rauður": 360 * 65535 / 360, + # "Rauð": 360 * 65535 / 360, +} # _SPEAKER_WORDS: FrozenSet[str] = frozenset( # ( @@ -305,96 +306,98 @@ # ) -# def sentence(state: QueryStateDict, result: Result) -> None: -# """Called when sentence processing is complete""" -# q: Query = state["query"] -# if result.get("abort"): -# q.set_error("E_QUERY_NOT_UNDERSTOOD") -# # lemmas = set( -# # i[0].root(state, result.params) -# # for i in result.enum_descendants(lambda x: isinstance(x, TerminalNode)) -# # ) -# # if not _SPEAKER_WORDS.isdisjoint(lemmas): -# # print("matched with music word list") -# # q.set_error("E_QUERY_NOT_UNDERSTOOD") -# # return -# changing_color = result.get("changing_color", False) -# changing_scene = result.get("changing_scene", False) -# changing_brightness = result.get("changing_brightness", False) -# print("error?", sum((changing_color, changing_scene, changing_brightness)) > 1) -# if ( -# sum((changing_color, changing_scene, changing_brightness)) > 1 -# or "qtype" not in result -# ): -# q.set_error("E_QUERY_NOT_UNDERSTOOD") -# return - -# q.set_qtype(result.qtype) - -# smartdevice_type = "iot_lights" -# client_id = str(q.client_id) -# print("client_id:", client_id) - -# # Fetch relevant data from the device_data table to perform an action on the lights -# device_data = cast(Optional[DeviceData], q.client_data(smartdevice_type)) -# print("location :", q.location) -# print("device data :", device_data) - -# selected_light: Optional[str] = None -# print("selected light:", selected_light) -# hue_credentials: Optional[Dict[str, str]] = None - -# if device_data is not None: -# dev = device_data -# assert dev is not None -# light = dev.get("philips_hue") -# hue_credentials = light.get("credentials") -# bridge_ip = hue_credentials.get("ip_address") -# username = hue_credentials.get("username") - -# if not device_data or not hue_credentials: -# answer = "Það vantar að tengja Philips Hub-inn." -# q.set_answer(*gen_answer(answer)) -# return - -# # Successfully matched a query type -# print("bridge_ip: ", bridge_ip) -# print("username: ", username) -# print("selected light :", selected_light) -# print("hue credentials :", hue_credentials) - -# try: -# # kalla í javascripts stuff -# light_or_group_name = result.get("light_name", result.get("group_name", "")) -# color_name = result.get("color_name", "") -# print("GROUP NAME:", light_or_group_name) -# print("COLOR NAME:", color_name) -# print(result.hue_obj) -# q.set_answer( -# *gen_answer( -# "ég var að kveikja ljósin! " -# # + group_name -# # + " " -# # + color_name -# # + " " -# # + result.action -# # + " " -# # + str(result.hue_obj.get("hue", "enginn litur")) -# ) -# ) -# js = ( -# read_jsfile("IoT_Embla/fuse.js") -# + f"var BRIDGE_IP = '{bridge_ip}';var USERNAME = '{username}';" -# + read_jsfile("IoT_Embla/Philips_Hue/fuse_search.js") -# + read_jsfile("IoT_Embla/Philips_Hue/lights.js") -# + read_jsfile("IoT_Embla/Philips_Hue/set_lights.js") -# ) -# js += f"setLights('{light_or_group_name}', '{json.dumps(result.hue_obj)}');" -# q.set_command(js) -# except Exception as e: -# logging.warning("Exception while processing random query: {0}".format(e)) -# q.set_error("E_EXCEPTION: {0}".format(e)) -# raise - - -# # f"var BRIDGE_IP = '192.168.1.68';var USERNAME = 'p3obluiXT13IbHMpp4X63ZvZnpNRdbqqMt723gy2';" +def sentence(state: QueryStateDict, result: Result) -> None: + """Called when sentence processing is complete""" + q: Query = state["query"] + print("start of sentence") + if result.get("abort"): + print("aborted") + q.set_error("E_QUERY_NOT_UNDERSTOOD") + # lemmas = set( + # i[0].root(state, result.params) + # for i in result.enum_descendants(lambda x: isinstance(x, TerminalNode)) + # ) + # if not _SPEAKER_WORDS.isdisjoint(lemmas): + # print("matched with music word list") + # q.set_error("E_QUERY_NOT_UNDERSTOOD") + # return + changing_color = result.get("changing_color", False) + changing_scene = result.get("changing_scene", False) + changing_brightness = result.get("changing_brightness", False) + print("error?", sum((changing_color, changing_scene, changing_brightness)) > 1) + if ( + sum((changing_color, changing_scene, changing_brightness)) > 1 + or "qtype" not in result + ): + q.set_error("E_QUERY_NOT_UNDERSTOOD") + return + + q.set_qtype(result.qtype) + + smartdevice_type = "iot_lights" + client_id = str(q.client_id) + print("client_id:", client_id) + + # Fetch relevant data from the device_data table to perform an action on the lights + device_data = cast(Optional[DeviceData], q.client_data(smartdevice_type)) + print("location :", q.location) + print("device data :", device_data) + + selected_light: Optional[str] = None + print("selected light:", selected_light) + hue_credentials: Optional[Dict[str, str]] = None + + if device_data is not None: + dev = device_data + assert dev is not None + light = dev.get("philips_hue") + hue_credentials = light.get("credentials") + bridge_ip = hue_credentials.get("ip_address") + username = hue_credentials.get("username") + + if not device_data or not hue_credentials: + answer = "Það vantar að tengja Philips Hub-inn." + q.set_answer(*gen_answer(answer)) + return + + # Successfully matched a query type + print("bridge_ip: ", bridge_ip) + print("username: ", username) + print("selected light :", selected_light) + print("hue credentials :", hue_credentials) + + try: + # kalla í javascripts stuff + light_or_group_name = result.get("light_name", result.get("group_name", "")) + color_name = result.get("color_name", "") + print("GROUP NAME:", light_or_group_name) + print("COLOR NAME:", color_name) + print(result.hue_obj) + q.set_answer( + *gen_answer( + "ég var að kveikja ljósin! " + # + group_name + # + " " + # + color_name + # + " " + # + result.action + # + " " + # + str(result.hue_obj.get("hue", "enginn litur")) + ) + ) + js = ( + read_jsfile("IoT_Embla/fuse.js") + + f"var BRIDGE_IP = '{bridge_ip}';var USERNAME = '{username}';" + + read_jsfile("IoT_Embla/Philips_Hue/fuse_search.js") + + read_jsfile("IoT_Embla/Philips_Hue/lights.js") + + read_jsfile("IoT_Embla/Philips_Hue/set_lights.js") + ) + js += f"setLights('{light_or_group_name}', '{json.dumps(result.hue_obj)}');" + q.set_command(js) + except Exception as e: + logging.warning("Exception while processing random query: {0}".format(e)) + q.set_error("E_EXCEPTION: {0}".format(e)) + raise + + +# f"var BRIDGE_IP = '192.168.1.68';var USERNAME = 'p3obluiXT13IbHMpp4X63ZvZnpNRdbqqMt723gy2';" diff --git a/queries/iot_speakers.py b/queries/iot_speakers.py index 3735345e..55c7f6b3 100644 --- a/queries/iot_speakers.py +++ b/queries/iot_speakers.py @@ -129,6 +129,26 @@ def QIoTSpeakerPauseVerb(node: Node, params: QueryStateDict, result: Result) -> result.qkey = "turn_off" +def QIoTSpeakerSkipVerb(node: Node, params: QueryStateDict, result: Result) -> None: + result.qkey = "fast_forward" + + +def QIoTSpeakerNewPlay(node: Node, params: QueryStateDict, result: Result) -> None: + result.qkey = "turn_on" + + +def QIoTSpeakerNewPause(node: Node, params: QueryStateDict, result: Result) -> None: + result.qkey = "turn_off" + + +def QIoTSpeakerNewNext(node: Node, params: QueryStateDict, result: Result) -> None: + result.qkey = "fast_forward" + + +def QIoTSpeakerNewPrevious(node: Node, params: QueryStateDict, result: Result) -> None: + result.qkey = "rewind" + + def QIoTSpeakerIncreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: result.qkey = "increase_volume" @@ -281,7 +301,7 @@ def sentence(state: QueryStateDict, result: Result) -> None: q: Query = state["query"] if "qkey" not in result: result.qkey = "turn_on" - if result.qkey == "turn_on" and result.target == "radio": + if result.qkey == "turn_on" and result.get("target") == "radio": result.qkey = "radio" if "qtype" in result: print("IF QTYPE AND QKEY") From deb0ec85646769beaa65701831205185774aaa38 Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Fri, 8 Jul 2022 11:55:36 +0000 Subject: [PATCH 213/371] Next prev song in iot_speakers --- queries/iot_speakers.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/queries/iot_speakers.py b/queries/iot_speakers.py index 55c7f6b3..7f965388 100644 --- a/queries/iot_speakers.py +++ b/queries/iot_speakers.py @@ -130,7 +130,7 @@ def QIoTSpeakerPauseVerb(node: Node, params: QueryStateDict, result: Result) -> def QIoTSpeakerSkipVerb(node: Node, params: QueryStateDict, result: Result) -> None: - result.qkey = "fast_forward" + result.qkey = "next_song" def QIoTSpeakerNewPlay(node: Node, params: QueryStateDict, result: Result) -> None: @@ -142,11 +142,11 @@ def QIoTSpeakerNewPause(node: Node, params: QueryStateDict, result: Result) -> N def QIoTSpeakerNewNext(node: Node, params: QueryStateDict, result: Result) -> None: - result.qkey = "fast_forward" + result.qkey = "next_song" def QIoTSpeakerNewPrevious(node: Node, params: QueryStateDict, result: Result) -> None: - result.qkey = "rewind" + result.qkey = "prev_song" def QIoTSpeakerIncreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: @@ -342,6 +342,16 @@ def sentence(state: QueryStateDict, result: Result) -> None: [radio_url], "Ég setti á útvarpstöðina", ], + "next_song": [ + sonos_client.next_song, + [], + "Ég hötta á næsta tón", + ], + "prev_song": [ + sonos_client.prev_song, + [], + "Ég hötta á fyrri tón", + ], } handler, args, answer = handler_map.get(result.qkey) response = handler(*args) From eaa2e45c4b4075771fe69d0aa786965fc0e4ce16 Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Fri, 8 Jul 2022 13:04:53 +0000 Subject: [PATCH 214/371] minor fixes --- queries/grammars/iot_speakers.grammar | 12 +++++++----- queries/iot_hue.py | 1 + query.py | 1 + 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/queries/grammars/iot_speakers.grammar b/queries/grammars/iot_speakers.grammar index bf6cb58b..4cc705c7 100644 --- a/queries/grammars/iot_speakers.grammar +++ b/queries/grammars/iot_speakers.grammar @@ -1,7 +1,7 @@ # TODO: Fix music hardcoding # TODO: Check whether to force determinate form # TODO: "Settu á útvarpið", which station to play? -# TODO: Refactor make and let "Gerðu tónlistina hærri í hátalaranum (í eldhúsinu)"-type sentences +# TODO: "Kveiktu á tónlist í hátalarnum" /þgf = þgf /ef = ef @@ -112,13 +112,13 @@ QIoTSpeakerComparative/fall → # Sometimes "á" is not registered by Embla QIoTSpeakerAHvad → - "á"? QIoTSpeakerMusicOrRadioWord/þf - | "á"? QIoTSpeakerNewSetting/þf + "yfir"? "á"? QIoTSpeakerMusicOrRadioWord/þf + | "yfir"? "á"? QIoTSpeakerNewSetting/þf QIoTSpeakerAHverju → "á" QIoTSpeakerMusicOrApplianceWord/þgf | "á" QIoTSpeakerNewRadio/þgf - | "á" QIoTSpeakerMusicOrRadioWord/þgf "í" QIoTSpeakerSpeakerWord/þgf + | "á" QIoTSpeakerMusicOrRadioWord/þgf | "á" QIoTSpeakerNewNextOrPrevious/fall QIoTSpeakerNewSetting/fall → @@ -152,6 +152,7 @@ QIoTSpeakerNewNext/fall -> QIoTSpeakerNewPrevious/fall -> 'seinastur:lo'_kvk/fall 'lag:hk'_et/fall | 'síðastur:lo'_kvk/fall 'lag:hk'_et/fall + | 'fyrri:lo'_kvk/fall 'lag:hk'_et/fall QIoTSpeakerMusicOrSoundPhrase/fall → QIoTSpeakerMusicPhrase/fall @@ -465,4 +466,5 @@ QIoTSpeakerHotwords/fall -> QIoTSpeakerMusicOrApplianceWord/fall | QIoTSpeakerSoundWord/fall | QIoTSpeakerRadioStationName - | 'lag:no' \ No newline at end of file + | 'lag:no' + | QIoTSpeakerNewSetting/fall \ No newline at end of file diff --git a/queries/iot_hue.py b/queries/iot_hue.py index f9553680..e94bcbd3 100755 --- a/queries/iot_hue.py +++ b/queries/iot_hue.py @@ -313,6 +313,7 @@ def sentence(state: QueryStateDict, result: Result) -> None: if result.get("abort"): print("aborted") q.set_error("E_QUERY_NOT_UNDERSTOOD") + return # lemmas = set( # i[0].root(state, result.params) # for i in result.enum_descendants(lambda x: isinstance(x, TerminalNode)) diff --git a/query.py b/query.py index f598aaf7..e0391ee7 100755 --- a/query.py +++ b/query.py @@ -301,6 +301,7 @@ def process_queries( return False with self.context(session, processor, query=query) as state: for query_tree in self._query_trees: + print("Processing query tree", query_tree.string_self()) # Is the processor interested in the root nonterminal # of this query tree? if query_tree.string_self() in processor_query_types: From afec05ccf3be510f6c811b9df256070491e8d842 Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Fri, 8 Jul 2022 13:05:19 +0000 Subject: [PATCH 215/371] Next and prev song functionality in sonos client --- queries/iot_hue.py | 1 + queries/sonos.py | 69 ++++++++++++++++++++++++++++++---------------- 2 files changed, 47 insertions(+), 23 deletions(-) diff --git a/queries/iot_hue.py b/queries/iot_hue.py index f9553680..e94bcbd3 100755 --- a/queries/iot_hue.py +++ b/queries/iot_hue.py @@ -313,6 +313,7 @@ def sentence(state: QueryStateDict, result: Result) -> None: if result.get("abort"): print("aborted") q.set_error("E_QUERY_NOT_UNDERSTOOD") + return # lemmas = set( # i[0].root(state, result.params) # for i in result.enum_descendants(lambda x: isinstance(x, TerminalNode)) diff --git a/queries/sonos.py b/queries/sonos.py index a9b13943..0b8e3f49 100644 --- a/queries/sonos.py +++ b/queries/sonos.py @@ -78,7 +78,7 @@ from util import read_api_key from queries import query_json_api, post_to_json_api from query import Query -from typing import Dict, Optional +from typing import Dict import json @@ -114,6 +114,7 @@ def __init__( self._household_id = self._households[0]["id"] self._groups = self._get_groups() self._players = self._get_players() + self._group_id = self._get_group_id() self._store_sonos_data_and_credentials() """ @@ -359,8 +360,8 @@ def _create_playerdict_for_db(self, players: list): def _create_or_join_session(self): print("_create_or_join_session") - group_id = self._get_group_id() - url = f"https://api.ws.sonos.com/control/api/v1/groups/{group_id}/playbackSession/joinOrCreate" + # group_id = self._get_group_id() + url = f"https://api.ws.sonos.com/control/api/v1/groups/{self._group_id}/playbackSession/joinOrCreate" payload = json.dumps({"appId": "com.mideind.embla", "appContext": "embla123"}) headers = { @@ -410,8 +411,8 @@ def play_radio_stream(self, radio_url): #: Optional[str] = self._device_data.ge def increase_volume(self): print("increase_volume") - group_id = self._get_group_id() - url = f"https://api.ws.sonos.com/control/api/v1/groups/{group_id}/groupVolume/relative" + # group_id = self._get_group_id() + url = f"https://api.ws.sonos.com/control/api/v1/groups/{self._group_id}/groupVolume/relative" payload = json.dumps({"volumeDelta": 10}) headers = { @@ -445,9 +446,9 @@ def toggle_play(self): Toggles play/pause of a group """ print("toggle playpause") - group_id = self._get_group_id() + # group_id = self._get_group_id() print("exited group_id") - url = f"https://api.ws.sonos.com/control/api/v1/groups/{group_id}/playback/play" + url = f"https://api.ws.sonos.com/control/api/v1/groups/{self._group_id}/playback/play" headers = { "Content-Type": "application/json", "Authorization": f"Bearer {self._access_token}", @@ -466,11 +467,9 @@ def toggle_pause(self): Toggles play/pause of a group """ print("toggle playpause") - group_id = self._get_group_id() + # group_id = self._get_group_id() print("exited group_id") - url = ( - f"https://api.ws.sonos.com/control/api/v1/groups/{group_id}/playback/pause" - ) + url = f"https://api.ws.sonos.com/control/api/v1/groups/{self._group_id}/playback/pause" headers = { "Content-Type": "application/json", "Authorization": f"Bearer {self._access_token}", @@ -530,20 +529,44 @@ def play_chime(self): "Authorization": f"Bearer {self._access_token}", } - response = requests.request("POST", url, headers=headers, data=payload) + response = post_to_json_api(url, payload, headers) + + return response - print(response.text) + def next_song(self): + url = f"https://api.ws.sonos.com/control/api/v1/groups/{self._group_id}/playback/skipToNextTrack" - def _refresh_data(self, function): - print("refresh data") - print("device_data: ", self._device_data) - # self._device_data["sonos"]["data"] = None - # print("device_data after deletion: ", self._device_data) - self._households = self._get_households() - self._groups = self._get_groups() - self._players = self._get_players() - # self._store_sonos_data_and_credentials() - getattr(self, function)() + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {self._access_token}", + } + + response = post_to_json_api(url, headers=headers) + + return response + + def prev_song(self): + url = f"https://api.ws.sonos.com/control/api/v1/groups/{self._group_id}/playback/skipToPreviousTrack" + + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {self._access_token}", + } + + response = post_to_json_api(url, headers=headers) + + return response + + # def _refresh_data(self, function): + # print("refresh data") + # print("device_data: ", self._device_data) + # # self._device_data["sonos"]["data"] = None + # # print("device_data after deletion: ", self._device_data) + # self._households = self._get_households() + # self._groups = self._get_groups() + # self._players = self._get_players() + # # self._store_sonos_data_and_credentials() + # getattr(self, function)() # def get_groups_and_players(self): # """ From 69aa7d76885f1fd32e4cfc6be567249b16397a46 Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Fri, 8 Jul 2022 14:01:02 +0000 Subject: [PATCH 216/371] moved banwords into sentence again --- queries/grammars/iot_hue.grammar | 18 ++--- queries/grammars/iot_speakers.grammar | 15 ++-- queries/iot_hue.py | 99 ++++++++++++++------------- query.py | 2 +- 4 files changed, 69 insertions(+), 65 deletions(-) diff --git a/queries/grammars/iot_hue.grammar b/queries/grammars/iot_hue.grammar index 2466e5e2..cd2ff734 100644 --- a/queries/grammars/iot_hue.grammar +++ b/queries/grammars/iot_hue.grammar @@ -186,20 +186,20 @@ QIoTLocationPrepositionSecondPart -> "á" | "í" QIoTGroupName/fall -> - QIoTLightsBanwords/fall - > no/fall + # QIoTLightsBanwords/fall + no/fall QIoTLightName/fall -> - QIoTLightsBanwords/fall - > no/fall + # QIoTLightsBanwords/fall + no/fall QIoTColorName -> {color_names} QIoTSceneName -> - QIoTLightsBanwords/fall - > no - > lo + # QIoTLightsBanwords/fall + no + | lo QIoTAnnadAndlag -> QIoTNewSetting/nf @@ -323,5 +323,5 @@ QIoTSettingWord/fall -> 'stilling'/fall # Catching hotwords from iot_speakers -QIoTLightsBanwords/fall -> - QIoTSpeakerHotwords/fall \ No newline at end of file +# QIoTLightsBanwords/fall -> +# QIoTSpeakerHotwords/fall \ No newline at end of file diff --git a/queries/grammars/iot_speakers.grammar b/queries/grammars/iot_speakers.grammar index 4cc705c7..172ac4b3 100644 --- a/queries/grammars/iot_speakers.grammar +++ b/queries/grammars/iot_speakers.grammar @@ -148,11 +148,14 @@ QIoTSpeakerNewNextOrPrevious/fall -> QIoTSpeakerNewNext/fall -> 'næstur:lo'_kvk/fall 'lag:hk'_et/fall + | 'lag:hk'_et_gr/fall "á" "eftir" + # | "næsta" "lagið" QIoTSpeakerNewPrevious/fall -> 'seinastur:lo'_kvk/fall 'lag:hk'_et/fall | 'síðastur:lo'_kvk/fall 'lag:hk'_et/fall | 'fyrri:lo'_kvk/fall 'lag:hk'_et/fall + | 'lag:hk'_et_gr/fall "á" "undan" QIoTSpeakerMusicOrSoundPhrase/fall → QIoTSpeakerMusicPhrase/fall @@ -462,9 +465,9 @@ QIoTSpeaker90s → # TODO: Fix the song hotword # Hotwords to block other similarly phrased query modules from catching queries belonging to iot_speakers -QIoTSpeakerHotwords/fall -> - QIoTSpeakerMusicOrApplianceWord/fall - | QIoTSpeakerSoundWord/fall - | QIoTSpeakerRadioStationName - | 'lag:no' - | QIoTSpeakerNewSetting/fall \ No newline at end of file +# QIoTSpeakerHotwords/fall -> +# QIoTSpeakerMusicOrApplianceWord/fall +# | QIoTSpeakerSoundWord/fall +# | QIoTSpeakerRadioStationName +# | 'lag:no' +# | QIoTSpeakerNewSetting/fall \ No newline at end of file diff --git a/queries/iot_hue.py b/queries/iot_hue.py index e94bcbd3..a9c32965 100755 --- a/queries/iot_hue.py +++ b/queries/iot_hue.py @@ -263,47 +263,48 @@ def QIoTSpeakerHotwords(node: Node, params: QueryStateDict, result: Result) -> N # "Rauð": 360 * 65535 / 360, } -# _SPEAKER_WORDS: FrozenSet[str] = frozenset( -# ( -# "tónlist", -# "hátalari", -# "bylgja", -# "útvarp", -# "útvarpsstöð", -# "útvarp saga", -# "gullbylgja", -# "x-ið", -# "léttbylgjan", -# "rás 1", -# "rás 2", -# "rondo", -# "rondó", -# "fm 957", -# "fm957", -# "fm-957", -# "k-100", -# "k 100", -# "kk 100", -# "k hundrað", -# "kk hundrað", -# "x977", -# "x 977", -# "x-977", -# "x-ið 977", -# "x-ið", -# "retro", -# "kiss fm", -# "flassbakk", -# "flassbakk fm", -# "útvarp hundraðið", -# "útvarp 101", -# "útvarp hundraðogeinn", -# "útvarp hundrað og einn", -# "útvarp hundrað einn", -# "útvarp hundrað 1", -# "útvarp", -# ) -# ) +_SPEAKER_WORDS: FrozenSet[str] = frozenset( + ( + "tónlist", + "lag", + "hátalari", + "bylgja", + "útvarp", + "útvarpsstöð", + "útvarp saga", + "gullbylgja", + "x-ið", + "léttbylgjan", + "rás 1", + "rás 2", + "rondo", + "rondó", + "fm 957", + "fm957", + "fm-957", + "k-100", + "k 100", + "kk 100", + "k hundrað", + "kk hundrað", + "x977", + "x 977", + "x-977", + "x-ið 977", + "x-ið", + "retro", + "kiss fm", + "flassbakk", + "flassbakk fm", + "útvarp hundraðið", + "útvarp 101", + "útvarp hundraðogeinn", + "útvarp hundrað og einn", + "útvarp hundrað einn", + "útvarp hundrað 1", + "útvarp", + ) +) def sentence(state: QueryStateDict, result: Result) -> None: @@ -314,14 +315,14 @@ def sentence(state: QueryStateDict, result: Result) -> None: print("aborted") q.set_error("E_QUERY_NOT_UNDERSTOOD") return - # lemmas = set( - # i[0].root(state, result.params) - # for i in result.enum_descendants(lambda x: isinstance(x, TerminalNode)) - # ) - # if not _SPEAKER_WORDS.isdisjoint(lemmas): - # print("matched with music word list") - # q.set_error("E_QUERY_NOT_UNDERSTOOD") - # return + lemmas = set( + i[0].root(state, result.params) + for i in result.enum_descendants(lambda x: isinstance(x, TerminalNode)) + ) + if not _SPEAKER_WORDS.isdisjoint(lemmas): + print("matched with music word list") + q.set_error("E_QUERY_NOT_UNDERSTOOD") + return changing_color = result.get("changing_color", False) changing_scene = result.get("changing_scene", False) changing_brightness = result.get("changing_brightness", False) diff --git a/query.py b/query.py index e0391ee7..a6ebc91d 100755 --- a/query.py +++ b/query.py @@ -301,7 +301,7 @@ def process_queries( return False with self.context(session, processor, query=query) as state: for query_tree in self._query_trees: - print("Processing query tree", query_tree.string_self()) + print("Processing query tree", query_tree.string_self(), "in module", processor.__name__) # Is the processor interested in the root nonterminal # of this query tree? if query_tree.string_self() in processor_query_types: From 8b2088eb8e35845b87d4ddc891e3c914ab4bb34d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Fri, 8 Jul 2022 16:05:43 +0000 Subject: [PATCH 217/371] Added timed_out functionality for dialogues --- queries/dialogue.py | 28 +++++++++++++++++++--------- queries/dialogues/fruitseller.toml | 1 + queries/dialogues/theater.toml | 1 + query.py | 7 +++++++ 4 files changed, 28 insertions(+), 9 deletions(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index 12b0fb32..22fa7222 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -9,7 +9,7 @@ TypeVar, cast, ) -from typing_extensions import TypedDict +from typing_extensions import TypedDict, NotRequired import os.path import json @@ -38,7 +38,7 @@ # TODO: Add try-except blocks where appropriate _TOML_FOLDER_NAME = "dialogues" -_EXPIRATION_TIME = 30 * 60 # a dialogue expires after 30 minutes +_DEFAULT_EXPIRATION_TIME = 30 * 60 # a dialogue expires after 30 minutes _FINAL_RESOURCE_NAME = "Final" # Generic resource type @@ -76,6 +76,7 @@ class DialogueTOMLStructure(TypedDict): """Structure of a dialogue TOML file.""" resources: List[Dict[str, Any]] + expiration_time: NotRequired[int] # Keys for accessing saved client data for dialogues @@ -117,19 +118,16 @@ def __init__(self, dialogue_name: str, saved_state: Optional[str] = None): self._saved_state: Optional[DialogueDBStructure] = None # Whether this dialogue is finished (successful/cancelled) or not self._finished: bool = False + self._expiration_time: int = _DEFAULT_EXPIRATION_TIME + self._timed_out: bool = False if isinstance(saved_state, str): self._saved_state = cast( DialogueDBStructure, json.loads(saved_state, cls=DialogueJSONDecoder) ) - time_from_last_interaction = ( - datetime.datetime.now() - self._saved_state[_MODIFIED_KEY] - ) + # Check that we have saved data for this dialogue and that it is not expired - if ( - self._saved_state[_RESOURCES_KEY] - and time_from_last_interaction.total_seconds() < _EXPIRATION_TIME - ): + if self._saved_state[_RESOURCES_KEY]: self._in_this_dialogue = True self.setup_resources() # TODO: IF EXPIRED DO SOMETHING @@ -140,6 +138,13 @@ def setup_resources(self) -> None: """ # Fetch empty resources from TOML self._initialize_resources(self._dialogue_name) + if self._saved_state: + time_from_last_interaction = ( + datetime.datetime.now() - self._saved_state[_MODIFIED_KEY] + ) + if time_from_last_interaction.total_seconds() >= _DEFAULT_EXPIRATION_TIME: + self._timed_out = True + return # Update empty resources with data from database for rname, resource in self._resources.items(): if self._saved_state and rname in self._saved_state["resources"]: @@ -192,6 +197,7 @@ def _initialize_resources(self, filename: str) -> None: self._resources[resource["name"]] = RESOURCE_MAP[resource["type"]]( **resource, order_index=i ) + self._expiration_time = obj.get("expiration_time", _DEFAULT_EXPIRATION_TIME) def hotword_activated(self) -> None: self._in_this_dialogue = True @@ -225,6 +231,10 @@ def get_resource(self, name: str) -> Resource: def extras(self) -> Dict[str, Any]: return self._extras + @property + def timed_out(self) -> bool: + return self._timed_out + def get_answer( self, answering_functions: AnsweringFunctionMap, result: Any ) -> Optional[AnswerTuple]: diff --git a/queries/dialogues/fruitseller.toml b/queries/dialogues/fruitseller.toml index 0f7aeecf..69168379 100644 --- a/queries/dialogues/fruitseller.toml +++ b/queries/dialogues/fruitseller.toml @@ -44,3 +44,4 @@ type = "FinalResource" requires = ["DateTime"] prompts.final = "Pöntunin þín er {fruits} og verður afhent {date_time}." prompts.cancelled = "Móttekið, hætti við pöntunina." +prompts.timed_out = "Ávaxtapöntunin þín rann út á tíma. Vinsamlegast byrjaðu aftur." diff --git a/queries/dialogues/theater.toml b/queries/dialogues/theater.toml index 43346de6..f246c68c 100644 --- a/queries/dialogues/theater.toml +++ b/queries/dialogues/theater.toml @@ -71,5 +71,6 @@ type = "FinalResource" requires = ["ShowSeatNumber"] prompts.final = "Þú bókaðir sæti {seats} í röð {row} fyrir sýninguna {show} {date_time}." prompts.cancelled = "Móttekið, hætti við leikhús pöntunina." +prompts.timed_out = "Leikhúsmiðapöntunin þín rann út á tíma. Vinsamlegast byrjaðu aftur." # TODO: Add a resource for the payment method diff --git a/query.py b/query.py index 1f7ab1f6..4c5d37cb 100755 --- a/query.py +++ b/query.py @@ -336,6 +336,13 @@ def process_queries( # Query matches this dialogue processor, start DialogueStateManager query.start_dsm(dialogue_name, dialogue_data) print("FINISHED SETTING UP DSM") + # TODO: if dialogue is timed_out, set query answer and return True + if query.dsm.timed_out: + print("TIMED OUT") + timed_out_ans = query.dsm.get_resource("Final").prompts["timed_out"] + ans = (dict(answer=timed_out_ans), timed_out_ans, timed_out_ans) + query.set_answer(*ans) + return True with self.context(session, processor, query=query) as state: for query_tree in self._query_trees: # Is the processor interested in the root nonterminal From fcf5a54b7217d5f3ed514c6987573a1cb2dcc83d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Mon, 11 Jul 2022 09:35:09 +0000 Subject: [PATCH 218/371] client data for dialogue resets after dialogue timing out --- queries/dialogue.py | 3 ++- query.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index 22fa7222..b71a80aa 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -142,6 +142,7 @@ def setup_resources(self) -> None: time_from_last_interaction = ( datetime.datetime.now() - self._saved_state[_MODIFIED_KEY] ) + # The dialogue timed out, nothing should be done if time_from_last_interaction.total_seconds() >= _DEFAULT_EXPIRATION_TIME: self._timed_out = True return @@ -340,7 +341,7 @@ def serialize_data(self) -> Dict[str, Optional[str]]: # When final resource is confirmed, the dialogue is over self.finish_dialogue() ds_json: Optional[str] = None - if not self._finished: + if not self._finished and not self._timed_out: ds_json = json.dumps( { _RESOURCES_KEY: self._resources, diff --git a/query.py b/query.py index 4c5d37cb..d9924e78 100755 --- a/query.py +++ b/query.py @@ -342,6 +342,7 @@ def process_queries( timed_out_ans = query.dsm.get_resource("Final").prompts["timed_out"] ans = (dict(answer=timed_out_ans), timed_out_ans, timed_out_ans) query.set_answer(*ans) + query.update_dialogue_data() return True with self.context(session, processor, query=query) as state: for query_tree in self._query_trees: From 649b403365966c81450392d951d0b8cd0c071439 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Mon, 11 Jul 2022 09:52:49 +0000 Subject: [PATCH 219/371] Expiration time can now be added to toml files, default is 30 minutes --- queries/dialogue.py | 3 ++- queries/dialogues/fruitseller.toml | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index b71a80aa..18f180c1 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -84,6 +84,7 @@ class DialogueTOMLStructure(TypedDict): _RESOURCES_KEY = "resources" _MODIFIED_KEY = "modified" _EXTRAS_KEY = "extras" +_EXPIRATION_TIME_KEY = "expiration_time" class DialogueDBStructure(TypedDict): @@ -143,7 +144,7 @@ def setup_resources(self) -> None: datetime.datetime.now() - self._saved_state[_MODIFIED_KEY] ) # The dialogue timed out, nothing should be done - if time_from_last_interaction.total_seconds() >= _DEFAULT_EXPIRATION_TIME: + if time_from_last_interaction.total_seconds() >= self._expiration_time: self._timed_out = True return # Update empty resources with data from database diff --git a/queries/dialogues/fruitseller.toml b/queries/dialogues/fruitseller.toml index 69168379..0363a099 100644 --- a/queries/dialogues/fruitseller.toml +++ b/queries/dialogues/fruitseller.toml @@ -1,3 +1,5 @@ +expiration_time = 900 # 15 minutes + [[resources]] name = "Fruits" type = "ListResource" From 0e7dbd38ea8b041df08ba4165f000be48cd1580d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Mon, 11 Jul 2022 10:44:00 +0000 Subject: [PATCH 220/371] Removed max_items from theater.toml and ListResource --- queries/dialogues/theater.toml | 1 - queries/resources.py | 1 - 2 files changed, 2 deletions(-) diff --git a/queries/dialogues/theater.toml b/queries/dialogues/theater.toml index f246c68c..7a28e689 100644 --- a/queries/dialogues/theater.toml +++ b/queries/dialogues/theater.toml @@ -1,7 +1,6 @@ [[resources]] name = "Show" type = "ListResource" -max_items = 1 cascade_state = true prompts.initial = "Hvaða sýningu má bjóða þér að fara á?" prompts.options = "Sýningarnar sem eru í boði eru: {options}" diff --git a/queries/resources.py b/queries/resources.py index 900b41af..6c50ab47 100644 --- a/queries/resources.py +++ b/queries/resources.py @@ -124,7 +124,6 @@ class ListResource(Resource): """Resource representing a list of items.""" data: List[Any] = field(default_factory=list) - max_items: Optional[int] = None def format_data(self, format_func: Optional[Callable[[Any], str]] = None) -> str: if format_func: From f18487dc7758b06082f086a5f4ea7a017472bf14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Mon, 11 Jul 2022 10:44:23 +0000 Subject: [PATCH 221/371] Removed timed_out TODOs --- queries/dialogue.py | 1 - query.py | 1 - 2 files changed, 2 deletions(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index 18f180c1..dbacd0b2 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -131,7 +131,6 @@ def __init__(self, dialogue_name: str, saved_state: Optional[str] = None): if self._saved_state[_RESOURCES_KEY]: self._in_this_dialogue = True self.setup_resources() - # TODO: IF EXPIRED DO SOMETHING def setup_resources(self) -> None: """ diff --git a/query.py b/query.py index d9924e78..a17802b0 100755 --- a/query.py +++ b/query.py @@ -336,7 +336,6 @@ def process_queries( # Query matches this dialogue processor, start DialogueStateManager query.start_dsm(dialogue_name, dialogue_data) print("FINISHED SETTING UP DSM") - # TODO: if dialogue is timed_out, set query answer and return True if query.dsm.timed_out: print("TIMED OUT") timed_out_ans = query.dsm.get_resource("Final").prompts["timed_out"] From ff592b7ba1b8a87a7111f7c87e5ae12fd92cacc0 Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Mon, 11 Jul 2022 14:02:14 +0000 Subject: [PATCH 222/371] Spotify connect process saving to DB and refresh token --- queries/iot_connect.py | 22 +++++++ queries/spotify.py | 140 +++++++++++++++++++++++++++++++++++++++++ routes/api.py | 36 +++++++++-- 3 files changed, 192 insertions(+), 6 deletions(-) create mode 100644 queries/spotify.py diff --git a/queries/iot_connect.py b/queries/iot_connect.py index 11a50968..756225b5 100644 --- a/queries/iot_connect.py +++ b/queries/iot_connect.py @@ -91,6 +91,7 @@ def help_text(lemma: str) -> str: | QIoTConnectHub | QIoTConnectSpeaker | QIoTCreateSpeakerToken + | QIoTConnectSpotify QIoTConnectLights → "tengdu" "ljósin" @@ -104,6 +105,9 @@ def help_text(lemma: str) -> str: QIoTCreateSpeakerToken → "skapaðu" "tóka" +QIoTConnectSpotify → + "tengdu" "spotify" + """ @@ -130,6 +134,12 @@ def QIoTCreateSpeakerToken(node: Node, params: QueryStateDict, result: Result) - result.action = "create_speaker_token" +def QIoTConnectSpotify(node: Node, params: QueryStateDict, result: Result) -> None: + print("Connect Spotify") + result.qtype = "connect_spotify" + result.action = "connect_spotify" + + def sentence(state: QueryStateDict, result: Result) -> None: """Called when sentence processing is complete""" q: Query = state["query"] @@ -214,6 +224,18 @@ def sentence(state: QueryStateDict, result: Result) -> None: q.set_answer(response, answer, voice_answer) return + elif result.qtype == "connect_spotify": + print("connect spotify") + spotify_key = read_api_key("SpotifyKey") + answer = "Skráðu þig inn hjá Spotify" + voice_answer, response = answer, dict(answer=answer) + q.set_answer(response, answer, voice_answer) + # Redirect the user to a Sonos login screen, which will then forward the neccessary credentials to the connect_sonos.api found in api.py + q.set_url( + f"https://accounts.spotify.com/authorize?client_id={spotify_key}&response_type=code&redirect_uri=http://{host}/connect_spotify.api&state={client_id}&scope=user-read-playback-state+user-modify-playback-state+user-read-playback-position+user-read-recently-played+app-remote-control+user-top-read+user-read-currently-playing+playlist-read-private+streaming" + ) + return + # def create_sonos_data_dict(access_token, q): # data_dict = {} diff --git a/queries/spotify.py b/queries/spotify.py new file mode 100644 index 00000000..05212c38 --- /dev/null +++ b/queries/spotify.py @@ -0,0 +1,140 @@ +from inspect import getargs +import requests +from datetime import datetime, timedelta +import flask +import random + +from util import read_api_key +from queries import query_json_api, post_to_json_api +from query import Query +from typing import Dict + +import json + + +class SpotifyClient: + def __init__( + self, + device_data: Dict[str, str], + client_id: str, + ): + self._client_id = client_id + self._device_data = device_data + self._encoded_credentials = read_api_key("SpotifyEncodedCredentials") + self._code = self._device_data["credentials"]["code"] + print("code :", self._code) + self._timestamp = datetime.now() + print("device data :", self._device_data) + try: + self._access_token = self._device_data["credentials"]["access_token"] + except (KeyError, TypeError): + self._create_token() + self._check_token_expiration() + self._store_credentials() + + def _create_token(self): + """ + Create a new access token for the Spotify API. + """ + host = flask.request.host + url = f"https://accounts.spotify.com/api/token?grant_type=authorization_code&code={self._code}&redirect_uri=http://{host}/connect_spotify.api" + + payload = {} + headers = { + "Content-Type": "application/x-www-form-urlencoded", + "Authorization": f"Basic {self._encoded_credentials}", + # "Cookie": "__Host-device_id=AQBxVApczxoXIW_roLoJ5nY1ND2wR8StM3lgCAP1SzmApFbSWeNGRpxDLjOtLaGOHTM-CpdxKbWCvXcc77StrhE1N4L5q21o2l0; __Secure-TPASESSION=AQB0Nywu3HtM0ccHT76ksjXMzzeDpIEIbYzytEhvu05ELAEfMRTsc0qyaxUphsBxE8qCN2Vsruz6Mo897xYLznaxfa0ZGdh5Jpw=; sp_sso_csrf_token=013acda7191871a43462f6a67f78e88cb74e9b5bc031363537353339303539343131; sp_tr=false", + } + + response = post_to_json_api(url, payload, headers) + self._access_token = response.get("access_token") + self._refresh_token = response.get("refresh_token") + self._timestamp = datetime.now() + return response + + def _check_token_expiration(self): + """ + Checks if access token is expired, and calls a function to refresh it if necessary. + """ + try: + timestamp = self._device_data["spotify"]["credentials"]["timestamp"] + except (KeyError, TypeError): + print("No timestamp found for Sonos token.") + return + timestamp = datetime.strptime(timestamp, "%Y-%m-%d %H:%M:%S.%f") + if (datetime.now() - timestamp) > timedelta(hours=1): + self._update_sonos_token() + + def _update_sonos_token(self): + """ + Updates the access token + """ + print("update sonos token") + self._refresh_expired_token() + cred_dict = { + "credentials": { + "access_token": self._access_token, + "timestamp": str(datetime.now()), + } + } + self._store_data(cred_dict) + + def _refresh_expired_token(self): + """ + Helper function for updating the access token. + """ + print("_refresh_expired_token") + + url = f"https://accounts.spotify.com/api/token?grant_type=refresh_token&refresh_token={self._refresh_token}" + + payload = {} + headers = { + "Content-Type": "application/x-www-form-urlencoded", + "Authorization": f"Basic {self._encoded_credentials}", + # 'Cookie': '__Host-device_id=AQBxVApczxoXIW_roLoJ5nY1ND2wR8StM3lgCAP1SzmApFbSWeNGRpxDLjOtLaGOHTM-CpdxKbWCvXcc77StrhE1N4L5q21o2l0; __Secure-TPASESSION=AQB0Nywu3HtM0ccHT76ksjXMzzeDpIEIbYzytEhvu05ELAEfMRTsc0qyaxUphsBxE8qCN2Vsruz6Mo897xYLznaxfa0ZGdh5Jpw=; sp_sso_csrf_token=013acda7191871a43462f6a67f78e88cb74e9b5bc031363537353339303539343131; sp_tr=false' + } + + response = post_to_json_api(url, payload, headers) + self._access_token = response.get("access_token") + + def _store_credentials(self): + print("_store_smartthings_cred") + # data_dict = self._create_sonos_data_dict() + cred_dict = self._create_cred_dict() + smartthings_dict = {} + smartthings_dict["smartthings"] = {"credentials": cred_dict} + self._store_data(smartthings_dict) + + def _create_cred_dict(self): + print("_create_smartthings_cred_dict") + cred_dict = {} + cred_dict.update( + { + "access_token": self._access_token, + "timestamp": str(datetime.now()), + } + ) + return cred_dict + + def _store_data(self, data): + Query.store_query_data(self._client_id, "spotify", data, update_in_place=True) + + def _store_credentials(self): + print("_store_spotify credentials") + # data_dict = self._create_sonos_data_dict() + cred_dict = self._create_cred_dict() + spotify_dict = {} + spotify_dict["credentials"] = cred_dict + self._store_data(spotify_dict) + + def _create_cred_dict(self): + print("_create_spotify_cred_dict") + cred_dict = {} + cred_dict.update( + { + "access_token": self._access_token, + "refresh_token": self._refresh_token, + "timestamp": str(datetime.now()), + } + ) + return cred_dict diff --git a/routes/api.py b/routes/api.py index 381a8da0..71f9ecd0 100755 --- a/routes/api.py +++ b/routes/api.py @@ -53,6 +53,7 @@ from util import read_api_key, icelandic_asciify from queries.sonos import SonosClient from queries.smartthings import SmartThingsClient +from queries.spotify import SpotifyClient from . import routes, better_jsonify, text_from_request, bool_from_request from . import MAX_URL_LENGTH, MAX_UUID_LENGTH @@ -731,15 +732,15 @@ def sonos_code(version: int = 1) -> Response: args = request.args client_id = args.get("state") code = args.get("code") - code = { + code_dict = { "sonos": {"credentials": {"code": code}} } # create a dictonary with the code if client_id and code: success = QueryObject.store_query_data( - client_id, "iot_speakers", code, update_in_place=True + client_id, "iot_speakers", code_dict, update_in_place=True ) if success: - device_data = code + device_data = code_dict # Create an instance of the SonosClient class. This will automatically create the rest of the credentials needed. sonos_client = SonosClient(device_data, client_id) sonos_voice_clip = ( @@ -764,15 +765,15 @@ def smartthings_code(version: int = 1) -> Response: args = request.args client_id = args.get("state") code = args.get("code") - code = { + code_dict = { "smartthings": {"credentials": {"code": code}} } # create a dictonary with the code if client_id and code: success = QueryObject.store_query_data( - client_id, "iot_hubs", code, update_in_place=True + client_id, "iot_hubs", code_dict, update_in_place=True ) if success: - device_data = code + device_data = code_dict smartthings_client = SmartThingsClient(device_data, client_id) # device_data = code # Create an instance of the SonosClient class. This will automatically create the rest of the credentials needed. @@ -780,6 +781,29 @@ def smartthings_code(version: int = 1) -> Response: return better_jsonify(valid=False, errmsg="Error registering smartthings code.") +@routes.route("/connect_spotify.api", methods=["GET"]) +@routes.route("/connect_spotofy.api/v", methods=["GET", "POST"]) +def spotify_code(version: int = 1) -> Response: + """ + API endpoint to connect Spotify account + """ + print("Spotifs code") + args = request.args + client_id = args.get("state") + code = args.get("code") + code_dict = {"credentials": {"code": code}} # create a dictonary with the code + if client_id and code: + success = QueryObject.store_query_data( + client_id, "spotify", code_dict, update_in_place=True + ) + if success: + device_data = code_dict + spotify_client = SpotifyClient(device_data, client_id) + # Create an instance of the SonosClient class. This will automatically create the rest of the credentials needed. + return better_jsonify(valid=True, msg="Registered spotify code") + return better_jsonify(valid=False, errmsg="Error registering spotify code.") + + # def sonos_code2(version: int = 1) -> Response: # print("sonos code") # args = request.args From a677b21c43dbf124a2a78739f0ec2854d4e4e79e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Wed, 13 Jul 2022 16:04:10 +0000 Subject: [PATCH 223/371] Initial banned nonterminal functionality, should be made better --- queries/fruitseller.py | 36 ++++++++++++++++- queries/theater.py | 88 ++++++++++++++++++++++++++++++++++++++---- query.py | 17 ++++++-- 3 files changed, 129 insertions(+), 12 deletions(-) diff --git a/queries/fruitseller.py b/queries/fruitseller.py index 837ede2b..f89f76bc 100644 --- a/queries/fruitseller.py +++ b/queries/fruitseller.py @@ -1,4 +1,4 @@ -from typing import Any, List, Optional, cast +from typing import Any, List, Optional, Set, cast import json import logging import datetime @@ -35,7 +35,10 @@ GRAMMAR = """ Query → - QFruitStartQuery | QFruitSeller + QFruitStartQuery | QFruitSellerQuery + +QFruitSellerQuery → + QFruitSeller QFruitSeller → QFruitQuery '?'? @@ -151,6 +154,35 @@ _DIALOGUE_NAME = "fruitseller" +def banned_nonterminals(q: Query) -> Set[str]: + banned_nonterminals: set[str] = set() + dialogue_data = cast(Optional[str], q.all_dialogue_data.get(_DIALOGUE_NAME)) + print("Dialogue data: ", dialogue_data) + if dialogue_data is None: + banned_nonterminals.add("QFruitSeller") + return banned_nonterminals + print("STARTING DSM IN BANNED NONTERMINALS") + q.start_dsm(_DIALOGUE_NAME, dialogue_data) + if q.dsm.not_in_dialogue(): + banned_nonterminals.add("QFruitSeller") + return banned_nonterminals + dsm: DialogueStateManager = q.dsm + resource: Resource = dsm.current_resource + if resource.name == "Fruits": + print("BANNINGBANNINGBANNING: Current resource is show!") + banned_nonterminals.add("QFruitDateQuery") + if resource.is_unfulfilled: + print("BANNINGBANNINGBANNING: Show was unfulfilled") + banned_nonterminals.add("QFruitYes") + banned_nonterminals.add("QFruitNo") + elif resource.name == "DateTime": + print("BANNINGBANNINGBANNING: Current resource is ShowDateTime!") + if resource.is_unfulfilled: + banned_nonterminals.add("QFruitYes") + banned_nonterminals.add("QFruitNo") + return banned_nonterminals + + def _generate_fruit_answer( resource: ListResource, dsm: DialogueStateManager, result: Result ) -> Optional[AnswerTuple]: diff --git a/queries/theater.py b/queries/theater.py index 51979a7d..192f2837 100644 --- a/queries/theater.py +++ b/queries/theater.py @@ -21,7 +21,7 @@ This query module handles dialogue related to theater tickets. """ -from typing import Any, Dict, List, Optional, Tuple, cast +from typing import Any, Dict, List, Optional, Set, Tuple, cast from typing_extensions import TypedDict import json import logging @@ -150,7 +150,7 @@ def help_text(lemma: str) -> str: QTheaterSeatOptions → "hvaða" "sæti" "eru" QTheaterIBodiLausar - "hverjir" "eru" "sæta" "valmöguleikarnir" + | "hverjir" "eru" "sæta" "valmöguleikarnir" QTheaterIBodiLausar → "í" "boði" @@ -160,10 +160,13 @@ def help_text(lemma: str) -> str: QTheaterShowQuery → QTheaterEgVil? "velja" 'sýning' QTheaterShowName > QTheaterEgVil? "fara" "á" 'sýning' QTheaterShowName - > QTheaterShowName + > QTheaterShowOnlyName + +QTheaterShowOnlyName → QTheaterShowName QTheaterShowName → Nl + QTheaterShowPrice → "hvað" "kostar" "einn"? 'miði' | "hvað" "kostar" "1"? 'miði' @@ -207,10 +210,14 @@ def help_text(lemma: str) -> str: | "sýningartímana" QTheaterShowSeatCountQuery → - QTheaterEgVil? "fá"? QTheaterNum "sæti"? + QTheaterSeatCountNum + | QTheaterEgVil? "fá"? QTheaterNum "sæti" | QTheaterEgVil? "fá"? QTheaterNum "miða" | QTheaterEgVil? "fá"? QTheaterNum "miða" "á" "sýninguna" +QTheaterSeatCountNum → + to | töl | tala + QTheaterShowLocationQuery → QTheaterShowRow | QTheaterShowSeats @@ -226,13 +233,22 @@ def help_text(lemma: str) -> str: | "fá" "sæti" "á" QTheaterRodBekkur → - QTheaterRodBekk? "númer"? QTheaterNum + QTheaterRowNum + | QTheaterRodBekk "númer"? QTheaterNum | QTheaterNum "bekk" | QTheaterNum "röð" +QTheaterRowNum → + to | töl | tala + QTheaterShowSeats → - QTheaterEgVil? "sæti"? "númer"? QTheaterNum "til"? QTheaterNum? - | QTheaterEgVil? "sæti"? "númer"? QTheaterNum "og"? QTheaterNum? + QTheaterShowSeatsNum + | QTheaterEgVil? "sæti"? "númer"? QTheaterNum "til" QTheaterNum? + | QTheaterEgVil? "sæti" "númer"? QTheaterNum "og" QTheaterNum? + | "ég" "vil" "sitja" "í" "röð" "númer" QTheaterNum + +QTheaterShowSeatsNum → + to | töl | tala QTheaterDateOptions → "hvaða" "dagsetningar" "eru" "í" "boði" @@ -285,6 +301,59 @@ def help_text(lemma: str) -> str: """ +def banned_nonterminals(q: Query) -> Set[str]: + banned_nonterminals: set[str] = set() + dialogue_data = cast(Optional[str], q.all_dialogue_data.get(_THEATER_DIALOGUE_NAME)) + if dialogue_data is None: + banned_nonterminals.add("QTheaterDialogue") + return banned_nonterminals + q.start_dsm(_THEATER_DIALOGUE_NAME, dialogue_data) + dsm: DialogueStateManager = q.dsm + if q.dsm.not_in_dialogue(): + banned_nonterminals.add("QTheaterDialogue") + return banned_nonterminals + resource: Resource = dsm.current_resource + if resource.name == "Show": + banned_nonterminals.add("QTheaterShowDateQuery") + banned_nonterminals.add("QTheaterMoreDates") + banned_nonterminals.add("QTheaterPreviousDates") + banned_nonterminals.add("QTheaterShowSeatCountQuery") + banned_nonterminals.add("QTheaterShowLocationQuery") + banned_nonterminals.add("QTheaterDateOptions") + banned_nonterminals.add("QTheaterRowOptions") + banned_nonterminals.add("QTheaterSeatOptions") + if resource.is_unfulfilled: + banned_nonterminals.add("QTheaterShowLength") + banned_nonterminals.add("QTheaterShowPrice") + elif resource.name == "ShowDateTime": + banned_nonterminals.add("QTheaterShowSeatCountQuery") + banned_nonterminals.add("QTheaterShowLocationQuery") + banned_nonterminals.add("QTheaterRowOptions") + banned_nonterminals.add("QTheaterSeatOptions") + banned_nonterminals.add("QTheaterShowOnlyName") + elif resource.name == "ShowSeatCount": + banned_nonterminals.add("QTheaterShowLocationQuery") + banned_nonterminals.add("QTheaterRowOptions") + banned_nonterminals.add("QTheaterSeatOptions") + banned_nonterminals.add("QTheaterRowNum") + banned_nonterminals.add("QTheaterShowSeatsNum") + banned_nonterminals.add("QTheaterShowOnlyName") + elif resource.name == "ShowSeatRow": + banned_nonterminals.add("QTheaterShowSeats") + banned_nonterminals.add("QTheaterSeatCountNum") + banned_nonterminals.add("QTheaterShowSeatsNum") + banned_nonterminals.add("QTheaterSeatOptions") + banned_nonterminals.add("QTheaterShowOnlyName") + elif resource.name == "ShowSeatNumber": + banned_nonterminals.add("QTheaterSeatCountNum") + banned_nonterminals.add("QTheaterRowNum") + banned_nonterminals.add("QTheaterShowOnlyName") + if resource.is_unfulfilled: + banned_nonterminals.add("QTheaterYes") + banned_nonterminals.add("QTheaterNo") + return banned_nonterminals + + class ShowType(TypedDict): title: str price: int @@ -1203,6 +1272,11 @@ def QTheaterNum(node: Node, params: QueryStateDict, result: Result): result.number = number +QTheaterSeatCountNum = QTheaterNum +QTheaterRowNum = QTheaterNum +QTheaterShowSeatsNum = QTheaterNum + + def QTheaterCancel(node: Node, params: QueryStateDict, result: Result): dsm: DialogueStateManager = Query.get_dsm(result) dsm.set_resource_state("Final", ResourceState.CANCELLED) diff --git a/query.py b/query.py index a17802b0..8df0ae35 100755 --- a/query.py +++ b/query.py @@ -295,6 +295,7 @@ def process_queries( # For development, we allow processors to be disinterested in any query # assert len(processor_query_types) > 0 if self.query_nonterminals.isdisjoint(processor_query_types): + print("!!!!!No query trees to process in this processor!!!!!") # But this processor is not interested in any of the nonterminals # in this query's parse forest: don't waste more cycles on it return False @@ -539,13 +540,15 @@ def init_class(cls) -> None: cls._parser = QueryParser(grammar_additions) @staticmethod - def _parse(toklist: Iterable[Tok]) -> Tuple[ResponseDict, Dict[int, str]]: + def _parse( + toklist: Iterable[Tok], banned_nonterminals: Optional[Set[str]] = None + ) -> Tuple[ResponseDict, Dict[int, str]]: """Parse a token list as a query""" bp = Query._parser assert bp is not None num_sent = 0 num_parsed_sent = 0 - rdc = Reducer(bp.grammar) + rdc = Reducer(bp.grammar, banned_nonterminals=banned_nonterminals) trees: Dict[int, str] = dict() sent: List[Tok] = [] @@ -636,8 +639,16 @@ def parse(self, result: ResponseDict) -> bool: # Log the query string as seen by the parser print("Query is: '{0}'".format(actual_q)) + banned_nonterminals: Set[str] = set() + for t in Query._tree_processors: + f = getattr(t, "banned_nonterminals", None) + if f is not None: + banned_nonterminals.update(f(self)) + print("QUERY BANNED NON TERMINALS: ", banned_nonterminals) try: - parse_result, trees = Query._parse(toklist) + parse_result, trees = Query._parse( + toklist, banned_nonterminals=banned_nonterminals + ) except ParseError: self.set_error("E_PARSE_ERROR") return False From 5985e2dcbfe658de2a058f89a27fd396e743dcc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Wed, 13 Jul 2022 16:40:42 +0000 Subject: [PATCH 224/371] changed adding non_terminals to update --- queries/theater.py | 90 ++++++++++++++++++++++++++++++---------------- 1 file changed, 59 insertions(+), 31 deletions(-) diff --git a/queries/theater.py b/queries/theater.py index 192f2837..d2e7db98 100644 --- a/queries/theater.py +++ b/queries/theater.py @@ -314,43 +314,71 @@ def banned_nonterminals(q: Query) -> Set[str]: return banned_nonterminals resource: Resource = dsm.current_resource if resource.name == "Show": - banned_nonterminals.add("QTheaterShowDateQuery") - banned_nonterminals.add("QTheaterMoreDates") - banned_nonterminals.add("QTheaterPreviousDates") - banned_nonterminals.add("QTheaterShowSeatCountQuery") - banned_nonterminals.add("QTheaterShowLocationQuery") - banned_nonterminals.add("QTheaterDateOptions") - banned_nonterminals.add("QTheaterRowOptions") - banned_nonterminals.add("QTheaterSeatOptions") + banned_nonterminals.update( + { + "QTheaterShowDateQuery", + "QTheaterMoreDates", + "QTheaterPreviousDates", + "QTheaterShowSeatCountQuery", + "QTheaterShowLocationQuery", + "QTheaterDateOptions", + "QTheaterRowOptions", + "QTheaterSeatOptions", + } + ) if resource.is_unfulfilled: - banned_nonterminals.add("QTheaterShowLength") - banned_nonterminals.add("QTheaterShowPrice") + banned_nonterminals.update( + { + "QTheaterShowLength", + "QTheaterShowPrice", + } + ) elif resource.name == "ShowDateTime": - banned_nonterminals.add("QTheaterShowSeatCountQuery") - banned_nonterminals.add("QTheaterShowLocationQuery") - banned_nonterminals.add("QTheaterRowOptions") - banned_nonterminals.add("QTheaterSeatOptions") - banned_nonterminals.add("QTheaterShowOnlyName") + banned_nonterminals.update( + { + "QTheaterShowSeatCountQuery", + "QTheaterShowLocationQuery", + "QTheaterRowOptions", + "QTheaterSeatOptions", + "QTheaterShowOnlyName", + } + ) elif resource.name == "ShowSeatCount": - banned_nonterminals.add("QTheaterShowLocationQuery") - banned_nonterminals.add("QTheaterRowOptions") - banned_nonterminals.add("QTheaterSeatOptions") - banned_nonterminals.add("QTheaterRowNum") - banned_nonterminals.add("QTheaterShowSeatsNum") - banned_nonterminals.add("QTheaterShowOnlyName") + banned_nonterminals.update( + { + "QTheaterShowLocationQuery", + "QTheaterRowOptions", + "QTheaterSeatOptions", + "QTheaterRowNum", + "QTheaterShowSeatsNum", + "QTheaterShowOnlyName", + } + ) elif resource.name == "ShowSeatRow": - banned_nonterminals.add("QTheaterShowSeats") - banned_nonterminals.add("QTheaterSeatCountNum") - banned_nonterminals.add("QTheaterShowSeatsNum") - banned_nonterminals.add("QTheaterSeatOptions") - banned_nonterminals.add("QTheaterShowOnlyName") + banned_nonterminals.update( + { + "QTheaterShowSeats", + "QTheaterSeatCountNum", + "QTheaterShowSeatsNum", + "QTheaterSeatOptions", + "QTheaterShowOnlyName", + } + ) elif resource.name == "ShowSeatNumber": - banned_nonterminals.add("QTheaterSeatCountNum") - banned_nonterminals.add("QTheaterRowNum") - banned_nonterminals.add("QTheaterShowOnlyName") + banned_nonterminals.update( + { + "QTheaterSeatCountNum", + "QTheaterRowNum", + "QTheaterShowOnlyName", + } + ) if resource.is_unfulfilled: - banned_nonterminals.add("QTheaterYes") - banned_nonterminals.add("QTheaterNo") + banned_nonterminals.update( + { + "QTheaterYes", + "QTheaterNo", + } + ) return banned_nonterminals From a61d55e7f69f36b9303847cf0f394732b2296428 Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Thu, 14 Jul 2022 15:05:01 +0000 Subject: [PATCH 225/371] Basic spotify functionality for playing a song working Basic spotify functionality for playing a song working --- queries/__init__.py | 27 ++++++ queries/iot_spotify.py | 198 +++++++++++++++++++++++++++++++++++++++++ queries/sonos.py | 41 +++++---- queries/spotify.py | 97 +++++++++++++++++--- 4 files changed, 332 insertions(+), 31 deletions(-) create mode 100644 queries/iot_spotify.py diff --git a/queries/__init__.py b/queries/__init__.py index beff2e49..c2dd018a 100755 --- a/queries/__init__.py +++ b/queries/__init__.py @@ -480,6 +480,33 @@ def post_to_json_api( return None +def put_to_json_api( + url: str, json_data: Optional[Any] = None, headers: Optional[Dict[str, str]] = None +) -> Union[None, List[Any], Dict[str, Any]]: + """Send a POST request to the URL, expecting a JSON response which is + parsed and returned as a Python data structure.""" + + # Send request + try: + r = requests.put(url, data=json_data, headers=headers) + except Exception as e: + logging.warning(str(e)) + return None + + # Verify that status is OK + if r.status_code not in range(200, 300): + logging.warning("Received status {0} from API server".format(r.status_code)) + return None + + # Parse json API response + try: + res = json.loads(r.text) + return res + except Exception as e: + logging.warning("Error parsing JSON API response: {0}".format(e)) + return None + + def query_xml_api(url: str) -> Any: """Request the URL, expecting an XML response which is parsed and returned as an XML document object.""" diff --git a/queries/iot_spotify.py b/queries/iot_spotify.py new file mode 100644 index 00000000..450476b0 --- /dev/null +++ b/queries/iot_spotify.py @@ -0,0 +1,198 @@ +""" + + Greynir: Natural language processing for Icelandic + + Randomness query response module + + Copyright (C) 2022 Miðeind ehf. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see http://www.gnu.org/licenses/. + + This query module handles queries related to the generation + of random numbers, e.g. "Kastaðu tengingi", "Nefndu tölu milli 5 og 10", etc. + +""" + + +# TODO: add "láttu", "hafðu", "litaðu", "kveiktu" functionality. +# TODO: make the objects of sentences more modular, so that the same structure doesn't need to be written for each action +# TODO: ditto the previous comment. make the initial non-terminals general and go into specifics at the terminal level instead. +# TODO: substituion klósett, baðherbergi hugmyndÆ senda lista i javascript og profa i röð +# TODO: Embla stores old javascript code cached which has caused errors +# TODO: Cut down javascript sent to Embla +# TODO: Two specified groups or lights. +# TODO: No specified location +# TODO: Fix scene issues + +from os import access +from typing import Dict, Mapping, Optional, cast +from typing_extensions import TypedDict + +import logging +import random +import json +import flask +from datetime import datetime, timedelta +import re + +from reynir.lemmatize import simple_lemmatize + +from query import Query, QueryStateDict, AnswerTuple +from queries import gen_answer, read_jsfile, read_grammar_file +from queries.spotify import SpotifyClient +from tree import Result, Node, TerminalNode +from util import read_api_key + + +_IoT_QTYPE = "IoTSpotify" + +# TOPIC_LEMMAS = [ +# "tónlist", +# "spila", +# ] + + +def help_text(lemma: str) -> str: + """Help text to return when query.py is unable to parse a query but + one of the above lemmas is found in it""" + return "Ég skil þig ef þú segir til dæmis: {0}.".format( + random.choice( + ("Hækkaðu í tónlistinni", "Kveiktu á tónlist", "Láttu vera tónlist") + ) + ) + + +# This module wants to handle parse trees for queries +HANDLE_TREE = True + +# The grammar nonterminals this module wants to handle +QUERY_NONTERMINALS = {"QIoTSpotify"} + +# The context-free grammar for the queries recognized by this plug-in module + +_SPOTIFY_REGEXES = [ + r"^(spilaðu )([\w|\s]+)(með )([\w|\s]+)$", +] + +GRAMMAR = f""" + +/þgf = þgf +/ef = ef + +Query → + QIoTSpotify '?'? + +QIoTSpotify → + QIoTSpotifyPlaySongByArtist + +QIoTSpotifyPlaySongByArtist → + QIoTSpotifyPlayVerb QIoTSpotifySongName QIoTSpotifyWithPreposition QIoTSpotifyArtistName + +QIoTSpotifyPlayVerb → + 'spila:so'_bh + +QIoTSpotifySongName → + Nl + +QIoTSpotifyWithPreposition → + 'með' + | 'eftir' + +QIoTSpotifyArtistName → + Nl + | sérnafn +""" + + +def QIoTSpotify(node: Node, params: QueryStateDict, result: Result) -> None: + result.qtype = _IoT_QTYPE + + +def QIoTSpotifyPlayVerb(node: Node, params: QueryStateDict, result: Result) -> None: + "spotify play function" + result.action = "play" + + +def QIoTSpotifySongName(node: Node, params: QueryStateDict, result: Result) -> None: + result.song_name = result._text + + +def QIoTSpotifyArtistName(node: Node, params: QueryStateDict, result: Result) -> None: + result.artist_name = result._indefinite + + +def get_song_and_artist(q: Query) -> tuple: + """Handle a plain text query requesting Spotify to play a specific song by a specific artist.""" + # ql = q.query_lower.strip().rstrip("?") + print("handle_plain_text") + ql = q.query_lower.strip().rstrip("?") + print("QL:", ql) + + pfx = None + + for rx in _SPOTIFY_REGEXES: + print(rx) + print("") + m = re.search(rx, ql) + print(m) + if m: + (print("MATCH!")) + song_name = m.group(2) + artist_name = m.group(4).strip() + return (song_name, artist_name) + else: + return False + + +def sentence(state: QueryStateDict, result: Result) -> None: + """Called when sentence processing is complete""" + print("sentence") + q: Query = state["query"] + if result.action == "play": + print("SPOTIFY PLAY") + song_artist_tuple = get_song_and_artist(q) + print("exited plain text") + song_name = song_artist_tuple[0] + artist_name = song_artist_tuple[1] + print("SONG NAME :", song_name) + print("ARTIST NAME :", artist_name) + + print("RESTULT SONG NAME:", result.song_name) + print("RESTULT ARTIST NAME:", result.artist_name) + device_data = q.client_data("spotify") + client_id = str(q.client_id) + spotify_client = SpotifyClient( + device_data, + client_id, + song_name=result.song_name, + artist_name=result.artist_name, + ) + song_url = spotify_client.get_song_by_artist() + response = spotify_client.play_song_on_device() + print("RESPONSE FROM SPOTIFY:", response) + if response is None: + q.set_url(song_url) + + answer = "Ég spilaði lagið" + # q.set_url( + # "https://spotify.app.link/?product=open&%24full_url=https%3A%2F%2Fopen.spotify.com%2Ftrack%2F2BSyX4weGuITcvl5r2lLCC%3Fgo%3D1%26sp_cid%3D2a74d03dedb9fa4450d122ddebebcf9b%26fallback%3Dgetapp&feature=organic&_p=c31529c0980b7af1e11b90f9" + # ) + voice_answer, response = answer, dict(answer=answer) + q.set_answer(response, answer, voice_answer) + + else: + print("ELSE") + q.set_error("E_QUERY_NOT_UNDERSTOOD") + return + + # # TODO: Need to add check for if there are no registered devices to an account, probably when initilazing the querydata diff --git a/queries/sonos.py b/queries/sonos.py index 0b8e3f49..42c015e7 100644 --- a/queries/sonos.py +++ b/queries/sonos.py @@ -95,10 +95,10 @@ def __init__( self._device_data = device_data self._group_name = group_name self._radio_name = radio_name - self._sonos_encoded_credentials = read_api_key("SonosEncodedCredentials") + self._encoded_credentials = read_api_key("SonosEncodedCredentials") self._code = self._device_data["sonos"]["credentials"]["code"] print("code :", self._code) - self._timestamp = datetime.now() + self._timestamp = device_data.get("sonos").get("credentials").get("timestamp") print("device data :", self._device_data) try: self._access_token = self._device_data["sonos"]["credentials"][ @@ -115,7 +115,7 @@ def __init__( self._groups = self._get_groups() self._players = self._get_players() self._group_id = self._get_group_id() - self._store_sonos_data_and_credentials() + self._store_data_and_credentials() """ ------------------------------------- PRIVATE METHODS -------------------------------------------------------------------------------- @@ -139,14 +139,13 @@ def _update_sonos_token(self): Updates the access token """ print("update sonos token") - self._sonos_encoded_credentials = read_api_key("SonosEncodedCredentials") - self._access_token = self._refresh_expired_token() - self._access_token = self._access_token["access_token"] + self._encoded_credentials = read_api_key("SonosEncodedCredentials") + self._refresh_expired_token() sonos_dict = { "sonos": { "credentials": { "access_token": self._access_token, - "timestamp": str(datetime.now()), + "timestamp": self._timestamp, } } } @@ -159,10 +158,13 @@ def _refresh_expired_token(self): """ print("_refresh_expired_token") url = f"https://api.sonos.com/login/v3/oauth/access?grant_type=refresh_token&refresh_token={self._refresh_token}" - headers = {"Authorization": f"Basic {self._sonos_encoded_credentials}"} + headers = {"Authorization": f"Basic {self._encoded_credentials}"} response = post_to_json_api(url, headers=headers) + self._access_token = response["access_token"] + self._timestamp = str(datetime.now()) + return response def _create_token(self): @@ -173,7 +175,7 @@ def _create_token(self): host = str(flask.request.host) url = f"https://api.sonos.com/login/v3/oauth/access?grant_type=authorization_code&code={self._code}&redirect_uri=http://{host}/connect_sonos.api" headers = { - "Authorization": f"Basic {self._sonos_encoded_credentials}", + "Authorization": f"Basic {self._encoded_credentials}", "Cookie": "JSESSIONID=F710019AF0A3B7126A8702577C883B5F; AWSELB=69BFEFC914A689BF6DC8E4652748D7B501ED60290D5EA56F2E543ABD7CF357A5F65186AEBCFB059E28075D83A700FD504C030A53CC28683B515BE3DCA3CC587AFAF606E171; AWSELBCORS=69BFEFC914A689BF6DC8E4652748D7B501ED60290D5EA56F2E543ABD7CF357A5F65186AEBCFB059E28075D83A700FD504C030A53CC28683B515BE3DCA3CC587AFAF606E171", } @@ -181,6 +183,7 @@ def _create_token(self): self._access_token = response.get("access_token") self._refresh_token = response.get("refresh_token") + self._timestamp = str(datetime.now()) return response def _get_households(self): @@ -308,8 +311,8 @@ def _get_player_id(self): return response["players"][0]["id"] - def _create_sonos_data_dict(self): - print("_create_sonos_data_dict") + def _create_data_dict(self): + print("_create_data_dict") data_dict = {"households": self._households} for i in range(len(self._households)): groups_dict = self._groups @@ -319,22 +322,22 @@ def _create_sonos_data_dict(self): data_dict["players"] = players_dict return data_dict - def _create_sonos_cred_dict(self): - print("_create_sonos_cred_dict") + def _create_cred_dict(self): + print("_create_cred_dict") cred_dict = {} cred_dict.update( { "access_token": self._access_token, "refresh_token": self._refresh_token, - "timestamp": str(datetime.now()), + "timestamp": self._timestamp, } ) return cred_dict - def _store_sonos_data_and_credentials(self): - print("_store_sonos_data_and_credentials") - # data_dict = self._create_sonos_data_dict() - cred_dict = self._create_sonos_cred_dict() + def _store_data_and_credentials(self): + print("_store_data_and_credentials") + # data_dict = self._create_data_dict() + cred_dict = self._create_cred_dict() sonos_dict = {} sonos_dict["sonos"] = {"credentials": cred_dict} self._store_data(sonos_dict) @@ -565,7 +568,7 @@ def prev_song(self): # self._households = self._get_households() # self._groups = self._get_groups() # self._players = self._get_players() - # # self._store_sonos_data_and_credentials() + # # self._store_data_and_credentials() # getattr(self, function)() # def get_groups_and_players(self): diff --git a/queries/spotify.py b/queries/spotify.py index 05212c38..142dbda6 100644 --- a/queries/spotify.py +++ b/queries/spotify.py @@ -5,31 +5,38 @@ import random from util import read_api_key -from queries import query_json_api, post_to_json_api +from queries import query_json_api, post_to_json_api, put_to_json_api from query import Query from typing import Dict import json - +# TODO Finna út af hverju self token virkar ekki class SpotifyClient: def __init__( self, device_data: Dict[str, str], client_id: str, + song_name=None, + artist_name=None, ): self._client_id = client_id self._device_data = device_data self._encoded_credentials = read_api_key("SpotifyEncodedCredentials") self._code = self._device_data["credentials"]["code"] + self._song_name = song_name + self._artist_name = artist_name + self._song_url = None print("code :", self._code) - self._timestamp = datetime.now() + self._timestamp = self._device_data.get("credentials").get("timestamp") print("device data :", self._device_data) try: self._access_token = self._device_data["credentials"]["access_token"] + self._refresh_token = self._device_data["credentials"]["refresh_token"] except (KeyError, TypeError): self._create_token() self._check_token_expiration() + self._devices = self._get_devices() self._store_credentials() def _create_token(self): @@ -49,20 +56,22 @@ def _create_token(self): response = post_to_json_api(url, payload, headers) self._access_token = response.get("access_token") self._refresh_token = response.get("refresh_token") - self._timestamp = datetime.now() + self._timestamp = str(datetime.now()) return response def _check_token_expiration(self): """ Checks if access token is expired, and calls a function to refresh it if necessary. """ + print("check token expiration") try: - timestamp = self._device_data["spotify"]["credentials"]["timestamp"] + timestamp = self._device_data["credentials"]["timestamp"] except (KeyError, TypeError): print("No timestamp found for Sonos token.") return timestamp = datetime.strptime(timestamp, "%Y-%m-%d %H:%M:%S.%f") if (datetime.now() - timestamp) > timedelta(hours=1): + print("more than 1 hour") self._update_sonos_token() def _update_sonos_token(self): @@ -74,7 +83,7 @@ def _update_sonos_token(self): cred_dict = { "credentials": { "access_token": self._access_token, - "timestamp": str(datetime.now()), + "timestamp": self._timestamp, } } self._store_data(cred_dict) @@ -96,22 +105,21 @@ def _refresh_expired_token(self): response = post_to_json_api(url, payload, headers) self._access_token = response.get("access_token") + self._timestamp = str(datetime.now()) def _store_credentials(self): print("_store_smartthings_cred") # data_dict = self._create_sonos_data_dict() cred_dict = self._create_cred_dict() - smartthings_dict = {} - smartthings_dict["smartthings"] = {"credentials": cred_dict} - self._store_data(smartthings_dict) + self._store_data(cred_dict) def _create_cred_dict(self): - print("_create_smartthings_cred_dict") + print("_create_spotify_cred_dict") cred_dict = {} cred_dict.update( { "access_token": self._access_token, - "timestamp": str(datetime.now()), + "timestamp": self._timestamp, } ) return cred_dict @@ -134,7 +142,72 @@ def _create_cred_dict(self): { "access_token": self._access_token, "refresh_token": self._refresh_token, - "timestamp": str(datetime.now()), + "timestamp": self._timestamp, } ) return cred_dict + + def get_song_by_artist(self): + print("get song by artist") + print("accesss token get song; ", self._access_token) + song_name = self._song_name.replace(" ", "%20") + artist_name = self._artist_name.replace(" ", "%20") + + url = f"https://api.spotify.com/v1/search?q=track:{song_name}+artist:{artist_name}&type=track" + + payload = "" + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {self._access_token}", + } + + response = query_json_api(url, headers) + self._song_url = response["tracks"]["items"][0]["external_urls"]["spotify"] + self._song_uri = response["tracks"]["items"][0]["uri"] + print("SONG URI: ", self._song_uri) + + return self._song_url + + def play_song_on_device(self): + print("play song from device") + print("accesss token play song; ", self._access_token) + self._device_data = self._get_devices() + url = "https://api.spotify.com/v1/me/player/play" + + payload = json.dumps( + { + "uris": [ + f"{self._song_uri}", + ] + } + ) + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {self._access_token}", + } + + response = put_to_json_api(url, payload, headers) + + print(response) + return response + + def _get_devices(self): + print("get devices") + print("accesss token get devices; ", self._access_token) + url = f"https://api.spotify.com/v1/me/player/devices" + + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {self._access_token}", + } + + response = query_json_api(url, headers) + return response["devices"] + + def filter_devices(self): + print("filter devices") + filtered_devices = [] + for device in self._devices: + if device["type"] == "Smartphone": + filtered_devices.append(device) + return filtered_devices From caae3d17761488357d42330e8a115642cf52084a Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Thu, 14 Jul 2022 15:25:09 +0000 Subject: [PATCH 226/371] "query" changed to "target" in set_lights.js --- .../js/IoT_Embla/Philips_Hue/set_lights.js | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/queries/js/IoT_Embla/Philips_Hue/set_lights.js b/queries/js/IoT_Embla/Philips_Hue/set_lights.js index 46c790c7..798607a0 100644 --- a/queries/js/IoT_Embla/Philips_Hue/set_lights.js +++ b/queries/js/IoT_Embla/Philips_Hue/set_lights.js @@ -7,10 +7,10 @@ // TODO: Implement a hotfix for Ikea Tradfri bulbs, since it can only take one argument at a time /** Gets a target for the given query and sets the state of the target to the given state using a fetch request. - * @param {String} query - the query to find the target e.g. "eldhús" or "lampi" + * @param {String} target - the target to find the target e.g. "eldhús" or "lampi" * @param {String} state - the state to set the target to e.g. "{"on": true}" or "{"scene": "energize"}" */ -function setLights(query, state) { +function setLights(target, state) { let parsedState = JSON.parse(state); let promiseList = [getAllGroups(), getAllLights()]; let sceneName; @@ -29,8 +29,8 @@ function setLights(query, state) { console.log("No scene in state"); } - // Get the target object for the given query - let targetObject = getTargetObject(query, allLights, allGroups); + // Get the target object for the given target + let targetObject = getTargetObject(target, allLights, allGroups); if (targetObject === undefined) { return "Ekki tókst að finna ljós"; } @@ -65,14 +65,14 @@ function setLights(query, state) { } /** Finds a matching light or group and returns an object with the ID, name and url for the target - * @param {String} query - the query to find the target e.g. "eldhús" + * @param {String} target - the target to find the target e.g. "eldhús" * @param {Object} allLights - an object of all lights from the API * @param {Object} allGroups - an object of all groups from the API */ -function getTargetObject(query, allLights, allGroups) { +function getTargetObject(target, allLights, allGroups) { let targetObject, selection, url; - let lightsResult = philipsFuzzySearch(query, allLights); - let groupsResult = philipsFuzzySearch(query, allGroups); + let lightsResult = philipsFuzzySearch(target, allLights); + let groupsResult = philipsFuzzySearch(target, allGroups); if (lightsResult != null && groupsResult != null) { // Found a match for a light group and a light+ @@ -120,37 +120,37 @@ function getSceneID(scene_name, allScenes) { /* Tester function for setting lights directly from HTML controls */ function setLightsFromHTML() { - let query = document.getElementById("queryInput").value; + let target = document.getElementById("queryInput").value; let stateObject = new Object(); stateObject.bri_inc = Number( document.getElementById("brightnessInput").value ); stateObject = JSON.stringify(stateObject); - setLights(query, stateObject); + setLights(target, stateObject); } /* Tester function for setting lights directly from HTML input fields */ function queryTestFromHTML() { - let query = document.getElementById("queryInput").value; + let target = document.getElementById("queryInput").value; let bool = document.getElementById("boolInput").value; let scene = document.getElementById("sceneInput").value; - console.log(query); + console.log(target); if (scene === "") { - setLights(query, `{"on": ${bool}}`); + setLights(target, `{"on": ${bool}}`); } else { - setLights(query, `{"scene": "${scene}"}`); + setLights(target, `{"scene": "${scene}"}`); } } // /** Finds a matching light or group and returns an object with the ID, name and url for the target -// * @param {String} query - the query to find the target e.g. "eldhús" +// * @param {String} target - the target to find the target e.g. "eldhús" // * @param {Object} allLights - an array of all lights from the API // * @param {Object} allGroups - an array of all groups from the API // */ -// function getTargetObjectOLD(query, allLights, allGroups) { +// function getTargetObjectOLD(target, allLights, allGroups) { // let targetObject; -// let lightsResult = philipsFuzzySearch(query, allLights); -// let groupsResult = philipsFuzzySearch(query, allGroups); +// let lightsResult = philipsFuzzySearch(target, allLights); +// let groupsResult = philipsFuzzySearch(target, allGroups); // console.log("lightsResult: ", lightsResult); // console.log("groupsResult: ", groupsResult); From c2ee4151dd2e2242a59e9b0c81ea0fc30cab446c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Fri, 15 Jul 2022 16:53:49 +0000 Subject: [PATCH 227/371] initial singleton dsm implementation --- queries/dialogue.py | 46 +++++++---- queries/fruitseller.py | 44 +++++------ queries/theater.py | 34 ++------ query.py | 176 ++++++++++++++++++++++++++--------------- 4 files changed, 173 insertions(+), 127 deletions(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index dbacd0b2..c84e54f7 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -87,6 +87,10 @@ class DialogueTOMLStructure(TypedDict): _EXPIRATION_TIME_KEY = "expiration_time" +# Dialogue data +DialogueDataDict = Dict[str, str] + + class DialogueDBStructure(TypedDict): """ Representation of the dialogue structure, @@ -98,10 +102,22 @@ class DialogueDBStructure(TypedDict): extras: Dict[str, Any] -class DialogueStateManager: +class DialogueStateManager(object): DIALOGUE_DATA_KEY = "dialogue" + _instance = None + + # TODO: Check if singleton can be done in a better way + def __new__(cls, dialogue_data: DialogueDataDict) -> "DialogueStateManager": + if cls._instance is None: + cls._instance = super(DialogueStateManager, cls).__new__(cls) + # Put any initialization here. + cls._dialogue_data: DialogueDataDict = dialogue_data + return cls._instance + + def __init__(self, dialogue_data: DialogueDataDict) -> None: + self._dialogue_data: DialogueDataDict = dialogue_data - def __init__(self, dialogue_name: str, saved_state: Optional[str] = None): + def load_dialogue(self, dialogue_name: str): self._dialogue_name: str = dialogue_name # Dict mapping resource name to resource instance self._resources: Dict[str, Resource] = {} @@ -122,15 +138,21 @@ def __init__(self, dialogue_name: str, saved_state: Optional[str] = None): self._expiration_time: int = _DEFAULT_EXPIRATION_TIME self._timed_out: bool = False - if isinstance(saved_state, str): + dialogue_saved_state: Optional[str] = self._dialogue_data.get( + dialogue_name, None + ) + if isinstance(dialogue_saved_state, str): self._saved_state = cast( - DialogueDBStructure, json.loads(saved_state, cls=DialogueJSONDecoder) + DialogueDBStructure, + json.loads(dialogue_saved_state, cls=DialogueJSONDecoder), ) # Check that we have saved data for this dialogue and that it is not expired if self._saved_state[_RESOURCES_KEY]: self._in_this_dialogue = True self.setup_resources() + else: + print("NO DIALOGUE DATA FOR", dialogue_name) def setup_resources(self) -> None: """ @@ -156,9 +178,7 @@ def setup_resources(self) -> None: if self._saved_state and _EXTRAS_KEY in self._saved_state: self._extras = self._saved_state.get(_EXTRAS_KEY) or self._extras # Create resource dependency relationship graph - print("SETTING UP RESOURCE GRAPH") self._initialize_resource_graph() - print("FINISHED SETTING UP RESOURCE GRAPH") def _initialize_resource_graph(self) -> None: """ @@ -175,7 +195,6 @@ def _initialize_resource_graph(self) -> None: for req in resource.requires: self._resource_graph[self._resources[req]]["parents"].append(resource) self._resource_graph[resource]["children"].append(self._resources[req]) - print(self._resource_graph) def _initialize_resources(self, filename: str) -> None: """ @@ -202,9 +221,7 @@ def _initialize_resources(self, filename: str) -> None: def hotword_activated(self) -> None: self._in_this_dialogue = True - print("STARTING RESOURCE SETUP") self.setup_resources() - print("FINISHED RESOURCE SETUP") def pause_dialogue(self) -> None: ... # TODO @@ -216,6 +233,12 @@ def not_in_dialogue(self) -> bool: """Check if the client is in or wants to start this dialogue""" return not self._in_this_dialogue + @property + def dialogue_name(self) -> Optional[str]: + if hasattr(self, "_dialogue_name"): + return self._dialogue_name + return None + @property def current_resource(self) -> Resource: if self._current_resource is None: @@ -290,16 +313,12 @@ def set_resource_state(self, resource_name: str, state: ResourceState): Sets state of all parent resources to unfulfilled if cascade_state is set to True for the resource. """ - print("SETTING STATE OF RESOURCE:", resource_name, "TO STATE:", state) resource = self._resources[resource_name] lowered_state = resource.state > state resource.state = state - print("CASCADES?", self._resources[resource_name].cascade_state) if resource.cascade_state and lowered_state: # Find all parent resources and set to corresponding state - print("SEARCHING FOR PARENTS") parents = self._find_parent_resources(self._resources[resource_name]) - print("PARENTS FOUND:", parents) for parent in parents: parent.state = ResourceState.UNFULFILLED @@ -328,7 +347,6 @@ def _find_current_resource(self) -> Resource: ): curr_res = grandparents[0] break - print("CURRENT RESOURCE:", curr_res) return curr_res def finish_dialogue(self) -> None: diff --git a/queries/fruitseller.py b/queries/fruitseller.py index f89f76bc..8dd58d0a 100644 --- a/queries/fruitseller.py +++ b/queries/fruitseller.py @@ -21,9 +21,12 @@ WrapperResource, ) -# Indicate that this module wants to handle parse trees for queries, +_START_DIALOGUE_QTYPE = "QFruitStartQuery" +_DIALOGUE_NAME = "fruitseller" + +# Indicate that this module wants to handle dialogue parse trees for queries, # as opposed to simple literal text strings -HANDLE_TREE = True +HANDLE_DIALOGUE = True DIALOGUE_NAME = "fruitseller" HOTWORD_NONTERMINALS = {"QFruitStartQuery"} @@ -31,16 +34,17 @@ # The grammar nonterminals this module wants to handle QUERY_NONTERMINALS = {"QFruitSeller"}.union(HOTWORD_NONTERMINALS) + # The context-free grammar for the queries recognized by this plug-in module GRAMMAR = """ Query → - QFruitStartQuery | QFruitSellerQuery - -QFruitSellerQuery → - QFruitSeller + QFruitStartQuery | QFruitSeller QFruitSeller → + QFruitSellerQuery + +QFruitSellerQuery → QFruitQuery '?'? | QFruitDateQuery '?'? | QFruitInfoQuery '?'? @@ -150,33 +154,19 @@ """ -_START_DIALOGUE_QTYPE = "QFruitStartQuery" -_DIALOGUE_NAME = "fruitseller" - def banned_nonterminals(q: Query) -> Set[str]: banned_nonterminals: set[str] = set() - dialogue_data = cast(Optional[str], q.all_dialogue_data.get(_DIALOGUE_NAME)) - print("Dialogue data: ", dialogue_data) - if dialogue_data is None: - banned_nonterminals.add("QFruitSeller") + if q.dsm.dialogue_name != DIALOGUE_NAME: + banned_nonterminals.add("QFruitSellerQuery") return banned_nonterminals - print("STARTING DSM IN BANNED NONTERMINALS") - q.start_dsm(_DIALOGUE_NAME, dialogue_data) - if q.dsm.not_in_dialogue(): - banned_nonterminals.add("QFruitSeller") - return banned_nonterminals - dsm: DialogueStateManager = q.dsm - resource: Resource = dsm.current_resource + resource: Resource = q.dsm.current_resource if resource.name == "Fruits": - print("BANNINGBANNINGBANNING: Current resource is show!") banned_nonterminals.add("QFruitDateQuery") if resource.is_unfulfilled: - print("BANNINGBANNINGBANNING: Show was unfulfilled") banned_nonterminals.add("QFruitYes") banned_nonterminals.add("QFruitNo") elif resource.name == "DateTime": - print("BANNINGBANNINGBANNING: Current resource is ShowDateTime!") if resource.is_unfulfilled: banned_nonterminals.add("QFruitYes") banned_nonterminals.add("QFruitNo") @@ -464,6 +454,13 @@ def QFruitDateTime(node: Node, params: QueryStateDict, result: Result) -> None: def QFruitInfoQuery(node: Node, params: QueryStateDict, result: Result): result.qtype = "QFruitInfo" + dsm: DialogueStateManager = Query.get_dsm(result) + at = dsm.get_answer(_ANSWERING_FUNCTIONS, result) + if at: + (_, ans, voice) = at + ans = "Ávaxtapöntunin þín gengur bara vel. " + ans + voice = "Ávaxtapöntunin þín gengur bara vel. " + voice + dsm.set_answer((dict(answer=ans), ans, voice)) _ANSWERING_FUNCTIONS: AnsweringFunctionMap = { @@ -499,6 +496,7 @@ def sentence(state: QueryStateDict, result: Result) -> None: # return ans = dsm.get_answer(_ANSWERING_FUNCTIONS, result) + print("FRUIT ANS: ", ans) if not ans: print("No answer generated") q.set_error("E_QUERY_NOT_UNDERSTOOD") diff --git a/queries/theater.py b/queries/theater.py index d2e7db98..37534473 100644 --- a/queries/theater.py +++ b/queries/theater.py @@ -48,7 +48,7 @@ DialogueStateManager, ) -_THEATER_DIALOGUE_NAME = "theater" +_DIALOGUE_NAME = "theater" _THEATER_QTYPE = "theater" _START_DIALOGUE_QTYPE = "theater_start" @@ -63,8 +63,8 @@ def help_text(lemma: str) -> str: ) -# This module wants to handle parse trees for queries -HANDLE_TREE = True +# This module wants to handle dialogue parse trees for queries +HANDLE_DIALOGUE = True # This module involves dialogue functionality DIALOGUE_NAME = "theater" @@ -79,10 +79,10 @@ def help_text(lemma: str) -> str: Query → QTheaterHotWord | QTheater -QTheater → QTheaterQuery '?'? +QTheater → QTheaterQuery QTheaterQuery → - QTheaterDialogue + QTheaterDialogue '?'? QTheaterHotWord → QTheaterNames '?'? @@ -303,16 +303,10 @@ def help_text(lemma: str) -> str: def banned_nonterminals(q: Query) -> Set[str]: banned_nonterminals: set[str] = set() - dialogue_data = cast(Optional[str], q.all_dialogue_data.get(_THEATER_DIALOGUE_NAME)) - if dialogue_data is None: - banned_nonterminals.add("QTheaterDialogue") + if q.dsm.dialogue_name != DIALOGUE_NAME: + banned_nonterminals.add("QTheaterQuery") return banned_nonterminals - q.start_dsm(_THEATER_DIALOGUE_NAME, dialogue_data) - dsm: DialogueStateManager = q.dsm - if q.dsm.not_in_dialogue(): - banned_nonterminals.add("QTheaterDialogue") - return banned_nonterminals - resource: Resource = dsm.current_resource + resource: Resource = q.dsm.current_resource if resource.name == "Show": banned_nonterminals.update( { @@ -467,17 +461,14 @@ def _generate_date_answer( resource: ListResource, dsm: DialogueStateManager, result: Result ) -> Optional[AnswerTuple]: title = dsm.get_resource("Show").data[0] - print("GENERATING DATE ANSWER") dates: list[str] = [] text_dates: list[str] = [] index: int = 0 extras: Dict[str, Any] = dsm.extras if "page_index" in extras: index = extras["page_index"] - print("itering shows") for show in _SHOWS: if show["title"] == title: - print("itering dates") for date in show["date"]: with changedlocale(category="LC_TIME"): text_dates.append(date.strftime("\n - %a %d. %b kl. %H:%M")) @@ -524,7 +515,6 @@ def _generate_date_answer( ans.format(times=natlang_seq(show_times)).replace("dagur", "dagurinn") ) return (dict(answer=text_ans), text_ans, numbers_to_ordinal(voice_ans)) - print("UNFULFILLLED?", resource.is_unfulfilled) # No date selected, list available dates if resource.is_unfulfilled: if len(dates) > 0: @@ -740,7 +730,6 @@ def QTheaterHotWord(node: Node, params: QueryStateDict, result: Result) -> None: def QTheaterShowQuery(node: Node, params: QueryStateDict, result: Result) -> None: dsm: DialogueStateManager = Query.get_dsm(result) selected_show: str = result.show_name - print("SEARCHING FOR A SHOW:", selected_show) resource: ListResource = cast(ListResource, dsm.get_resource("Show")) show_exists = False for show in _SHOWS: @@ -1036,11 +1025,8 @@ def QTheaterShowRow(node: Node, params: QueryStateDict, result: Result) -> None: text_ans = ans.format(seats=seats) voice_ans = ans.format(seats=number_to_text(seats)) dsm.set_answer((dict(answer=text_ans), text_ans, voice_ans)) - print("Available rows: ", available_rows) - print("Result number: ", result.number) # Available row chosen if result.number in available_rows: - print("Chosen row: ", result.number) resource.data = [result.number] dsm.set_resource_state(resource.name, ResourceState.FULFILLED) # Incorrect row chosen @@ -1158,7 +1144,6 @@ def QTheaterDateOptions(node: Node, params: QueryStateDict, result: Result) -> N index = extras["page_index"] for show in _SHOWS: if show["title"].lower() == title.lower(): - print("itering dates") for date in show["date"]: with changedlocale(category="LC_TIME"): text_dates.append(date.strftime("\n - %a %d. %b kl. %H:%M")) @@ -1171,7 +1156,6 @@ def QTheaterDateOptions(node: Node, params: QueryStateDict, result: Result) -> N if date_number == 2 else "Næstu þrjár dagsetningarnar eru:" ) - print("blaaaaa") if index == 0: start_string = start_string.replace("Næstu", "Fyrstu", 1) if len(dates) < 3: @@ -1327,9 +1311,7 @@ def QTheaterYes(node: Node, params: QueryStateDict, result: Result): "ShowSeatNumber", ) ) and current_resource.is_fulfilled: - print("CONFIRIMNGG") dsm.set_resource_state(current_resource.name, ResourceState.CONFIRMED) - print("CASCADED STATE") if isinstance(current_resource, WrapperResource): for rname in current_resource.requires: dsm.get_resource(rname).state = ResourceState.CONFIRMED diff --git a/query.py b/query.py index 8df0ae35..12090eea 100755 --- a/query.py +++ b/query.py @@ -53,7 +53,11 @@ import random from copy import deepcopy from collections import defaultdict -from queries.dialogue import DialogueStateManager as DSM, ResourceNotFoundError +from queries.dialogue import ( + DialogueStateManager as DSM, + ResourceNotFoundError, + DialogueDataDict, +) from settings import Settings @@ -299,65 +303,16 @@ def process_queries( # But this processor is not interested in any of the nonterminals # in this query's parse forest: don't waste more cycles on it return False - dialogue_name: Optional[str] = getattr(processor, "DIALOGUE_NAME", None) - print("IN DIALOGUE MODULE:", dialogue_name) - if dialogue_name: - # This processor uses dialogue functionality, - # check if it wants to process this query - hotword_nonterminals: Set[str] = getattr( - processor, "HOTWORD_NONTERMINALS", set() - ) - print("HOTWORD NONTERMINALS:", hotword_nonterminals) - print("QUERY NONTERMINALS:", self.query_nonterminals) - # Dialogue modules must have at least one - # hotword nonterminal for activating dialogue - assert isinstance(hotword_nonterminals, set) - assert len(hotword_nonterminals) > 0 - dialogue_data = cast( - Optional[str], query.all_dialogue_data.get(dialogue_name) - ) - print( - "CHECKING WHETHER PROCESSOR IS INTERESTED IN DIALOGUE:", - not ( - self.query_nonterminals.isdisjoint(hotword_nonterminals) - and dialogue_data is None - ), - ) - # Fetch saved dialogue state - if ( - self.query_nonterminals.isdisjoint(hotword_nonterminals) - and dialogue_data is None - ): - print("NO DIALOGUE DATA AND NO HOTWORDS MATCHED") - # Query's parse forest doesn't contain hotwords - # and has no saved data for this dialogue, - # not interested in this query - return False - print("STARTING DSM") - # Query matches this dialogue processor, start DialogueStateManager - query.start_dsm(dialogue_name, dialogue_data) - print("FINISHED SETTING UP DSM") - if query.dsm.timed_out: - print("TIMED OUT") - timed_out_ans = query.dsm.get_resource("Final").prompts["timed_out"] - ans = (dict(answer=timed_out_ans), timed_out_ans, timed_out_ans) - query.set_answer(*ans) - query.update_dialogue_data() - return True with self.context(session, processor, query=query) as state: for query_tree in self._query_trees: # Is the processor interested in the root nonterminal # of this query tree? if query_tree.string_self() in processor_query_types: # Yes: hand the query tree over to the processor - try: - self.process_sentence(state, query_tree) - except ResourceNotFoundError: - pass + self.process_sentence(state, query_tree) if query.has_answer(): # The processor successfully answered the query: We're done # Also save any changes to dialogue data, if needed - query.update_dialogue_data() return True return False @@ -453,7 +408,9 @@ def __init__( # This should be a dict that can be represented in JSON self._context: Optional[ContextDict] = None # Dialogue state manager and dialogue data, used for dialogue modules - self._dsm: Optional[DSM] = None + self._dsm: DSM = DSM( + cast(DialogueDataDict, self.client_data(DSM.DIALOGUE_DATA_KEY)) or dict() + ) self._all_dialogue_data: Optional[ClientDataDict] = None def _preprocess_query_string(self, q: str) -> str: @@ -477,6 +434,7 @@ def init_class(cls) -> None: all_procs: List[ModuleType] = [] tree_procs: List[Tuple[int, ModuleType]] = [] text_procs: List[Tuple[int, Callable[["Query"], bool]]] = [] + dialogue_procs: List[Tuple[int, ModuleType]] = [] # Load the query processor modules found in the # queries directory. The modules can be tree and/or text processors, # and we sort them into two lists, accordingly. @@ -491,6 +449,10 @@ def init_class(cls) -> None: # This is a tree processor is_proc = True tree_procs.append((priority, m)) + if getattr(m, "HANDLE_DIALOGUE", False): + # This is a dialogue processor + is_proc = True + dialogue_procs.append((priority, m)) handle_plain_text = getattr(m, "handle_plain_text", None) if handle_plain_text is not None: # This is a text processor: @@ -507,11 +469,14 @@ def init_class(cls) -> None: # so that the higher-priority ones get invoked bfore the lower-priority ones cls._tree_processors = [t[1] for t in sorted(tree_procs, key=lambda x: -x[0])] cls._text_processors = [t[1] for t in sorted(text_procs, key=lambda x: -x[0])] + cls._dialogue_processors = [ + t[1] for t in sorted(dialogue_procs, key=lambda x: -x[0]) + ] # Obtain query grammar fragments from the tree processors grammar_fragments: List[str] = [] - for processor in cls._tree_processors: - # Check whether this tree processor supplies a query grammar fragment + for processor in cls._dialogue_processors + cls._tree_processors: + # Check whether this dialogue/tree processor supplies a query grammar fragment fragment = getattr(processor, "GRAMMAR", None) if fragment and isinstance(fragment, str): # Looks legit: add it to our list @@ -640,11 +605,10 @@ def parse(self, result: ResponseDict) -> bool: print("Query is: '{0}'".format(actual_q)) banned_nonterminals: Set[str] = set() - for t in Query._tree_processors: + for t in Query._dialogue_processors: f = getattr(t, "banned_nonterminals", None) if f is not None: banned_nonterminals.update(f(self)) - print("QUERY BANNED NON TERMINALS: ", banned_nonterminals) try: parse_result, trees = Query._parse( toklist, banned_nonterminals=banned_nonterminals @@ -693,6 +657,97 @@ def execute_from_plain_text(self) -> bool: handle_plain_text(self) for handle_plain_text in self._text_processors ) + def execute_from_dialogue(self) -> bool: + """Execute the query or queries contained in the previously parsed tree; + return True if successful""" + if self._tree is None: + self.set_error("E_QUERY_NOT_PARSED") + return False + # Try each dialogue processor in turn, in priority order (highest priority first) + for processor in self._dialogue_processors: + self._error = None + self._qtype = None + # Process the dialogue, which has only one sentence, but may + # have multiple matching query nonterminals + # (children of Query in the grammar) + try: + # Note that passing query=self here means that the + # "query" field of the TreeStateDict is populated, + # turning it into a QueryStateDict. + processor_query_types: Set[str] = getattr( + processor, "QUERY_NONTERMINALS", set() + ) + if self._tree.query_nonterminals.isdisjoint(processor_query_types): + print("!!!!!No query trees to process in this processor!!!!!") + # But this processor is not interested in any of the nonterminals + # in this query's parse forest: don't waste more cycles on it + continue + dialogue_name: Optional[str] = getattr(processor, "DIALOGUE_NAME", None) + if dialogue_name: + # This processor uses dialogue functionality, + # check if it wants to process this query + hotword_nonterminals: Set[str] = getattr( + processor, "HOTWORD_NONTERMINALS", set() + ) + # Dialogue modules must have at least one + # hotword nonterminal for activating dialogue + assert isinstance(hotword_nonterminals, set) + assert len(hotword_nonterminals) > 0 + dialogue_data = cast( + Optional[str], self.all_dialogue_data.get(dialogue_name) + ) + print( + "CHECKING WHETHER PROCESSOR IS INTERESTED IN DIALOGUE:", + not ( + self._tree.query_nonterminals.isdisjoint( + hotword_nonterminals + ) + and dialogue_data is None + ), + ) + # Fetch saved dialogue state + if ( + self._tree.query_nonterminals.isdisjoint(hotword_nonterminals) + and dialogue_data is None + ): + print("NO DIALOGUE DATA AND NO HOTWORDS MATCHED") + # Query's parse forest doesn't contain hotwords + # and has no saved data for this dialogue, + # not interested in this query + continue + # Query matches this dialogue processor, start DialogueStateManager + self.dsm.load_dialogue(dialogue_name) + if self.dsm.timed_out: + timed_out_ans = self.dsm.get_resource("Final").prompts[ + "timed_out" + ] + ans = (dict(answer=timed_out_ans), timed_out_ans, timed_out_ans) + self.set_answer(*ans) + self.update_dialogue_data() + return True + with self._tree.context(self._session, processor, query=self) as state: + for query_tree in self._tree._query_trees: + # Is the processor interested in the root nonterminal + # of this query tree? + if query_tree.string_self() in processor_query_types: + # Yes: hand the query tree over to the processor + try: + self._tree.process_sentence(state, query_tree) + except ResourceNotFoundError: + pass + if self.has_answer(): + # The processor successfully answered the query: We're done + # Also save any changes to dialogue data, if needed + self.update_dialogue_data() + return True + except Exception as e: + logging.error( + f"Exception in execute_from_dialogue('{processor.__name__}') " + f"for query '{self._query}': {repr(e)}" + ) + # No processor was able to answer the query + return False + def execute_from_tree(self) -> bool: """Execute the query or queries contained in the previously parsed tree; return True if successful""" @@ -703,7 +758,6 @@ def execute_from_tree(self) -> bool: for processor in self._tree_processors: self._error = None self._qtype = None - self._dsm = None # Process the tree, which has only one sentence, but may # have multiple matching query nonterminals # (children of Query in the grammar) @@ -711,12 +765,10 @@ def execute_from_tree(self) -> bool: # Note that passing query=self here means that the # "query" field of the TreeStateDict is populated, # turning it into a QueryStateDict. - print("TRYING PROCESSOR:", processor.__name__) if self._tree.process_queries(self, self._session, processor): # This processor found an answer, which is already stored # in the Query object: return True return True - print("FAILED PROCESSOR:", processor.__name__) except Exception as e: logging.error( f"Exception in execute_from_tree('{processor.__name__}') " @@ -925,10 +977,6 @@ def dsm(self) -> DSM: assert self._dsm is not None, "dsm property used in non-dialogue state" return self._dsm - def start_dsm(self, dialogue_name: str, dialogue_data: Optional[str]) -> None: - """Start a new DialogueStateManager instance""" - self._dsm = DSM(dialogue_name, dialogue_data) - @property def all_dialogue_data(self) -> ClientDataDict: if self._all_dialogue_data is None: @@ -1111,7 +1159,7 @@ def execute(self) -> ResponseDict: result["error"] = err result["valid"] = False return result - if not self.execute_from_tree(): + if not self.execute_from_dialogue() and not self.execute_from_tree(): # This is a query, but its execution failed for some reason: # return the error # if Settings.DEBUG: From 458ceca5a17210f23321326ecf3c4ae30a43eec5 Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Tue, 19 Jul 2022 15:24:39 +0000 Subject: [PATCH 228/371] hue scene bug fix, color temp grammar for tradfri and spotify device data error handling --- queries/grammars/iot_hue.grammar | 14 + queries/iot_connect.py | 1 + queries/iot_hue.py | 17 + queries/iot_spotify.py | 35 +- .../js/IoT_Embla/Philips_Hue/set_lights.js | 71 +++- queries/js/IoT_Embla/Postman/ikea.json | 318 ++++++++++++++++++ queries/js/IoT_Embla/philips_hue.html | 4 +- 7 files changed, 431 insertions(+), 29 deletions(-) create mode 100644 queries/js/IoT_Embla/Postman/ikea.json diff --git a/queries/grammars/iot_hue.grammar b/queries/grammars/iot_hue.grammar index cd2ff734..981297e2 100644 --- a/queries/grammars/iot_hue.grammar +++ b/queries/grammars/iot_hue.grammar @@ -188,10 +188,12 @@ QIoTLocationPrepositionSecondPart -> QIoTGroupName/fall -> # QIoTLightsBanwords/fall no/fall + | sérnafn/fall QIoTLightName/fall -> # QIoTLightsBanwords/fall no/fall + | sérnafn/fall QIoTColorName -> {color_names} @@ -200,6 +202,7 @@ QIoTSceneName -> # QIoTLightsBanwords/fall no | lo + | sérnafn/fall QIoTAnnadAndlag -> QIoTNewSetting/nf @@ -262,6 +265,7 @@ QIoTSpyrjaHuldu/fall -> # QIoTHuldaColor/fall QIoTHuldaBrightness/fall # | QIoTHuldaScene/fall + | QIoTHuldaTemp/fall # Do I need a "new light state" non-terminal? QIoTNewSetting/fall -> @@ -274,6 +278,10 @@ QIoTHuldaBrightness/fall -> QIoTMoreBrighterOrHigher/fall QIoTBrightnessWords/fall? | QIoTLessDarkerOrLower/fall QIoTBrightnessWords/fall? +QIoTHuldaTemp/fall -> + QIoTWarmer/fall + | QIoTCooler/fall + #Unsure about whether to include /fall after QIoTColorName QIoTNewColor/fall -> QIoTColorWord/fall QIoTColorName @@ -299,6 +307,12 @@ QIoTLessDarkerOrLower/fall -> | 'dimmur:lo'_mst/fall | 'lágur:lo'_mst/fall +QIoTWarmer/fall -> + 'hlýr:lo'_mst/fall + +QIoTCooler/fall -> + 'kaldur:lo'_mst/fall + QIoTBrightestOrDarkest/fall -> QIoTBrightest/fall | QIoTDarkest/fall diff --git a/queries/iot_connect.py b/queries/iot_connect.py index 756225b5..cd6da227 100644 --- a/queries/iot_connect.py +++ b/queries/iot_connect.py @@ -156,6 +156,7 @@ def sentence(state: QueryStateDict, result: Result) -> None: q.set_qtype(result.qtype) host = str(flask.request.host) + print("host :", host) client_id = str(q.client_id) if result.qtype == "connect_lights": diff --git a/queries/iot_hue.py b/queries/iot_hue.py index a9c32965..3117a2c9 100755 --- a/queries/iot_hue.py +++ b/queries/iot_hue.py @@ -34,6 +34,7 @@ # TODO: Fix scene issues # TODO: Turning on lights without using "turn on" # TODO: Add functionality for robot-like commands "ljós í eldhúsinu", "rauður í eldhúsinu" +# TODO: Heldur að 'gerðu ljósið kaldara' sé senan 'köld' from typing import Dict, Mapping, Optional, cast, FrozenSet from typing_extensions import TypedDict @@ -191,6 +192,22 @@ def QIoTIncreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None result["hue_obj"]["on"] = True +def QIoTCooler(node: Node, params: QueryStateDict, result: Result) -> None: + result.action = "decrease_colortemp" + if "hue_obj" not in result: + result["hue_obj"] = {"ct_inc": -30000} + else: + result["hue_obj"]["ct_inc"] = -30000 + + +def QIoTWarmer(node: Node, params: QueryStateDict, result: Result) -> None: + result.action = "increase_colortemp" + if "hue_obj" not in result: + result["hue_obj"] = {"ct_inc": 30000} + else: + result["hue_obj"]["ct_inc"] = 30000 + + def QIoTDecreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: result.action = "decrease_brightness" if "hue_obj" not in result: diff --git a/queries/iot_spotify.py b/queries/iot_spotify.py index 450476b0..4f0467aa 100644 --- a/queries/iot_spotify.py +++ b/queries/iot_spotify.py @@ -168,22 +168,27 @@ def sentence(state: QueryStateDict, result: Result) -> None: print("ARTIST NAME :", artist_name) print("RESTULT SONG NAME:", result.song_name) - print("RESTULT ARTIST NAME:", result.artist_name) + print("RESULT ARTIST NAME:", result.artist_name) device_data = q.client_data("spotify") - client_id = str(q.client_id) - spotify_client = SpotifyClient( - device_data, - client_id, - song_name=result.song_name, - artist_name=result.artist_name, - ) - song_url = spotify_client.get_song_by_artist() - response = spotify_client.play_song_on_device() - print("RESPONSE FROM SPOTIFY:", response) - if response is None: - q.set_url(song_url) - - answer = "Ég spilaði lagið" + if device_data is not None: + client_id = str(q.client_id) + spotify_client = SpotifyClient( + device_data, + client_id, + song_name=result.song_name, + artist_name=result.artist_name, + ) + song_url = spotify_client.get_song_by_artist() + response = spotify_client.play_song_on_device() + print("RESPONSE FROM SPOTIFY:", response) + if response is None: + q.set_url(song_url) + + answer = "Ég spilaði lagið" + else: + answer = "Það vantar að tengja Spotify aðgang." + q.set_answer(*gen_answer(answer)) + return # q.set_url( # "https://spotify.app.link/?product=open&%24full_url=https%3A%2F%2Fopen.spotify.com%2Ftrack%2F2BSyX4weGuITcvl5r2lLCC%3Fgo%3D1%26sp_cid%3D2a74d03dedb9fa4450d122ddebebcf9b%26fallback%3Dgetapp&feature=organic&_p=c31529c0980b7af1e11b90f9" # ) diff --git a/queries/js/IoT_Embla/Philips_Hue/set_lights.js b/queries/js/IoT_Embla/Philips_Hue/set_lights.js index 798607a0..73676005 100644 --- a/queries/js/IoT_Embla/Philips_Hue/set_lights.js +++ b/queries/js/IoT_Embla/Philips_Hue/set_lights.js @@ -1,8 +1,8 @@ "use strict"; // Constants to be used when setting lights from HTML -// var BRIDGE_IP = "192.168.1.68"; -// var USERNAME = "MQH9DVv1lhgaOKN67uVox4fWNc9iu3j5g7MmdDUr"; +var BRIDGE_IP = "192.168.1.68"; +var USERNAME = "BzdNyxr6mGSHVdQN86UeZP67qp5huJ2Q6TWyTzvz"; // TODO: Implement a hotfix for Ikea Tradfri bulbs, since it can only take one argument at a time @@ -50,19 +50,50 @@ function setLights(target, state) { // Send data to API let url = targetObject.url; - fetch(`http://${BRIDGE_IP}/api/${USERNAME}/${url}`, { - method: "PUT", - body: state, - }) - .then((resp) => resp.json()) - .then((obj) => { - console.log(obj); - }) - .catch((err) => { - console.log("an error occurred!"); + call_api(url, state); + let isTradfriBulb = check_if_if_ikea_bulb_in_group( + targetObject, + allLights + ); + console.log("isTradfriBulb:", isTradfriBulb); + if (isTradfriBulb) { + console.log("sending request again"); + const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); + sleep(450).then(() => { + call_api(url, state); }); + } }); } +// fetch(`http://${BRIDGE_IP}/api/${USERNAME}/${url}`, { +// method: "PUT", +// body: state, +// }) +// .then((resp) => resp.json()) +// .then((obj) => { +// console.log(obj); +// }) +// .catch((err) => { +// console.log("an error occurred!"); +// }); +// }); +// } + +function call_api(url, state) { + console.log("call api"); + fetch(`http://${BRIDGE_IP}/api/${USERNAME}/${url}`, { + method: "PUT", + body: state, + }) + .then((resp) => resp.json()) + .then((obj) => { + console.log(obj); + }) + .catch((err) => { + console.log("an error occurred!"); + }); + return; +} /** Finds a matching light or group and returns an object with the ID, name and url for the target * @param {String} target - the target to find the target e.g. "eldhús" @@ -84,6 +115,7 @@ function getTargetObject(target, allLights, allGroups) { } : { id: groupsResult.result.ID, + lights: groupsResult.result.info.lights, url: `groups/${groupsResult.result.ID}/action`, }; } else if (lightsResult != null && groupsResult == null) { @@ -96,6 +128,7 @@ function getTargetObject(target, allLights, allGroups) { // Found a match for a light group targetObject = { id: groupsResult.result.ID, + lights: groupsResult.result.info.lights, url: `groups/${groupsResult.result.ID}/action`, }; } else { @@ -142,6 +175,20 @@ function queryTestFromHTML() { } } +function check_if_if_ikea_bulb_in_group(groupsObject, all_lights) { + for (let key in groupsObject.lights) { + let lightID = groupsObject.lights[key]; + let light = all_lights[lightID]; + if ( + light.manufacturername.includes("IKEA") | + light.modelid.includes("TRADFRI") + ); + { + return true; + } + } +} + // /** Finds a matching light or group and returns an object with the ID, name and url for the target // * @param {String} target - the target to find the target e.g. "eldhús" // * @param {Object} allLights - an array of all lights from the API diff --git a/queries/js/IoT_Embla/Postman/ikea.json b/queries/js/IoT_Embla/Postman/ikea.json new file mode 100644 index 00000000..aaa32f82 --- /dev/null +++ b/queries/js/IoT_Embla/Postman/ikea.json @@ -0,0 +1,318 @@ +{ + "info": { + "_postman_id": "f0e8ea0e-501e-4b89-a45d-c99278219f2b", + "name": "ikea", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "name": "http://localhost:3500/test", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "type": "text", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"groupOperation\":{\n\t\t\"group\": \"stue loft\",\n\t\t\"operation\": {\n\t\t\t\"onOff\": true\n\t\t}\n\t}\n}" + }, + "url": { + "raw": "http://localhost:3500/test", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "3500", + "path": [ + "test" + ] + } + }, + "response": [] + }, + { + "name": "http://localhost:3500/devices", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "http://localhost:3500/devices", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "3500", + "path": [ + "devices" + ], + "query": [ + { + "key": "asdf", + "value": "asdf", + "disabled": true + } + ] + } + }, + "response": [] + }, + { + "name": "http://localhost:3500/devices/get-single-device", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\"deviceNameOrId\": \"Sov.loft.E27WS\"}" + }, + "url": { + "raw": "http://localhost:3500/devices/get-single-device", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "3500", + "path": [ + "devices", + "get-single-device" + ], + "query": [ + { + "key": "asdf", + "value": "asdf", + "disabled": true + } + ] + } + }, + "response": [] + }, + { + "name": "http://localhost:3500/devices/set-device", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"deviceNameOrId\": \"Stue.gulvlampe.E27WS\",\n\t\"action\": {\n\t\t\"name\": \"toggle\"\n\t}\n}" + }, + "url": { + "raw": "http://localhost:3500/devices/set-device", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "3500", + "path": [ + "devices", + "set-device" + ] + } + }, + "response": [] + }, + { + "name": "http://localhost:3500/rooms", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "http://localhost:3500/rooms", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "3500", + "path": [ + "rooms" + ] + } + }, + "response": [] + }, + { + "name": "http://localhost:3500/rooms/set-room", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"room\": {\n\t\t\"name\": \"STUE LOFT\",\n\t\t\"scene\": \"Lidt lys\"\n\t}\n}" + }, + "url": { + "raw": "http://localhost:3500/rooms/set-room", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "3500", + "path": [ + "rooms", + "set-room" + ] + } + }, + "response": [] + }, + { + "name": "http://localhost:3500/rooms/get-single-room", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"room\": {\n\t\t\"name\": \"STUE LOFT\"\n\t}\n}" + }, + "url": { + "raw": "http://localhost:3500/rooms/get-single-room", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "3500", + "path": [ + "rooms", + "get-single-room" + ] + } + }, + "response": [] + }, + { + "name": "http://localhost:3500/masterswitch", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"confirmation\": true\n}" + }, + "url": { + "raw": "http://localhost:3500/masterswitch", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "3500", + "path": [ + "masterswitch" + ] + } + }, + "response": [] + }, + { + "name": "http://localhost:3500/masterswitch/all-on", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "name": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"confirmation\": true\n}" + }, + "url": { + "raw": "http://localhost:3500/masterswitch/all-on", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "3500", + "path": [ + "masterswitch", + "all-on" + ] + } + }, + "response": [] + }, + { + "name": "http://localhost:3500/devices/get-battery-life", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "" + }, + "url": { + "raw": "http://localhost:3500/devices/get-battery-life", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "3500", + "path": [ + "devices", + "get-battery-life" + ] + } + }, + "response": [] + } + ] +} \ No newline at end of file diff --git a/queries/js/IoT_Embla/philips_hue.html b/queries/js/IoT_Embla/philips_hue.html index 5fe5e294..7b751ad4 100644 --- a/queries/js/IoT_Embla/philips_hue.html +++ b/queries/js/IoT_Embla/philips_hue.html @@ -60,11 +60,11 @@

Testing


- + - +
From d2e22d774789754cf4babab0fa3d5d078ccd87a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Tue, 19 Jul 2022 15:55:06 +0000 Subject: [PATCH 229/371] First pizza convo implementation, dynamic resources for pizzas added --- queries/dialogue.py | 138 +++++++++++++++++++++--- queries/dialogues/pizza.toml | 75 +++++++++++++ queries/fruitseller.py | 4 + queries/pizza.py | 203 +++++++++++++++++++++++++++++++++++ queries/resources.py | 2 + queries/theater.py | 4 + 6 files changed, 411 insertions(+), 15 deletions(-) create mode 100644 queries/dialogues/pizza.toml create mode 100644 queries/pizza.py diff --git a/queries/dialogue.py b/queries/dialogue.py index c84e54f7..df0d0076 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -76,12 +76,14 @@ class DialogueTOMLStructure(TypedDict): """Structure of a dialogue TOML file.""" resources: List[Dict[str, Any]] + dynamic_resources: List[Dict[str, Any]] expiration_time: NotRequired[int] # Keys for accessing saved client data for dialogues # (must match typed dict attributes below) _RESOURCES_KEY = "resources" +_DYNAMIC_RESOURCES_KEY = "dynamic_resources" _MODIFIED_KEY = "modified" _EXTRAS_KEY = "extras" _EXPIRATION_TIME_KEY = "expiration_time" @@ -111,11 +113,13 @@ def __new__(cls, dialogue_data: DialogueDataDict) -> "DialogueStateManager": if cls._instance is None: cls._instance = super(DialogueStateManager, cls).__new__(cls) # Put any initialization here. + print(">>>>>>>>Dialogue data in NEW:", dialogue_data) cls._dialogue_data: DialogueDataDict = dialogue_data return cls._instance def __init__(self, dialogue_data: DialogueDataDict) -> None: self._dialogue_data: DialogueDataDict = dialogue_data + print(">>>>>>>>Dialogue data in INIT:", dialogue_data) def load_dialogue(self, dialogue_name: str): self._dialogue_name: str = dialogue_name @@ -158,6 +162,8 @@ def setup_resources(self) -> None: """ Load dialogue resources from TOML file and update their state from database data. """ + print("Setting up resources") + # TODO: Only initialize if not hotword activated # Fetch empty resources from TOML self._initialize_resources(self._dialogue_name) if self._saved_state: @@ -178,7 +184,9 @@ def setup_resources(self) -> None: if self._saved_state and _EXTRAS_KEY in self._saved_state: self._extras = self._saved_state.get(_EXTRAS_KEY) or self._extras # Create resource dependency relationship graph + print("Initializing resource graph") self._initialize_resource_graph() + print("Finished setting up resources") def _initialize_resource_graph(self) -> None: """ @@ -187,40 +195,131 @@ def _initialize_resource_graph(self) -> None: to what each resource requires. """ for resource in self._resources.values(): + print("Initializing resource graph for", resource.name) if resource.order_index == 0: self._initial_resource = resource self._resource_graph[resource] = {"children": [], "parents": []} - + print("Children/parents set up, starting to fill:") for resource in self._resources.values(): + print("In outer for loop") for req in resource.requires: + print("Appending parents and children for resource", req) self._resource_graph[self._resources[req]]["parents"].append(resource) self._resource_graph[resource]["children"].append(self._resources[req]) + print("Done appending parents and children for resource", req) + print("Resource graph: ", self._resource_graph) def _initialize_resources(self, filename: str) -> None: """ Loads dialogue structure from TOML file and fills self._resources with empty Resource instances. """ + if self._saved_state: + print("IN IFFFFFF with resources: ", self._saved_state[_RESOURCES_KEY]) + self._resources = {} + for rname, resource in self._saved_state[_RESOURCES_KEY].items(): + print("Adding resource", rname) + self._resources[rname] = resource + self._expiration_time = self._saved_state.get( + "expiration_time", _DEFAULT_EXPIRATION_TIME + ) + else: + basepath, _ = os.path.split(os.path.realpath(__file__)) + fpath = os.path.join(basepath, _TOML_FOLDER_NAME, filename + ".toml") + with open(fpath, mode="r") as file: + f = file.read() + # Read TOML file containing a list of resources for the dialogue + obj: DialogueTOMLStructure = tomllib.loads(f) # type: ignore + assert _RESOURCES_KEY in obj, f"No resources found in TOML file {f}" + # Create resource instances from TOML data and return as a dict + for i, resource in enumerate(obj[_RESOURCES_KEY]): + assert "name" in resource, f"Name missing for resource {i+1}" + if "type" not in resource: + resource["type"] = "Resource" + # Create instances of Resource classes (and its subclasses) + self._resources[resource["name"]] = RESOURCE_MAP[resource["type"]]( + **resource, order_index=i + ) + self._expiration_time = obj.get("expiration_time", _DEFAULT_EXPIRATION_TIME) + + def add_dynamic_resource(self, resource_name: str, parent_name: str) -> None: + """ + Adds a dynamic resource to the dialogue from TOML file and + updates the requirements of it's parents. + """ + # TODO: should dynamic resources be loaded from TOML at initialization? + # Loading dynamic resources from TOML basepath, _ = os.path.split(os.path.realpath(__file__)) - fpath = os.path.join(basepath, _TOML_FOLDER_NAME, filename + ".toml") + fpath = os.path.join(basepath, _TOML_FOLDER_NAME, self._dialogue_name + ".toml") with open(fpath, mode="r") as file: f = file.read() - # Read TOML file containing a list of resources for the dialogue + obj: DialogueTOMLStructure = tomllib.loads(f) # type: ignore - assert _RESOURCES_KEY in obj, f"No resources found in TOML file {f}" - # Create resource instances from TOML data and return as a dict - for i, resource in enumerate(obj[_RESOURCES_KEY]): - assert "name" in resource, f"Name missing for resource {i+1}" - if "type" not in resource: - resource["type"] = "Resource" - # Create instances of Resource classes (and its subclasses) - self._resources[resource["name"]] = RESOURCE_MAP[resource["type"]]( - **resource, order_index=i + assert ( + _DYNAMIC_RESOURCES_KEY in obj + ), f"No dynamic resources found in TOML file {f}" + parent_resource: Resource = self.get_resource(parent_name) + order_index: int = parent_resource.order_index + dynamic_resources: Dict[str, Resource] = {} + # Index of dynamic resource + dynamic_resource_index = ( + len( + [ + i + for i in self._resources + if self.get_resource(i).name.startswith(resource_name + "_") + ] ) - self._expiration_time = obj.get("expiration_time", _DEFAULT_EXPIRATION_TIME) + + 1 + ) + # Adding all dynamic resources to a list + for dynamic_resource in obj[_DYNAMIC_RESOURCES_KEY]: + assert "name" in dynamic_resource, f"Name missing for dynamic resource" + if "type" not in dynamic_resource: + dynamic_resource["type"] = "Resource" + # Updating required resources to have indexed name + dynamic_resource["requires"] = [ + "{res}_{index}".format(res=res, index=dynamic_resource_index) + for res in dynamic_resource.get("requires", []) + ] + # Updating dynamic resource name to have indexed name + dynamic_resource["name"] = "{name}_{index}".format( + name=dynamic_resource["name"], index=dynamic_resource_index + ) + # Adding dynamic resource to list + dynamic_resources[dynamic_resource["name"]] = RESOURCE_MAP[ + dynamic_resource["type"] + ]( + **dynamic_resource, + order_index=order_index, + ) + # Indexed resource name of the dynamic resource + indexed_resource_name = "{name}_{index}".format( + name=resource_name, index=dynamic_resource_index + ) + resource: Resource = dynamic_resources[indexed_resource_name] + # Appending resource to required list of parent resource + parent_resource.requires.append(indexed_resource_name) + + def _add_child_resource(resource: Resource) -> None: + """ + Recursively adds a child resource to the resources list + """ + print("Start of add child resource") + self._resources[resource.name] = resource + for req in resource.requires: + _add_child_resource(dynamic_resources[req]) + + _add_child_resource(resource) + # Initialize the resource graph again with the update resources + # for i, (rname, resource) in enumerate(self._resources.items()): + # self._resources[rname].order_index = i + self._initialize_resource_graph() + self._find_current_resource() def hotword_activated(self) -> None: self._in_this_dialogue = True + print("In hotword activated") self.setup_resources() def pause_dialogue(self) -> None: @@ -276,8 +375,10 @@ def get_answer( raise ValueError("No answer for cancelled dialogue") return self._answer_tuple - if self._current_resource.name in self._answering_functions: - ans = self._answering_functions[self._current_resource.name]( + resource_name = self._current_resource.name.partition("_")[0] + if resource_name in self._answering_functions: + print("GENERATING ANSWER FOR ", resource_name) + ans = self._answering_functions[resource_name]( self._current_resource, self, result ) return ans @@ -338,6 +439,12 @@ def _find_current_resource(self) -> Resource: Finds the current resource in the resource graph. """ curr_res: Resource = self._initial_resource + # If the initial parent is a wrapper, the current resource should be that parent + initial_parents = self._resource_graph[curr_res]["parents"] + if len(initial_parents) == 1 and isinstance( + initial_parents[0], WrapperResource + ): + curr_res = initial_parents[0] while curr_res.is_confirmed: for parent in self._resource_graph[curr_res]["parents"]: curr_res = parent @@ -360,6 +467,7 @@ def serialize_data(self) -> Dict[str, Optional[str]]: self.finish_dialogue() ds_json: Optional[str] = None if not self._finished and not self._timed_out: + print("!!!!!!!!!!!!!!!Serializing data! with resources: ", self._resources) ds_json = json.dumps( { _RESOURCES_KEY: self._resources, diff --git a/queries/dialogues/pizza.toml b/queries/dialogues/pizza.toml new file mode 100644 index 00000000..a25ed908 --- /dev/null +++ b/queries/dialogues/pizza.toml @@ -0,0 +1,75 @@ +# [[resources]] +# name = "PizzaCount" +# type = "NumberResource" + +# [[resources]] +# name = "Pizzas" +# type = "WrapperResource" +# requires = ["PizzaCount"] +# prompts.initial = "Hvað má bjóða þér?" + +# [[resources]] +# name = "Sides" +# type = "ListResource" + +# [[resources]] +# name = "Sauces" +# type = "ListResource" + +# [[resources]] +# name = "Drinks" +# type = "ListResource" + +[[resources]] +name = "PizzaOrder" +type = "WrapperResource" +#requires = ["Pizzas"] #, "Sides", "Sauces", "Drinks"] +prompts.initial = "Hvað má bjóða þér?" + +[[resources]] +name = "Final" +type = "FinalResource" +requires = ["PizzaOrder"] +prompts.final = "Pizzupöntunin þín er móttekin." +prompts.cancelled = "Móttekið, hætti við pizzu pöntunina." +prompts.timed_out = "Pizzupöntunin þín rann út á tíma. Vinsamlegast byrjaðu aftur." + +# Dynamic resources that get created when a user orders a pizza +[[dynamic_resources]] +name = "Pizza" +type = "WrapperResource" +requires = ["Type", "Size", "Crust"] +prompts.initial = "Hvað má bjóða þér á pizzuna?" + +[[dynamic_resources]] +name = "Type" +type = "OrResource" +requires = ["Toppings", "FromMenu"] #, "Split"] + +[[dynamic_resources]] +name = "Toppings" +type = "ListResource" + +[[dynamic_resources]] +name = "FromMenu" + +[[dynamic_resources]] +name = "Size" + +[[dynamic_resource]] +name = "Split" +type = "WrapperResource" +requires = ["Side1", "Side2"] + +[[dynamic_resource]] +name = "Side1" +type = "OrResource" +requires = ["Toppings", "FromMenu"] + +[[dynamic_resource]] +name = "Side2" +type = "OrResource" +requires = ["Toppings", "FromMenu"] + +[[dynamic_resources]] +name = "Crust" diff --git a/queries/fruitseller.py b/queries/fruitseller.py index 8dd58d0a..c37f9417 100644 --- a/queries/fruitseller.py +++ b/queries/fruitseller.py @@ -156,6 +156,10 @@ def banned_nonterminals(q: Query) -> Set[str]: + """ + Returns a set of nonterminals that are not + allowed due to the state of the dialogue + """ banned_nonterminals: set[str] = set() if q.dsm.dialogue_name != DIALOGUE_NAME: banned_nonterminals.add("QFruitSellerQuery") diff --git a/queries/pizza.py b/queries/pizza.py new file mode 100644 index 00000000..4cc21d84 --- /dev/null +++ b/queries/pizza.py @@ -0,0 +1,203 @@ +""" + + Greynir: Natural language processing for Icelandic + + Randomness query response module + + Copyright (C) 2022 Miðeind ehf. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see http://www.gnu.org/licenses/. + + This query module handles dialogue related to ordering pizza. +""" +from typing import Any, Dict, List, Optional, Set, Tuple, cast +from typing_extensions import TypedDict +import json +import logging +import random +import datetime + +from settings import changedlocale +from query import Query, QueryStateDict +from tree import Result, Node, TerminalNode +from queries import AnswerTuple, gen_answer, natlang_seq, parse_num, time_period_desc +from queries.num import number_to_text, numbers_to_ordinal, numbers_to_text +from queries.resources import ( + DateResource, + FinalResource, + ListResource, + NumberResource, + Resource, + ResourceState, + TimeResource, + WrapperResource, +) +from queries.dialogue import ( + AnsweringFunctionMap, + DialogueStateManager, +) + +_DIALOGUE_NAME = "pizza" +_PIZZA_QTYPE = "pizza" +_START_DIALOGUE_QTYPE = "pizza_start" + +TOPIC_LEMMAS = ["pizza", "pitsa"] + + +def help_text(lemma: str) -> str: + """Help text to return when query.py is unable to parse a query but + one of the above lemmas is found in it""" + return "Ég skil þig ef þú segir til dæmis: {0}.".format( + random.choice(("Ég vil panta pizzu",)) + ) + + +# This module wants to handle dialogue parse trees for queries +HANDLE_DIALOGUE = True + +# This module involves dialogue functionality +DIALOGUE_NAME = "pizza" +HOTWORD_NONTERMINALS = {"QPizzaHotWord"} + +# The grammar nonterminals this module wants to handle +QUERY_NONTERMINALS = {"QPizza"}.union(HOTWORD_NONTERMINALS) + +# The context-free grammar for the queries recognized by this plug-in module +GRAMMAR = """ + +Query → + QPizzaHotWord | QPizza + +QPizza → QPizzaQuery + +QPizzaQuery → + QPizzaDialogue '?'? + +QPizzaHotWord → + QPizzaNames + | QPizzaEgVil QPizzaKaupaFaraFaPanta QPizzaNames + +QPizzaNames → + 'pizza' + | 'pitsa' + | "pitsur" + | "pizzur" + +QPizzaKaupaFaraFaPanta → + "kaupa" "mér"? + | "fá" "mér"? + | "panta" "mér"? + +QPizzaDialogue → + QPizzaOrder + +QPizzaEgVil → + "ég"? "vil" + | "ég" "vill" + | "mig" "langar" "að" + | "mig" "langar" "í" + +QPizzaOrder → + QPizzaEgVil QPizzaKaupaFaraFaPanta QPizzaNum QPizzaNames + +QPizzaNum → + # to is a declinable number word ('tveir/tvo/tveim/tveggja') + # töl is an undeclinable number word ('sautján') + # tala is a number ('17') + to | töl | tala + +""" + + +def banned_nonterminals(query: str) -> Set[str]: + """ + Returns a set of nonterminals that are not + allowed due to the state of the dialogue + """ + # TODO: Implement this + return set() + + +def _generate_order_answer( + resource: WrapperResource, dsm: DialogueStateManager, result: Result +) -> Optional[AnswerTuple]: + return gen_answer(resource.prompts["initial"]) + + +def _generate_pizza_answer( + resource: WrapperResource, dsm: DialogueStateManager, result: Result +) -> Optional[AnswerTuple]: + return gen_answer(resource.prompts["initial"]) + + +def QPizzaDialogue(node: Node, params: QueryStateDict, result: Result) -> None: + if "qtype" not in result: + result.qtype = _PIZZA_QTYPE + + +def QPizzaHotWord(node: Node, params: QueryStateDict, result: Result) -> None: + result.qtype = _START_DIALOGUE_QTYPE + print("ACTIVATING PIZZA MODULE") + Query.get_dsm(result).hotword_activated() + + +def QPizzaOrder(node: Node, params: QueryStateDict, result: Result) -> None: + dsm: DialogueStateManager = Query.get_dsm(result) + # resource = dsm.get_resource("PizzaCount") + number: int = result.get("number", 1) + for _ in range(number): + print("CCCCCCAAAAALLLLLLLLLLLLLL") + dsm.add_dynamic_resource("Pizza", "PizzaOrder") + print("Pizza Count: ", number) + + +def QPizzaNum(node: Node, params: QueryStateDict, result: Result) -> None: + number: int = int(parse_num(node, result._nominative)) + if "numbers" not in result: + result["numbers"] = [] + result.numbers.append(number) + result.number = number + + +_ANSWERING_FUNCTIONS: AnsweringFunctionMap = { + "PizzaOrder": _generate_order_answer, + "Pizza": _generate_pizza_answer, +} + + +def sentence(state: QueryStateDict, result: Result) -> None: + """Called when sentence processing is complete""" + q: Query = state["query"] + dsm: DialogueStateManager = q.dsm + if dsm.not_in_dialogue(): + q.set_error("E_QUERY_NOT_UNDERSTOOD") + return + + try: + print("A") + ans: Optional[AnswerTuple] = dsm.get_answer(_ANSWERING_FUNCTIONS, result) + if "pizza_options" not in result: + q.query_is_command() + print("D", ans) + print("Current resource: ", dsm.current_resource) + if not ans: + print("No answer generated") + q.set_error("E_QUERY_NOT_UNDERSTOOD") + return + + q.set_qtype(result.qtype) + q.set_answer(*ans) + except Exception as e: + logging.warning("Exception while processing random query: {0}".format(e)) + q.set_error("E_EXCEPTION: {0}".format(e)) + raise diff --git a/queries/resources.py b/queries/resources.py index 6c50ab47..5abe4912 100644 --- a/queries/resources.py +++ b/queries/resources.py @@ -65,6 +65,8 @@ class Resource: cascade_state: bool = False # Used for comparing states (which one is earlier/later in the dialogue) order_index: int = 0 + # Extra variables to be used for specific variables + extras: Dict[str, Any] = field(default_factory=dict) @property def is_unfulfilled(self) -> bool: diff --git a/queries/theater.py b/queries/theater.py index 37534473..4164eab4 100644 --- a/queries/theater.py +++ b/queries/theater.py @@ -302,6 +302,10 @@ def help_text(lemma: str) -> str: def banned_nonterminals(q: Query) -> Set[str]: + """ + Returns a set of nonterminals that are not + allowed due to the state of the dialogue + """ banned_nonterminals: set[str] = set() if q.dsm.dialogue_name != DIALOGUE_NAME: banned_nonterminals.add("QTheaterQuery") From 0d95fab0d18861611de307cfeb8b732413ac09f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Wed, 20 Jul 2022 09:59:22 +0000 Subject: [PATCH 230/371] Start of answering func for pizza --- queries/dialogue.py | 2 -- queries/dialogues/pizza.toml | 2 ++ queries/pizza.py | 32 +++++++++++++++++++++----------- 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index df0d0076..c05ef663 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -312,8 +312,6 @@ def _add_child_resource(resource: Resource) -> None: _add_child_resource(resource) # Initialize the resource graph again with the update resources - # for i, (rname, resource) in enumerate(self._resources.items()): - # self._resources[rname].order_index = i self._initialize_resource_graph() self._find_current_resource() diff --git a/queries/dialogues/pizza.toml b/queries/dialogues/pizza.toml index a25ed908..3760c61e 100644 --- a/queries/dialogues/pizza.toml +++ b/queries/dialogues/pizza.toml @@ -40,6 +40,8 @@ name = "Pizza" type = "WrapperResource" requires = ["Type", "Size", "Crust"] prompts.initial = "Hvað má bjóða þér á pizzuna?" +prompts.size = "Hvaða stærð af pizzu viltu fá?" +prompts.crust = "Hvernig botn viltu?" [[dynamic_resources]] name = "Type" diff --git a/queries/pizza.py b/queries/pizza.py index 4cc21d84..cccc9475 100644 --- a/queries/pizza.py +++ b/queries/pizza.py @@ -20,26 +20,20 @@ This query module handles dialogue related to ordering pizza. """ -from typing import Any, Dict, List, Optional, Set, Tuple, cast -from typing_extensions import TypedDict -import json +from typing import Optional, Set, cast import logging import random -import datetime -from settings import changedlocale from query import Query, QueryStateDict -from tree import Result, Node, TerminalNode -from queries import AnswerTuple, gen_answer, natlang_seq, parse_num, time_period_desc +from tree import Result, Node +from queries import AnswerTuple, gen_answer, natlang_seq, parse_num from queries.num import number_to_text, numbers_to_ordinal, numbers_to_text from queries.resources import ( - DateResource, FinalResource, ListResource, NumberResource, + OrResource, Resource, - ResourceState, - TimeResource, WrapperResource, ) from queries.dialogue import ( @@ -137,7 +131,23 @@ def _generate_order_answer( def _generate_pizza_answer( resource: WrapperResource, dsm: DialogueStateManager, result: Result ) -> Optional[AnswerTuple]: - return gen_answer(resource.prompts["initial"]) + (_, _, index) = resource.name.partition("_") + type_resource: OrResource = cast( + OrResource, dsm.get_resource("Type_{}".format(index)) + ) + size_resource: Resource = dsm.get_resource("Size_{}".format(index)) + crust_resource: Resource = dsm.get_resource("Crust_{}".format(index)) + if resource.is_unfulfilled: + return gen_answer(resource.prompts["initial"]) + if resource.is_partially_fulfilled: + if type_resource.is_confirmed and size_resource.is_unfulfilled: + return gen_answer(resource.prompts["size"]) + if ( + type_resource.is_confirmed + and size_resource.is_confirmed + and crust_resource.is_unfulfilled + ): + return gen_answer(resource.prompts["crust"]) def QPizzaDialogue(node: Node, params: QueryStateDict, result: Result) -> None: From d5e623f7230e44b283138b8f37fd36c55310596d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Wed, 20 Jul 2022 11:25:52 +0000 Subject: [PATCH 231/371] Changed toml structure, made Sides, Sauces and Drinks be dynamic_resources --- queries/dialogues/pizza.toml | 52 ++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/queries/dialogues/pizza.toml b/queries/dialogues/pizza.toml index 3760c61e..03d72add 100644 --- a/queries/dialogues/pizza.toml +++ b/queries/dialogues/pizza.toml @@ -1,46 +1,28 @@ -# [[resources]] -# name = "PizzaCount" -# type = "NumberResource" - -# [[resources]] -# name = "Pizzas" -# type = "WrapperResource" -# requires = ["PizzaCount"] -# prompts.initial = "Hvað má bjóða þér?" - -# [[resources]] -# name = "Sides" -# type = "ListResource" - -# [[resources]] -# name = "Sauces" -# type = "ListResource" - -# [[resources]] -# name = "Drinks" -# type = "ListResource" - [[resources]] name = "PizzaOrder" type = "WrapperResource" #requires = ["Pizzas"] #, "Sides", "Sauces", "Drinks"] prompts.initial = "Hvað má bjóða þér?" +# [[resources]] +# name = "PickupDelivery" +# type = "OrResource" + [[resources]] name = "Final" type = "FinalResource" requires = ["PizzaOrder"] -prompts.final = "Pizzupöntunin þín er móttekin." +prompts.final = "Pítsupöntunin þín er móttekin." prompts.cancelled = "Móttekið, hætti við pizzu pöntunina." -prompts.timed_out = "Pizzupöntunin þín rann út á tíma. Vinsamlegast byrjaðu aftur." +prompts.timed_out = "Pítsupöntunin þín rann út á tíma. Vinsamlegast byrjaðu aftur." # Dynamic resources that get created when a user orders a pizza [[dynamic_resources]] name = "Pizza" type = "WrapperResource" requires = ["Type", "Size", "Crust"] -prompts.initial = "Hvað má bjóða þér á pizzuna?" -prompts.size = "Hvaða stærð af pizzu viltu fá?" +prompts.initial = "Hvernig pítsu viltu fá?" +prompts.size = "Hvaða stærð af pítsu viltu fá?" prompts.crust = "Hvernig botn viltu?" [[dynamic_resources]] @@ -75,3 +57,21 @@ requires = ["Toppings", "FromMenu"] [[dynamic_resources]] name = "Crust" + +# [[dynamic_resources]] +# name = "Sides" +# type = "ListResource" + +# [[dynamic_resources]] +# name = "Sauces" +# type = "ListResource" + +# [[dynamic_resources]] +# name = "Drinks" +# type = "ListResource" + +# [[dynamic_resources]] +# name = "Pickup" + +# [[dynamic_resources]] +# name = "Delivery" From 2fffbb0ddb87e651ed4d6af072be7c2b43fea9ef Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Wed, 20 Jul 2022 11:27:48 +0000 Subject: [PATCH 232/371] pizza grammar start --- queries/grammars/pizza.grammar | 81 ++++++++++++++++++++++++++++++++++ queries/pizza.py | 79 +++++++++++++++++++++++++-------- 2 files changed, 141 insertions(+), 19 deletions(-) create mode 100644 queries/grammars/pizza.grammar diff --git a/queries/grammars/pizza.grammar b/queries/grammars/pizza.grammar new file mode 100644 index 00000000..21052970 --- /dev/null +++ b/queries/grammars/pizza.grammar @@ -0,0 +1,81 @@ +# TODO: 2x of a topping. "Tvöfalt", "mikið", "extra" +# TODO: Ban more than two instances of a topping. + +/þgf = þgf +/ef = ef + +Query → + QPizza '?'? + +QPizza → + QPizzaQuery + +QPizzaQuery → + QPizzaHotword + | QPizzaDialogue + +QPizzaHotWord → + QPizzaWord + | QPizzaRequestBare + +QPizzaRequestBare -> + QPizzaEgVil QPizzaKaupaFaraFaPanta QPizzaWord + +QPizzaDialogue → + QPizzaNumberAnswer + | QPizzaToppingsAnswer + | QPizzaSizeAnswer + +QPizzaNumberAnswer → + QPizzaEgVil QPizzaKaupaFaraFaPanta QPizzaNum/þf QPizzaWord/þf + | QPizzaNum/þf QPizzaWord/þf? + +QPizzaToppingsAnswer -> + QPizzaEgVil? QPizzaToppingsList "á" QPizzaWord/þf + +QPizzaSizeAnswer -> + QPizza + +QPizzaToppingsList -> + QPizzaToppingsWord/þf* 'og:st'? QPizzaToppingsWord + +QPizzaEgVil → + "ég"? "vil" + | "ég" "vill" + | "mig" "langar" "að" + | "mig" "langar" "í" + +QPizzaKaupaFaraFaPanta → + "kaupa" "mér"? + | "fá" "mér"? + | "panta" "mér"? + +QPizzaWord/fall → + 'pizza:kvk'/fall + | 'pitsa:kvk'/fall + | 'pítsa:kvk'/fall + | 'flatbaka:kvk'/fall + +# Toppings that are transcribed in different ways are in separate nonterminals. +QPizzaToppingsWord/fall -> + 'sveppur:kk'/fall + | QPizzaPepperoniWord/fall + | 'ananas:kk'/fall + | 'skinka:kvk'/fall + | QPizzaOliveWord/fall + +QPizzaNum/fall → + # to is a declinable number word ('tveir/tvo/tveim/tveggja') + # töl is an undeclinable number word ('sautján') + # tala is a number ('17') + to | töl | tala + +QPizzaPepperoniWord/fall -> + 'pepperóní:hk'/fall + | "pepperoni" + | "pepperóni" + | "pepperoní" + +QPizzaOliveWord/fall + 'ólífa:kvk'/fall + | 'ólíva:kvk'/fall \ No newline at end of file diff --git a/queries/pizza.py b/queries/pizza.py index 4cc21d84..e8012b44 100644 --- a/queries/pizza.py +++ b/queries/pizza.py @@ -75,31 +75,46 @@ def help_text(lemma: str) -> str: # The context-free grammar for the queries recognized by this plug-in module GRAMMAR = """ +# TODO: 2x of a topping. "Tvöfalt", "mikið", "extra" +# TODO: Ban more than two instances of a topping. + +/þgf = þgf +/ef = ef + Query → - QPizzaHotWord | QPizza + QPizza '?'? -QPizza → QPizzaQuery +QPizza → + QPizzaQuery QPizzaQuery → - QPizzaDialogue '?'? + QPizzaHotword + | QPizzaDialogue QPizzaHotWord → - QPizzaNames - | QPizzaEgVil QPizzaKaupaFaraFaPanta QPizzaNames + QPizzaWord + | QPizzaRequestBare -QPizzaNames → - 'pizza' - | 'pitsa' - | "pitsur" - | "pizzur" - -QPizzaKaupaFaraFaPanta → - "kaupa" "mér"? - | "fá" "mér"? - | "panta" "mér"? +QPizzaRequestBare -> + QPizzaEgVil QPizzaKaupaFaraFaPanta QPizzaWord QPizzaDialogue → - QPizzaOrder + QPizzaNumberAnswer + | QPizzaToppingsAnswer + | QPizzaSizeAnswer + +QPizzaNumberAnswer → + QPizzaEgVil QPizzaKaupaFaraFaPanta QPizzaNum/þf QPizzaWord/þf + | QPizzaNum/þf QPizzaWord/þf? + +QPizzaToppingsAnswer -> + QPizzaEgVil? QPizzaToppingsList "á" QPizzaWord/þf + +QPizzaSizeAnswer -> + QPizza + +QPizzaToppingsList -> + QPizzaToppingsWord/þf* 'og:st'? QPizzaToppingsWord QPizzaEgVil → "ég"? "vil" @@ -107,15 +122,41 @@ def help_text(lemma: str) -> str: | "mig" "langar" "að" | "mig" "langar" "í" -QPizzaOrder → - QPizzaEgVil QPizzaKaupaFaraFaPanta QPizzaNum QPizzaNames +QPizzaKaupaFaraFaPanta → + "kaupa" "mér"? + | "fá" "mér"? + | "panta" "mér"? -QPizzaNum → +QPizzaWord/fall → + 'pizza:kvk'/fall + | 'pitsa:kvk'/fall + | 'pítsa:kvk'/fall + | 'flatbaka:kvk'/fall + +# Toppings that are transcribed in different ways are in separate nonterminals. +QPizzaToppingsWord/fall -> + 'sveppur:kk'/fall + | QPizzaPepperoniWord/fall + | 'ananas:kk'/fall + | 'skinka:kvk'/fall + | QPizzaOliveWord/fall + +QPizzaNum/fall → # to is a declinable number word ('tveir/tvo/tveim/tveggja') # töl is an undeclinable number word ('sautján') # tala is a number ('17') to | töl | tala +QPizzaPepperoniWord/fall -> + 'pepperóní:hk'/fall + | "pepperoni" + | "pepperóni" + | "pepperoní" + +QPizzaOliveWord/fall + 'ólífa:kvk'/fall + | 'ólíva:kvk'/fall + """ From b7b4abd608d6241a3c59e24aa146504228f26f64 Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Wed, 20 Jul 2022 11:31:16 +0000 Subject: [PATCH 233/371] fix arrow --- queries/grammars/pizza.grammar | 2 +- queries/pizza.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/queries/grammars/pizza.grammar b/queries/grammars/pizza.grammar index 21052970..261f0959 100644 --- a/queries/grammars/pizza.grammar +++ b/queries/grammars/pizza.grammar @@ -76,6 +76,6 @@ QPizzaPepperoniWord/fall -> | "pepperóni" | "pepperoní" -QPizzaOliveWord/fall +QPizzaOliveWord/fall -> 'ólífa:kvk'/fall | 'ólíva:kvk'/fall \ No newline at end of file diff --git a/queries/pizza.py b/queries/pizza.py index 4247121d..cc42fea0 100644 --- a/queries/pizza.py +++ b/queries/pizza.py @@ -147,7 +147,7 @@ def help_text(lemma: str) -> str: | "pepperóni" | "pepperoní" -QPizzaOliveWord/fall +QPizzaOliveWord/fall -> 'ólífa:kvk'/fall | 'ólíva:kvk'/fall From bbaadd6f92cdea32861dfd070457a449fa652120 Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Wed, 20 Jul 2022 11:35:54 +0000 Subject: [PATCH 234/371] pizza fixes --- queries/grammars/pizza.grammar | 14 +++++++------- queries/pizza.py | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/queries/grammars/pizza.grammar b/queries/grammars/pizza.grammar index 261f0959..e3610c11 100644 --- a/queries/grammars/pizza.grammar +++ b/queries/grammars/pizza.grammar @@ -11,20 +11,20 @@ QPizza → QPizzaQuery QPizzaQuery → - QPizzaHotword + QPizzaHotWord | QPizzaDialogue QPizzaHotWord → - QPizzaWord + QPizzaWord/nf | QPizzaRequestBare QPizzaRequestBare -> - QPizzaEgVil QPizzaKaupaFaraFaPanta QPizzaWord + QPizzaEgVil QPizzaKaupaFaraFaPanta QPizzaWord/þf QPizzaDialogue → QPizzaNumberAnswer | QPizzaToppingsAnswer - | QPizzaSizeAnswer + # | QPizzaSizeAnswer QPizzaNumberAnswer → QPizzaEgVil QPizzaKaupaFaraFaPanta QPizzaNum/þf QPizzaWord/þf @@ -33,11 +33,11 @@ QPizzaNumberAnswer → QPizzaToppingsAnswer -> QPizzaEgVil? QPizzaToppingsList "á" QPizzaWord/þf -QPizzaSizeAnswer -> - QPizza +# QPizzaSizeAnswer -> +# QPizza QPizzaToppingsList -> - QPizzaToppingsWord/þf* 'og:st'? QPizzaToppingsWord + QPizzaToppingsWord/þf* 'og:st'? QPizzaToppingsWord/þf QPizzaEgVil → "ég"? "vil" diff --git a/queries/pizza.py b/queries/pizza.py index cc42fea0..0264a98b 100644 --- a/queries/pizza.py +++ b/queries/pizza.py @@ -82,20 +82,20 @@ def help_text(lemma: str) -> str: QPizzaQuery QPizzaQuery → - QPizzaHotword + QPizzaHotWord | QPizzaDialogue QPizzaHotWord → - QPizzaWord + QPizzaWord/nf | QPizzaRequestBare QPizzaRequestBare -> - QPizzaEgVil QPizzaKaupaFaraFaPanta QPizzaWord + QPizzaEgVil QPizzaKaupaFaraFaPanta QPizzaWord/þf QPizzaDialogue → QPizzaNumberAnswer | QPizzaToppingsAnswer - | QPizzaSizeAnswer + # | QPizzaSizeAnswer QPizzaNumberAnswer → QPizzaEgVil QPizzaKaupaFaraFaPanta QPizzaNum/þf QPizzaWord/þf @@ -104,11 +104,11 @@ def help_text(lemma: str) -> str: QPizzaToppingsAnswer -> QPizzaEgVil? QPizzaToppingsList "á" QPizzaWord/þf -QPizzaSizeAnswer -> - QPizza +# QPizzaSizeAnswer -> +# QPizza QPizzaToppingsList -> - QPizzaToppingsWord/þf* 'og:st'? QPizzaToppingsWord + QPizzaToppingsWord/þf* 'og:st'? QPizzaToppingsWord/þf QPizzaEgVil → "ég"? "vil" From 10c02443b5735aceeca3e65360cba2b1d3172658 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Wed, 20 Jul 2022 11:43:35 +0000 Subject: [PATCH 235/371] Added QPizzaToppingsAnswer func, no code yet --- queries/pizza.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/queries/pizza.py b/queries/pizza.py index 0264a98b..46fa2fc8 100644 --- a/queries/pizza.py +++ b/queries/pizza.py @@ -76,14 +76,14 @@ def help_text(lemma: str) -> str: /ef = ef Query → - QPizza '?'? + QPizzaHotWord '?'? + | QPizza '?'? QPizza → QPizzaQuery QPizzaQuery → - QPizzaHotWord - | QPizzaDialogue + QPizzaDialogue QPizzaHotWord → QPizzaWord/nf @@ -102,7 +102,7 @@ def help_text(lemma: str) -> str: | QPizzaNum/þf QPizzaWord/þf? QPizzaToppingsAnswer -> - QPizzaEgVil? QPizzaToppingsList "á" QPizzaWord/þf + QPizzaEgVil? QPizzaToppingsList "á"? QPizzaWord/þf? # QPizzaSizeAnswer -> # QPizza @@ -202,7 +202,7 @@ def QPizzaHotWord(node: Node, params: QueryStateDict, result: Result) -> None: Query.get_dsm(result).hotword_activated() -def QPizzaOrder(node: Node, params: QueryStateDict, result: Result) -> None: +def QPizzaNumberAnswer(node: Node, params: QueryStateDict, result: Result) -> None: dsm: DialogueStateManager = Query.get_dsm(result) # resource = dsm.get_resource("PizzaCount") number: int = result.get("number", 1) @@ -212,6 +212,10 @@ def QPizzaOrder(node: Node, params: QueryStateDict, result: Result) -> None: print("Pizza Count: ", number) +def QPizzaToppingsAnswer(node: Node, params: QueryStateDict, result: Result) -> None: + ... + + def QPizzaNum(node: Node, params: QueryStateDict, result: Result) -> None: number: int = int(parse_num(node, result._nominative)) if "numbers" not in result: From efea4f2712d86773cc439add478f5f578717632c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Wed, 20 Jul 2022 13:19:11 +0000 Subject: [PATCH 236/371] Initial functionality for toppings --- queries/pizza.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/queries/pizza.py b/queries/pizza.py index 46fa2fc8..eda4bb5e 100644 --- a/queries/pizza.py +++ b/queries/pizza.py @@ -23,6 +23,7 @@ from typing import Optional, Set, cast import logging import random +from xxlimited import Str from query import Query, QueryStateDict from tree import Result, Node @@ -129,7 +130,7 @@ def help_text(lemma: str) -> str: # Toppings that are transcribed in different ways are in separate nonterminals. QPizzaToppingsWord/fall -> - 'sveppur:kk'/fall + QPizzaMushroomWord/fall | QPizzaPepperoniWord/fall | 'ananas:kk'/fall | 'skinka:kvk'/fall @@ -151,6 +152,10 @@ def help_text(lemma: str) -> str: 'ólífa:kvk'/fall | 'ólíva:kvk'/fall +QPizzaMushroomWord/fall -> + 'sveppur:kk'/fall + | "Sveppi" + """ @@ -212,8 +217,18 @@ def QPizzaNumberAnswer(node: Node, params: QueryStateDict, result: Result) -> No print("Pizza Count: ", number) -def QPizzaToppingsAnswer(node: Node, params: QueryStateDict, result: Result) -> None: - ... +def QPizzaToppingsList(node: Node, params: QueryStateDict, result: Result) -> None: + print("Toppings in QPizzaToppingsList: ", result.get("toppings", [])) + + +def QPizzaToppingsWord(node: Node, params: QueryStateDict, result: Result) -> None: + print("in toppings word with: ", result._root) + topping: str = result._root + if "toppings" not in result: + print("Toppings not in result") + result["toppings"] = set() + result["toppings"].add(topping) + print("Toppings: ", result["toppings"]) def QPizzaNum(node: Node, params: QueryStateDict, result: Result) -> None: From 446420a60af2961ba481cc910a64b5e5d8896d3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Wed, 20 Jul 2022 14:27:49 +0000 Subject: [PATCH 237/371] Toppings now added to result dictionary --- queries/pizza.py | 120 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 98 insertions(+), 22 deletions(-) diff --git a/queries/pizza.py b/queries/pizza.py index eda4bb5e..33c1396b 100644 --- a/queries/pizza.py +++ b/queries/pizza.py @@ -20,7 +20,7 @@ This query module handles dialogue related to ordering pizza. """ -from typing import Optional, Set, cast +from typing import Dict, Optional, Set, cast import logging import random from xxlimited import Str @@ -72,15 +72,16 @@ def help_text(lemma: str) -> str: # TODO: 2x of a topping. "Tvöfalt", "mikið", "extra" # TODO: Ban more than two instances of a topping. +# TODO: Fix the toppings being a set. Doesn't handle "Ég vil skinku, ólífur og auka skinku." /þgf = þgf /ef = ef Query → - QPizzaHotWord '?'? + QPizzaHotWord '?'? | QPizza '?'? -QPizza → +QPizza → QPizzaQuery QPizzaQuery → @@ -96,7 +97,7 @@ def help_text(lemma: str) -> str: QPizzaDialogue → QPizzaNumberAnswer | QPizzaToppingsAnswer - # | QPizzaSizeAnswer + | QPizzaSizeAnswer QPizzaNumberAnswer → QPizzaEgVil QPizzaKaupaFaraFaPanta QPizzaNum/þf QPizzaWord/þf @@ -105,11 +106,52 @@ def help_text(lemma: str) -> str: QPizzaToppingsAnswer -> QPizzaEgVil? QPizzaToppingsList "á"? QPizzaWord/þf? -# QPizzaSizeAnswer -> -# QPizza +QPizzaSizeAnswer -> + QPizzaEgVil? QPizzaSize/þf QPizzaWord/þf? + | QPizzaSize/nf + | QPizzaEgVil? QPizzaMediumWord QPizzaAfPitsuPhrase # Common to say "miðstærð", or "Ég vil miðstærð af pítsu." QPizzaToppingsList -> - QPizzaToppingsWord/þf* 'og:st'? QPizzaToppingsWord/þf + QPizzaToppingsWordWrapper/þf* 'og:st'? QPizzaToppingsWord/þf + +QPizzaSize/fall -> + QPizzaSizeLarge/fall + | QPizzaSizeMedium/fall + | QPizzaSizeSmall/fall + +QPizzaToppingsWordWrapper/fall -> + QPizzaToppingsWord/fall + +# Toppings that are transcribed in different ways are in separate nonterminals. +QPizzaToppingsWord/fall -> + QPizzaMushroomWord/fall + | QPizzaPepperoniWord/fall + | 'ananas:kk'/fall + | 'skinka:kvk'/fall + | QPizzaOliveWord/fall + +QPizzaAfPitsuPhrase -> + "af" QPizzaWord/þgf + +# A large pizza at Domino's is typically thought to be 16", some believe it to be 15". +# The actual size is 14.5". +QPizzaSizeLarge/fall -> + 'stór:lo'/fall + | QPizzaSixteenWord 'tomma:kvk'/fall? + | QPizzaFifteenWord 'tomma:kvk'/fall? + | QPizzaFourteenPointFiveWord 'tomma:kvk'/fall? + +QPizzaSizeMedium/fall -> + 'millistór:lo'/fall + | 'meðalstór:lo'/fall + | QPizzaTwelveWord 'tomma:kvk'/fall? + +QPizzaMediumWord -> + "miðstærð" + +QPizzaSizeSmall/fall -> + 'lítil:lo'/fall + | QPizzaNineWord 'tomma:kvk'/fall? QPizzaEgVil → "ég"? "vil" @@ -128,13 +170,32 @@ def help_text(lemma: str) -> str: | 'pítsa:kvk'/fall | 'flatbaka:kvk'/fall -# Toppings that are transcribed in different ways are in separate nonterminals. -QPizzaToppingsWord/fall -> - QPizzaMushroomWord/fall - | QPizzaPepperoniWord/fall - | 'ananas:kk'/fall - | 'skinka:kvk'/fall - | QPizzaOliveWord/fall +QPizzaSixteenWord -> + "16" + | "sextán" + +QPizzaFifteenWord -> + "15" + | "fimmtán" + +QPizzaFourteenPointFiveWord -> + QPizzaFourteenWord "komma" QPizzaFiveWord + +QPizzaFourteenWord -> + "14" + | "fjórtán" + +QPizzaFiveWord -> + "5" + | "fimm" + +QPizzaTwelveWord -> + "12" + | "tólf" + +QPizzaNineWord -> + "9" + | "níu" QPizzaNum/fall → # to is a declinable number word ('tveir/tvo/tveim/tveggja') @@ -155,7 +216,6 @@ def help_text(lemma: str) -> str: QPizzaMushroomWord/fall -> 'sveppur:kk'/fall | "Sveppi" - """ @@ -218,17 +278,21 @@ def QPizzaNumberAnswer(node: Node, params: QueryStateDict, result: Result) -> No def QPizzaToppingsList(node: Node, params: QueryStateDict, result: Result) -> None: - print("Toppings in QPizzaToppingsList: ", result.get("toppings", [])) + print("Toppings in QPizzaToppingsList: ", result.get("toppings", {})) + dsm: DialogueStateManager = Query.get_dsm(result) + toppings: Dict[str, int] = result.get("toppings", {}) + resource = dsm.current_resource + (_, _, index) = resource.name.partition("_") + toppings_resource = dsm.get_resource("Toppings_{}".format(index)) + for topping in toppings: + ... # toppings_resource.data[topping] = toppings[topping] def QPizzaToppingsWord(node: Node, params: QueryStateDict, result: Result) -> None: - print("in toppings word with: ", result._root) - topping: str = result._root + topping: str = result.dict.pop("real_name", result._nominative) if "toppings" not in result: - print("Toppings not in result") - result["toppings"] = set() - result["toppings"].add(topping) - print("Toppings: ", result["toppings"]) + result["toppings"] = {} + result["toppings"][topping] = 1 # TODO: Add support for extra toppings def QPizzaNum(node: Node, params: QueryStateDict, result: Result) -> None: @@ -239,6 +303,18 @@ def QPizzaNum(node: Node, params: QueryStateDict, result: Result) -> None: result.number = number +def QPizzaPepperoniWord(node: Node, params: QueryStateDict, result: Result) -> None: + result.real_name = "pepperóní" + + +def QPizzaOliveWord(node: Node, params: QueryStateDict, result: Result) -> None: + result.real_name = "ólífur" + + +def QPizzaMushroomWord(node: Node, params: QueryStateDict, result: Result) -> None: + result.real_name = "sveppir" + + _ANSWERING_FUNCTIONS: AnsweringFunctionMap = { "PizzaOrder": _generate_order_answer, "Pizza": _generate_pizza_answer, From 1a5e8bf9921a511160b0c152a309ce91698d09f5 Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Wed, 20 Jul 2022 14:28:16 +0000 Subject: [PATCH 238/371] grammar size addition --- queries/grammars/pizza.grammar | 99 ++++++++++++++++++++++++++++------ queries/pizza.py | 91 ++++++++++++++++++++++++++----- 2 files changed, 160 insertions(+), 30 deletions(-) diff --git a/queries/grammars/pizza.grammar b/queries/grammars/pizza.grammar index e3610c11..7125c422 100644 --- a/queries/grammars/pizza.grammar +++ b/queries/grammars/pizza.grammar @@ -1,18 +1,19 @@ # TODO: 2x of a topping. "Tvöfalt", "mikið", "extra" # TODO: Ban more than two instances of a topping. +# TODO: Fix the toppings being a set. Doesn't handle "Ég vil skinku, ólífur og auka skinku." /þgf = þgf /ef = ef Query → - QPizza '?'? + QPizzaHotWord '?'? + | QPizza '?'? -QPizza → +QPizza → QPizzaQuery QPizzaQuery → - QPizzaHotWord - | QPizzaDialogue + QPizzaDialogue QPizzaHotWord → QPizzaWord/nf @@ -24,20 +25,61 @@ QPizzaRequestBare -> QPizzaDialogue → QPizzaNumberAnswer | QPizzaToppingsAnswer - # | QPizzaSizeAnswer + | QPizzaSizeAnswer QPizzaNumberAnswer → QPizzaEgVil QPizzaKaupaFaraFaPanta QPizzaNum/þf QPizzaWord/þf | QPizzaNum/þf QPizzaWord/þf? QPizzaToppingsAnswer -> - QPizzaEgVil? QPizzaToppingsList "á" QPizzaWord/þf + QPizzaEgVil? QPizzaToppingsList "á"? QPizzaWord/þf? -# QPizzaSizeAnswer -> -# QPizza +QPizzaSizeAnswer -> + QPizzaEgVil? QPizzaSize/þf QPizzaWord/þf? + | QPizzaSize/nf + | QPizzaEgVil? QPizzaMediumWord QPizzaAfPitsuPhrase # Common to say "miðstærð", or "Ég vil miðstærð af pítsu." QPizzaToppingsList -> - QPizzaToppingsWord/þf* 'og:st'? QPizzaToppingsWord/þf + QPizzaToppingsWordWrapper/þf* 'og:st'? QPizzaToppingsWord/þf + +QPizzaSize/fall -> + QPizzaSizeLarge/fall + | QPizzaSizeMedium/fall + | QPizzaSizeSmall/fall + +QPizzaToppingsWordWrapper/fall -> + QPizzaToppingsWord/fall + +# Toppings that are transcribed in different ways are in separate nonterminals. +QPizzaToppingsWord/fall -> + QPizzaMushroomWord/fall + | QPizzaPepperoniWord/fall + | 'ananas:kk'/fall + | 'skinka:kvk'/fall + | QPizzaOliveWord/fall + +QPizzaAfPitsuPhrase -> + "af" QPizzaWord/þgf + +# A large pizza at Domino's is typically thought to be 16", some believe it to be 15". +# The actual size is 14.5". +QPizzaSizeLarge/fall -> + 'stór:lo'/fall + | QPizzaSixteenWord 'tomma:kvk'/fall? + | QPizzaFifteenWord 'tomma:kvk'/fall? + | QPizzaFourteenPointFiveWord 'tomma:kvk'/fall? + +QPizzaSizeMedium/fall -> + 'millistór:lo'/fall + | 'meðalstór:lo'/fall + | QPizzaTwelveWord 'tomma:kvk'/fall? + +QPizzaMediumWord -> + "miðstærð" + +QPizzaSizeSmall/fall -> + 'lítil:lo'/fall + | QPizzaNineWord 'tomma:kvk'/fall? QPizzaEgVil → "ég"? "vil" @@ -56,13 +98,32 @@ QPizzaWord/fall → | 'pítsa:kvk'/fall | 'flatbaka:kvk'/fall -# Toppings that are transcribed in different ways are in separate nonterminals. -QPizzaToppingsWord/fall -> - 'sveppur:kk'/fall - | QPizzaPepperoniWord/fall - | 'ananas:kk'/fall - | 'skinka:kvk'/fall - | QPizzaOliveWord/fall +QPizzaSixteenWord -> + "16" + | "sextán" + +QPizzaFifteenWord -> + "15" + | "fimmtán" + +QPizzaFourteenPointFiveWord -> + QPizzaFourteenWord "komma" QPizzaFiveWord + +QPizzaFourteenWord -> + "14" + | "fjórtán" + +QPizzaFiveWord -> + "5" + | "fimm" + +QPizzaTwelveWord -> + "12" + | "tólf" + +QPizzaNineWord -> + "9" + | "níu" QPizzaNum/fall → # to is a declinable number word ('tveir/tvo/tveim/tveggja') @@ -78,4 +139,8 @@ QPizzaPepperoniWord/fall -> QPizzaOliveWord/fall -> 'ólífa:kvk'/fall - | 'ólíva:kvk'/fall \ No newline at end of file + | 'ólíva:kvk'/fall + +QPizzaMushroomWord/fall -> + 'sveppur:kk'/fall + | "Sveppi" \ No newline at end of file diff --git a/queries/pizza.py b/queries/pizza.py index 46fa2fc8..b7d20875 100644 --- a/queries/pizza.py +++ b/queries/pizza.py @@ -71,15 +71,16 @@ def help_text(lemma: str) -> str: # TODO: 2x of a topping. "Tvöfalt", "mikið", "extra" # TODO: Ban more than two instances of a topping. +# TODO: Fix the toppings being a set. Doesn't handle "Ég vil skinku, ólífur og auka skinku." /þgf = þgf /ef = ef Query → - QPizzaHotWord '?'? + QPizzaHotWord '?'? | QPizza '?'? -QPizza → +QPizza → QPizzaQuery QPizzaQuery → @@ -95,7 +96,7 @@ def help_text(lemma: str) -> str: QPizzaDialogue → QPizzaNumberAnswer | QPizzaToppingsAnswer - # | QPizzaSizeAnswer + | QPizzaSizeAnswer QPizzaNumberAnswer → QPizzaEgVil QPizzaKaupaFaraFaPanta QPizzaNum/þf QPizzaWord/þf @@ -104,11 +105,52 @@ def help_text(lemma: str) -> str: QPizzaToppingsAnswer -> QPizzaEgVil? QPizzaToppingsList "á"? QPizzaWord/þf? -# QPizzaSizeAnswer -> -# QPizza +QPizzaSizeAnswer -> + QPizzaEgVil? QPizzaSize/þf QPizzaWord/þf? + | QPizzaSize/nf + | QPizzaEgVil? QPizzaMediumWord QPizzaAfPitsuPhrase # Common to say "miðstærð", or "Ég vil miðstærð af pítsu." QPizzaToppingsList -> - QPizzaToppingsWord/þf* 'og:st'? QPizzaToppingsWord/þf + QPizzaToppingsWordWrapper/þf* 'og:st'? QPizzaToppingsWord/þf + +QPizzaSize/fall -> + QPizzaSizeLarge/fall + | QPizzaSizeMedium/fall + | QPizzaSizeSmall/fall + +QPizzaToppingsWordWrapper/fall -> + QPizzaToppingsWord/fall + +# Toppings that are transcribed in different ways are in separate nonterminals. +QPizzaToppingsWord/fall -> + QPizzaMushroomWord/fall + | QPizzaPepperoniWord/fall + | 'ananas:kk'/fall + | 'skinka:kvk'/fall + | QPizzaOliveWord/fall + +QPizzaAfPitsuPhrase -> + "af" QPizzaWord/þgf + +# A large pizza at Domino's is typically thought to be 16", some believe it to be 15". +# The actual size is 14.5". +QPizzaSizeLarge/fall -> + 'stór:lo'/fall + | QPizzaSixteenWord 'tomma:kvk'/fall? + | QPizzaFifteenWord 'tomma:kvk'/fall? + | QPizzaFourteenPointFiveWord 'tomma:kvk'/fall? + +QPizzaSizeMedium/fall -> + 'millistór:lo'/fall + | 'meðalstór:lo'/fall + | QPizzaTwelveWord 'tomma:kvk'/fall? + +QPizzaMediumWord -> + "miðstærð" + +QPizzaSizeSmall/fall -> + 'lítil:lo'/fall + | QPizzaNineWord 'tomma:kvk'/fall? QPizzaEgVil → "ég"? "vil" @@ -127,13 +169,32 @@ def help_text(lemma: str) -> str: | 'pítsa:kvk'/fall | 'flatbaka:kvk'/fall -# Toppings that are transcribed in different ways are in separate nonterminals. -QPizzaToppingsWord/fall -> - 'sveppur:kk'/fall - | QPizzaPepperoniWord/fall - | 'ananas:kk'/fall - | 'skinka:kvk'/fall - | QPizzaOliveWord/fall +QPizzaSixteenWord -> + "16" + | "sextán" + +QPizzaFifteenWord -> + "15" + | "fimmtán" + +QPizzaFourteenPointFiveWord -> + QPizzaFourteenWord "komma" QPizzaFiveWord + +QPizzaFourteenWord -> + "14" + | "fjórtán" + +QPizzaFiveWord -> + "5" + | "fimm" + +QPizzaTwelveWord -> + "12" + | "tólf" + +QPizzaNineWord -> + "9" + | "níu" QPizzaNum/fall → # to is a declinable number word ('tveir/tvo/tveim/tveggja') @@ -151,6 +212,10 @@ def help_text(lemma: str) -> str: 'ólífa:kvk'/fall | 'ólíva:kvk'/fall +QPizzaMushroomWord/fall -> + 'sveppur:kk'/fall + | "Sveppi" + """ From 8df7f4eda4787a54cb6420015b58dd87935a2a09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Wed, 20 Jul 2022 14:59:12 +0000 Subject: [PATCH 239/371] Changing how initial resource gets set, still has to be fixed --- queries/dialogue.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index c05ef663..fcc3a7c1 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -141,6 +141,7 @@ def load_dialogue(self, dialogue_name: str): self._finished: bool = False self._expiration_time: int = _DEFAULT_EXPIRATION_TIME self._timed_out: bool = False + self._initial_resource = None dialogue_saved_state: Optional[str] = self._dialogue_data.get( dialogue_name, None @@ -196,7 +197,7 @@ def _initialize_resource_graph(self) -> None: """ for resource in self._resources.values(): print("Initializing resource graph for", resource.name) - if resource.order_index == 0: + if resource.order_index == 0 and self._initial_resource is None: self._initial_resource = resource self._resource_graph[resource] = {"children": [], "parents": []} print("Children/parents set up, starting to fill:") @@ -300,6 +301,7 @@ def add_dynamic_resource(self, resource_name: str, parent_name: str) -> None: resource: Resource = dynamic_resources[indexed_resource_name] # Appending resource to required list of parent resource parent_resource.requires.append(indexed_resource_name) + print("Parent resource requirements: ", parent_resource.requires) def _add_child_resource(resource: Resource) -> None: """ @@ -437,6 +439,7 @@ def _find_current_resource(self) -> Resource: Finds the current resource in the resource graph. """ curr_res: Resource = self._initial_resource + print("Current resource: ", curr_res.name) # If the initial parent is a wrapper, the current resource should be that parent initial_parents = self._resource_graph[curr_res]["parents"] if len(initial_parents) == 1 and isinstance( @@ -444,14 +447,16 @@ def _find_current_resource(self) -> Resource: ): curr_res = initial_parents[0] while curr_res.is_confirmed: + print("Current resource!!: ", curr_res.name) for parent in self._resource_graph[curr_res]["parents"]: curr_res = parent grandparents = self._resource_graph[parent]["parents"] if len(grandparents) == 1 and isinstance( grandparents[0], WrapperResource ): + print("!!Current resource!!: ", curr_res.name) curr_res = grandparents[0] - break + return curr_res return curr_res def finish_dialogue(self) -> None: From 210746500486a67892fad7ddcf2e215ec48e71bc Mon Sep 17 00:00:00 2001 From: Logi E Date: Wed, 20 Jul 2022 15:30:02 +0000 Subject: [PATCH 240/371] postorder find_current_resource --- queries/dialogue.py | 49 ++++++++++++++++++++++++++------------------ queries/resources.py | 10 ++++----- 2 files changed, 34 insertions(+), 25 deletions(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index fcc3a7c1..8acb020a 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -438,26 +438,35 @@ def _find_current_resource(self) -> Resource: """ Finds the current resource in the resource graph. """ - curr_res: Resource = self._initial_resource - print("Current resource: ", curr_res.name) - # If the initial parent is a wrapper, the current resource should be that parent - initial_parents = self._resource_graph[curr_res]["parents"] - if len(initial_parents) == 1 and isinstance( - initial_parents[0], WrapperResource - ): - curr_res = initial_parents[0] - while curr_res.is_confirmed: - print("Current resource!!: ", curr_res.name) - for parent in self._resource_graph[curr_res]["parents"]: - curr_res = parent - grandparents = self._resource_graph[parent]["parents"] - if len(grandparents) == 1 and isinstance( - grandparents[0], WrapperResource - ): - print("!!Current resource!!: ", curr_res.name) - curr_res = grandparents[0] - return curr_res - return curr_res + curr_res: Optional[Resource] = None + wrapper_parent: Optional[Resource] = None + + def _recurse_resources(resource: Resource) -> None: + nonlocal curr_res, wrapper_parent + if resource.is_confirmed or resource.is_skipped: + return + else: + if isinstance(resource, WrapperResource): + wrapper_parent = resource + for child in self._resource_graph[resource]["children"]: + _recurse_resources(child) + if curr_res == child: + # Found a non-confirmed descendant + if ( + not isinstance(child, WrapperResource) + and wrapper_parent == resource + ): + # If the direct child of a wrapper resource + # is the current resource (and not a wrapper itself), + # set the wrapper as the current resource + curr_res = resource + return + if curr_res is not None: + return + curr_res = resource + + _recurse_resources(self._resources["Final"]) + return curr_res or self._resources["Final"] def finish_dialogue(self) -> None: """Set the dialogue as finished.""" diff --git a/queries/resources.py b/queries/resources.py index 5abe4912..23186a79 100644 --- a/queries/resources.py +++ b/queries/resources.py @@ -236,16 +236,16 @@ class NumberResource(Resource): data: int = 0 -@dataclass(eq=False, repr=False) -class OrResource(Resource): - exclusive: bool = False # Only one of the resources should be fulfilled - - @dataclass(eq=False, repr=False) # Wrapper when multiple resources are required class WrapperResource(Resource): ... +@dataclass(eq=False, repr=False) +class OrResource(WrapperResource): + exclusive: bool = False # Only one of the resources should be fulfilled + + @dataclass(eq=False, repr=False) class FinalResource(Resource): """Resource representing the final state of a dialogue.""" From 48e479410fd7e5f7ee0cedc4af813baa5b30b379 Mon Sep 17 00:00:00 2001 From: Logi E Date: Wed, 20 Jul 2022 15:44:02 +0000 Subject: [PATCH 241/371] fixes to find_current_resource --- queries/dialogue.py | 42 ++++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index 8acb020a..081e3731 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -436,7 +436,8 @@ def _find_parent_resources(self, resource: Resource) -> Set[Resource]: def _find_current_resource(self) -> Resource: """ - Finds the current resource in the resource graph. + Finds the current resource in the resource graph + using a postorder traversal of the resource graph. """ curr_res: Optional[Resource] = None wrapper_parent: Optional[Resource] = None @@ -445,25 +446,26 @@ def _recurse_resources(resource: Resource) -> None: nonlocal curr_res, wrapper_parent if resource.is_confirmed or resource.is_skipped: return - else: - if isinstance(resource, WrapperResource): - wrapper_parent = resource - for child in self._resource_graph[resource]["children"]: - _recurse_resources(child) - if curr_res == child: - # Found a non-confirmed descendant - if ( - not isinstance(child, WrapperResource) - and wrapper_parent == resource - ): - # If the direct child of a wrapper resource - # is the current resource (and not a wrapper itself), - # set the wrapper as the current resource - curr_res = resource - return - if curr_res is not None: - return - curr_res = resource + # Current resource is neither confirmed nor skipped, + # so we try to recurse further + if isinstance(resource, WrapperResource): + # This resource is a wrapper, keep it in a variable + wrapper_parent = resource + for child in self._resource_graph[resource]["children"]: + _recurse_resources(child) + if ( + curr_res == child + and not isinstance(child, WrapperResource) + and wrapper_parent == resource + ): + # If the direct child of a wrapper resource + # is the current resource and not a wrapper itself, + # set the wrapper as the current resource instead + curr_res = resource + if curr_res is not None: + # Found a non-confirmed resource, stop looking + return + curr_res = resource _recurse_resources(self._resources["Final"]) return curr_res or self._resources["Final"] From f9fe459942797d83fdf3bb8197bfd3f9878d37f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Wed, 20 Jul 2022 15:45:25 +0000 Subject: [PATCH 242/371] Toppings can now be added to pizzas --- queries/dialogue.py | 2 +- queries/dialogues/pizza.toml | 6 ++--- queries/pizza.py | 43 +++++++++++++++++++++++++++++++----- queries/resources.py | 9 ++++++++ 4 files changed, 50 insertions(+), 10 deletions(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index 8acb020a..21502d58 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -375,7 +375,7 @@ def get_answer( raise ValueError("No answer for cancelled dialogue") return self._answer_tuple - resource_name = self._current_resource.name.partition("_")[0] + resource_name = self._current_resource.name.split("_")[0] if resource_name in self._answering_functions: print("GENERATING ANSWER FOR ", resource_name) ans = self._answering_functions[resource_name]( diff --git a/queries/dialogues/pizza.toml b/queries/dialogues/pizza.toml index 03d72add..4e8fecd7 100644 --- a/queries/dialogues/pizza.toml +++ b/queries/dialogues/pizza.toml @@ -21,18 +21,18 @@ prompts.timed_out = "Pítsupöntunin þín rann út á tíma. Vinsamlegast byrja name = "Pizza" type = "WrapperResource" requires = ["Type", "Size", "Crust"] -prompts.initial = "Hvernig pítsu viltu fá?" prompts.size = "Hvaða stærð af pítsu viltu fá?" prompts.crust = "Hvernig botn viltu?" [[dynamic_resources]] name = "Type" type = "OrResource" -requires = ["Toppings", "FromMenu"] #, "Split"] +requires = ["Toppings", "FromMenu"] #, "Split"] +prompts.initial = "Hvernig pítsu viltu fá?" [[dynamic_resources]] name = "Toppings" -type = "ListResource" +type = "DictResource" [[dynamic_resources]] name = "FromMenu" diff --git a/queries/pizza.py b/queries/pizza.py index 1c07eca7..22b14480 100644 --- a/queries/pizza.py +++ b/queries/pizza.py @@ -23,7 +23,6 @@ from typing import Dict, Optional, Set, cast import logging import random -from xxlimited import Str from query import Query, QueryStateDict from tree import Result, Node @@ -32,9 +31,11 @@ from queries.resources import ( FinalResource, ListResource, + DictResource, NumberResource, OrResource, Resource, + ResourceState, WrapperResource, ) from queries.dialogue import ( @@ -238,16 +239,24 @@ def _generate_order_answer( def _generate_pizza_answer( resource: WrapperResource, dsm: DialogueStateManager, result: Result ) -> Optional[AnswerTuple]: - (_, _, index) = resource.name.partition("_") + print("Generating pizza answer") + print("Generate pizza resource name: ", resource.name) + index = resource.name.split("_")[-1] type_resource: OrResource = cast( OrResource, dsm.get_resource("Type_{}".format(index)) ) + print("Type state: {}".format(type_resource.state)) size_resource: Resource = dsm.get_resource("Size_{}".format(index)) + print("Size state: {}".format(size_resource.state)) crust_resource: Resource = dsm.get_resource("Crust_{}".format(index)) + print("Crust state: {}".format(crust_resource.state)) if resource.is_unfulfilled: + print("Unfulfilled pizza") return gen_answer(resource.prompts["initial"]) if resource.is_partially_fulfilled: + print("Partially fulfilled pizza") if type_resource.is_confirmed and size_resource.is_unfulfilled: + print("Confirmed type, unfulfilled size") return gen_answer(resource.prompts["size"]) if ( type_resource.is_confirmed @@ -257,6 +266,19 @@ def _generate_pizza_answer( return gen_answer(resource.prompts["crust"]) +def _generate_type_answer( + resource: WrapperResource, dsm: DialogueStateManager, result: Result +) -> Optional[AnswerTuple]: + print("Generating type answer") + print("Generate type resource name: ", resource.name) + index = resource.name.split("_")[-1] + pizza_resource: Resource = dsm.get_resource("Pizza_{}".format(index)) + print("Pizza state: {}".format(pizza_resource.state)) + if resource.is_unfulfilled: + print("Unfulfilled type") + return gen_answer(resource.prompts["initial"]) + + def QPizzaDialogue(node: Node, params: QueryStateDict, result: Result) -> None: if "qtype" not in result: result.qtype = _PIZZA_QTYPE @@ -282,11 +304,19 @@ def QPizzaToppingsList(node: Node, params: QueryStateDict, result: Result) -> No print("Toppings in QPizzaToppingsList: ", result.get("toppings", {})) dsm: DialogueStateManager = Query.get_dsm(result) toppings: Dict[str, int] = result.get("toppings", {}) - resource = dsm.current_resource - (_, _, index) = resource.name.partition("_") + type_resource = dsm.current_resource + print("Current resource in topping list: ", type_resource.name) + index = type_resource.name.split("_")[-1] toppings_resource = dsm.get_resource("Toppings_{}".format(index)) - for topping in toppings: - ... # toppings_resource.data[topping] = toppings[topping] + pizza_resource = dsm.get_resource("Pizza_{}".format(index)) + print("Toppings resource: ", toppings_resource.name) + for (topping, amount) in toppings.items(): + toppings_resource.data[topping] = amount + print("Toppings in QPizzaToppingsList: ", toppings_resource.data) + dsm.set_resource_state(toppings_resource.name, ResourceState.CONFIRMED) + dsm.set_resource_state(type_resource.name, ResourceState.CONFIRMED) + dsm.set_resource_state(pizza_resource.name, ResourceState.CONFIRMED) + # TODO: This should not be done here, only for testing purposes def QPizzaToppingsWord(node: Node, params: QueryStateDict, result: Result) -> None: @@ -319,6 +349,7 @@ def QPizzaMushroomWord(node: Node, params: QueryStateDict, result: Result) -> No _ANSWERING_FUNCTIONS: AnsweringFunctionMap = { "PizzaOrder": _generate_order_answer, "Pizza": _generate_pizza_answer, + "Type": _generate_type_answer, } diff --git a/queries/resources.py b/queries/resources.py index 23186a79..cf7ac486 100644 --- a/queries/resources.py +++ b/queries/resources.py @@ -133,6 +133,13 @@ def format_data(self, format_func: Optional[Callable[[Any], str]] = None) -> str return ",".join(str(x) for x in self.data) +@dataclass(eq=False, repr=False) +class DictResource(Resource): + """Resource representing a dictionary of items.""" + + data: Dict[str, Any] = field(default_factory=dict) + + # TODO: ? # ExactlyOneResource (choose one resource from options) # SetResource (a set of resources)? @@ -244,6 +251,7 @@ class WrapperResource(Resource): @dataclass(eq=False, repr=False) class OrResource(WrapperResource): exclusive: bool = False # Only one of the resources should be fulfilled + # TODO: Add choose_resource() method to skip other options @dataclass(eq=False, repr=False) @@ -265,6 +273,7 @@ class FinalResource(Resource): "DatetimeResource": DatetimeResource, "FinalResource": FinalResource, "ListResource": ListResource, + "DictResource": DictResource, "NumberResource": NumberResource, "OrResource": OrResource, "TimeResource": TimeResource, From ddc7d9cb393a401fcc3e76c8545ebf2673474826 Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Wed, 20 Jul 2022 15:53:36 +0000 Subject: [PATCH 243/371] crust grammar --- queries/grammars/pizza.grammar | 65 ++++++++++---- queries/pizza.py | 156 +-------------------------------- 2 files changed, 53 insertions(+), 168 deletions(-) diff --git a/queries/grammars/pizza.grammar b/queries/grammars/pizza.grammar index 7125c422..b059aee6 100644 --- a/queries/grammars/pizza.grammar +++ b/queries/grammars/pizza.grammar @@ -1,10 +1,12 @@ # TODO: 2x of a topping. "Tvöfalt", "mikið", "extra" # TODO: Ban more than two instances of a topping. # TODO: Fix the toppings being a set. Doesn't handle "Ég vil skinku, ólífur og auka skinku." +# TODO: Add to PizzaRequestBare, start conversation with an order. /þgf = þgf /ef = ef +# Currently, the Hotword needs to be top-level, because the QPizza nonterminal is banned. Query → QPizzaHotWord '?'? | QPizza '?'? @@ -15,42 +17,54 @@ QPizza → QPizzaQuery → QPizzaDialogue +# Hotwords are used to initialize the conversation. QPizzaHotWord → - QPizzaWord/nf - | QPizzaRequestBare + QPizzaWord/nf # e.g. "Pítsa" + | QPizzaRequestBare # e.g. "Ég vil panta pizzu." +# Doesn't allow for any specification, i.e. number of pizzas: "Ég vil panta tvær flatbökur." QPizzaRequestBare -> - QPizzaEgVil QPizzaKaupaFaraFaPanta QPizzaWord/þf + QPizzaEgVil QPizzaKaupaFaraFaPanta? QPizzaWord/þf QPizzaDialogue → - QPizzaNumberAnswer - | QPizzaToppingsAnswer - | QPizzaSizeAnswer + QPizzaNumberAnswer #Answer to the question "How many pizzas do you want?" + | QPizzaToppingsAnswer #Answer to the question "What toppings do you want?" + | QPizzaSizeAnswer #Answer to the question "What size do you want?" + | QPizzaCrustAnswer #Answer to the question "What kind of crust do you want?" QPizzaNumberAnswer → - QPizzaEgVil QPizzaKaupaFaraFaPanta QPizzaNum/þf QPizzaWord/þf - | QPizzaNum/þf QPizzaWord/þf? + QPizzaEgVil QPizzaKaupaFaraFaPanta? QPizzaNum/þf QPizzaWord/þf # e.g. "Ég vil kaupa tvær pítsur." + | QPizzaNum/þf QPizzaWord/þf? # e.g. "Tvær pítsur." QPizzaToppingsAnswer -> - QPizzaEgVil? QPizzaToppingsList "á"? QPizzaWord/þf? + QPizzaEgVil? QPizzaToppingsList/þf QPizzaAPitsunaPhrase? # e.g. "Ég vil skinku, ólífur og pepperóní á pítsuna." + | QPizzaEgVil? QPizzaWord/þf "með" QPizzaToppingsList/þgf # e.g. "Ég vil pizzu með ólífum og ananas." +# It is common to say "miðstærð af pítsu", which is handled separately here. QPizzaSizeAnswer -> - QPizzaEgVil? QPizzaSize/þf QPizzaWord/þf? - | QPizzaSize/nf - | QPizzaEgVil? QPizzaMediumWord QPizzaAfPitsuPhrase # Common to say "miðstærð", or "Ég vil miðstærð af pítsu." + QPizzaEgVil? QPizzaSize/þf QPizzaWord/þf? # e.g. "Ég vil stóra pítsu." + | QPizzaSize/nf # e.g. "Tólf tommur." + | QPizzaEgVil? QPizzaMediumWord QPizzaAfPitsuPhrase # e.g. "Ég vil miðstærð af pítsu." -QPizzaToppingsList -> - QPizzaToppingsWordWrapper/þf* 'og:st'? QPizzaToppingsWord/þf +QPizzaCrustAnswer -> + QPizzaEgVil? QPizzaCrustPhrase/þf QPizzaAPitsunaPhrase? # e.g. "Ég vil klassískan botn." + | QPizzaCrustPhrase/nf # e.g. "Ítalskur botn." + +QPizzaToppingsList/fall -> + QPizzaToppingsWordWrapper/fall* 'og:st'? QPizzaToppingsWord/fall QPizzaSize/fall -> QPizzaSizeLarge/fall | QPizzaSizeMedium/fall | QPizzaSizeSmall/fall +# This wrapper is necessary to prevent the module from reading toppings multiple times. +# Otherwise, it reads "ananas skinku og pepperóní" as "ananas" "skinka" "pepperóní" and "ananas skinka" (2022/07/20). QPizzaToppingsWordWrapper/fall -> QPizzaToppingsWord/fall -# Toppings that are transcribed in different ways are in separate nonterminals. +# Toppings that are transcribed in different ways are in separate nonterminals for clarity. +# This also helps standardize the handling of each topping in the module, i.e. not reading "ólífa" and "ólíva" as separate toppings. QPizzaToppingsWord/fall -> QPizzaMushroomWord/fall | QPizzaPepperoniWord/fall @@ -58,9 +72,15 @@ QPizzaToppingsWord/fall -> | 'skinka:kvk'/fall | QPizzaOliveWord/fall +QPizzaCrustPhrase/fall -> + QPizzaCrustType/fall QPizzaCrustWord/fall? + QPizzaAfPitsuPhrase -> "af" QPizzaWord/þgf +QPizzaAPitsunaPhrase -> + "á" QPizzaWord/þgf + # A large pizza at Domino's is typically thought to be 16", some believe it to be 15". # The actual size is 14.5". QPizzaSizeLarge/fall -> @@ -81,6 +101,10 @@ QPizzaSizeSmall/fall -> 'lítil:lo'/fall | QPizzaNineWord 'tomma:kvk'/fall? +QPizzaCrustType/fall -> + QPizzaItalianWord/fall + | QPizzaClassicWord/fall + QPizzaEgVil → "ég"? "vil" | "ég" "vill" @@ -125,6 +149,15 @@ QPizzaNineWord -> "9" | "níu" +QPizzaItalianWord/fall -> + 'ítalskur:lo'/fall + +QPizzaClassicWord/fall -> + 'klassískur:lo'/fall + +QPizzaCrustWord/fall -> + 'botn:kk'/fall + QPizzaNum/fall → # to is a declinable number word ('tveir/tvo/tveim/tveggja') # töl is an undeclinable number word ('sautján') @@ -143,4 +176,4 @@ QPizzaOliveWord/fall -> QPizzaMushroomWord/fall -> 'sveppur:kk'/fall - | "Sveppi" \ No newline at end of file + | 'Sveppi:kk' diff --git a/queries/pizza.py b/queries/pizza.py index 1c07eca7..af7e3c1e 100644 --- a/queries/pizza.py +++ b/queries/pizza.py @@ -23,11 +23,10 @@ from typing import Dict, Optional, Set, cast import logging import random -from xxlimited import Str from query import Query, QueryStateDict from tree import Result, Node -from queries import AnswerTuple, gen_answer, natlang_seq, parse_num +from queries import AnswerTuple, gen_answer, natlang_seq, parse_num, read_grammar_file from queries.num import number_to_text, numbers_to_ordinal, numbers_to_text from queries.resources import ( FinalResource, @@ -68,156 +67,9 @@ def help_text(lemma: str) -> str: QUERY_NONTERMINALS = {"QPizza"}.union(HOTWORD_NONTERMINALS) # The context-free grammar for the queries recognized by this plug-in module -GRAMMAR = """ - -# TODO: 2x of a topping. "Tvöfalt", "mikið", "extra" -# TODO: Ban more than two instances of a topping. -# TODO: Fix the toppings being a set. Doesn't handle "Ég vil skinku, ólífur og auka skinku." - -/þgf = þgf -/ef = ef - -Query → - QPizzaHotWord '?'? - | QPizza '?'? - -QPizza → - QPizzaQuery - -QPizzaQuery → - QPizzaDialogue - -QPizzaHotWord → - QPizzaWord/nf - | QPizzaRequestBare - -QPizzaRequestBare -> - QPizzaEgVil QPizzaKaupaFaraFaPanta QPizzaWord/þf - -QPizzaDialogue → - QPizzaNumberAnswer - | QPizzaToppingsAnswer - | QPizzaSizeAnswer - -QPizzaNumberAnswer → - QPizzaEgVil QPizzaKaupaFaraFaPanta QPizzaNum/þf QPizzaWord/þf - | QPizzaNum/þf QPizzaWord/þf? - -QPizzaToppingsAnswer -> - QPizzaEgVil? QPizzaToppingsList "á"? QPizzaWord/þf? - -QPizzaSizeAnswer -> - QPizzaEgVil? QPizzaSize/þf QPizzaWord/þf? - | QPizzaSize/nf - | QPizzaEgVil? QPizzaMediumWord QPizzaAfPitsuPhrase # Common to say "miðstærð", or "Ég vil miðstærð af pítsu." - -QPizzaToppingsList -> - QPizzaToppingsWordWrapper/þf* 'og:st'? QPizzaToppingsWord/þf - -QPizzaSize/fall -> - QPizzaSizeLarge/fall - | QPizzaSizeMedium/fall - | QPizzaSizeSmall/fall - -QPizzaToppingsWordWrapper/fall -> - QPizzaToppingsWord/fall - -# Toppings that are transcribed in different ways are in separate nonterminals. -QPizzaToppingsWord/fall -> - QPizzaMushroomWord/fall - | QPizzaPepperoniWord/fall - | 'ananas:kk'/fall - | 'skinka:kvk'/fall - | QPizzaOliveWord/fall - -QPizzaAfPitsuPhrase -> - "af" QPizzaWord/þgf - -# A large pizza at Domino's is typically thought to be 16", some believe it to be 15". -# The actual size is 14.5". -QPizzaSizeLarge/fall -> - 'stór:lo'/fall - | QPizzaSixteenWord 'tomma:kvk'/fall? - | QPizzaFifteenWord 'tomma:kvk'/fall? - | QPizzaFourteenPointFiveWord 'tomma:kvk'/fall? - -QPizzaSizeMedium/fall -> - 'millistór:lo'/fall - | 'meðalstór:lo'/fall - | QPizzaTwelveWord 'tomma:kvk'/fall? - -QPizzaMediumWord -> - "miðstærð" - -QPizzaSizeSmall/fall -> - 'lítil:lo'/fall - | QPizzaNineWord 'tomma:kvk'/fall? - -QPizzaEgVil → - "ég"? "vil" - | "ég" "vill" - | "mig" "langar" "að" - | "mig" "langar" "í" - -QPizzaKaupaFaraFaPanta → - "kaupa" "mér"? - | "fá" "mér"? - | "panta" "mér"? - -QPizzaWord/fall → - 'pizza:kvk'/fall - | 'pitsa:kvk'/fall - | 'pítsa:kvk'/fall - | 'flatbaka:kvk'/fall - -QPizzaSixteenWord -> - "16" - | "sextán" - -QPizzaFifteenWord -> - "15" - | "fimmtán" - -QPizzaFourteenPointFiveWord -> - QPizzaFourteenWord "komma" QPizzaFiveWord - -QPizzaFourteenWord -> - "14" - | "fjórtán" - -QPizzaFiveWord -> - "5" - | "fimm" - -QPizzaTwelveWord -> - "12" - | "tólf" - -QPizzaNineWord -> - "9" - | "níu" - -QPizzaNum/fall → - # to is a declinable number word ('tveir/tvo/tveim/tveggja') - # töl is an undeclinable number word ('sautján') - # tala is a number ('17') - to | töl | tala - -QPizzaPepperoniWord/fall -> - 'pepperóní:hk'/fall - | "pepperoni" - | "pepperóni" - | "pepperoní" - -QPizzaOliveWord/fall -> - 'ólífa:kvk'/fall - | 'ólíva:kvk'/fall - -QPizzaMushroomWord/fall -> - 'sveppur:kk'/fall - | "Sveppi" - -""" +GRAMMAR = read_grammar_file( + "pizza", +) def banned_nonterminals(query: str) -> Set[str]: From 7c0181896a3cc8c797a1ee56c8dc1920f178d0e0 Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Wed, 20 Jul 2022 15:57:47 +0000 Subject: [PATCH 244/371] added nonterminal function template --- queries/grammars/pizza.grammar | 2 +- queries/pizza.py | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/queries/grammars/pizza.grammar b/queries/grammars/pizza.grammar index b059aee6..a600fe48 100644 --- a/queries/grammars/pizza.grammar +++ b/queries/grammars/pizza.grammar @@ -34,7 +34,7 @@ QPizzaDialogue → QPizzaNumberAnswer → QPizzaEgVil QPizzaKaupaFaraFaPanta? QPizzaNum/þf QPizzaWord/þf # e.g. "Ég vil kaupa tvær pítsur." - | QPizzaNum/þf QPizzaWord/þf? # e.g. "Tvær pítsur." + | QPizzaNum/nf QPizzaWord/nf? # e.g. "Tvær pítsur." QPizzaToppingsAnswer -> QPizzaEgVil? QPizzaToppingsList/þf QPizzaAPitsunaPhrase? # e.g. "Ég vil skinku, ólífur og pepperóní á pítsuna." diff --git a/queries/pizza.py b/queries/pizza.py index dc50ecd0..0e1fa80a 100644 --- a/queries/pizza.py +++ b/queries/pizza.py @@ -187,6 +187,30 @@ def QPizzaNum(node: Node, params: QueryStateDict, result: Result) -> None: result.number = number +def QPizzaSizeLarge(node: Node, params: QueryStateDict, result: Result) -> None: + return + + +def QPizzaSizeMedium(node: Node, params: QueryStateDict, result: Result) -> None: + return + + +def QPizzaMediumWord(node: Node, params: QueryStateDict, result: Result) -> None: + return + + +def QPizzaSizeSmall(node: Node, params: QueryStateDict, result: Result) -> None: + return + + +def QPizzaItalianWord(node: Node, params: QueryStateDict, result: Result) -> None: + return + + +def QPizzaClassicWord(node: Node, params: QueryStateDict, result: Result) -> None: + return + + def QPizzaPepperoniWord(node: Node, params: QueryStateDict, result: Result) -> None: result.real_name = "pepperóní" From c47bf241e7627662265d8cfa2a464c94733ff503 Mon Sep 17 00:00:00 2001 From: Logi E Date: Wed, 20 Jul 2022 16:00:39 +0000 Subject: [PATCH 245/371] prettification --- queries/dialogue.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index 0cb02a2c..6177341e 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -280,12 +280,12 @@ def add_dynamic_resource(self, resource_name: str, parent_name: str) -> None: dynamic_resource["type"] = "Resource" # Updating required resources to have indexed name dynamic_resource["requires"] = [ - "{res}_{index}".format(res=res, index=dynamic_resource_index) + f"{res}_{dynamic_resource_index}" for res in dynamic_resource.get("requires", []) ] # Updating dynamic resource name to have indexed name - dynamic_resource["name"] = "{name}_{index}".format( - name=dynamic_resource["name"], index=dynamic_resource_index + dynamic_resource["name"] = ( + f"{dynamic_resource['name']}_" f"{dynamic_resource_index}" ) # Adding dynamic resource to list dynamic_resources[dynamic_resource["name"]] = RESOURCE_MAP[ @@ -295,9 +295,7 @@ def add_dynamic_resource(self, resource_name: str, parent_name: str) -> None: order_index=order_index, ) # Indexed resource name of the dynamic resource - indexed_resource_name = "{name}_{index}".format( - name=resource_name, index=dynamic_resource_index - ) + indexed_resource_name = f"{resource_name}_{dynamic_resource_index}" resource: Resource = dynamic_resources[indexed_resource_name] # Appending resource to required list of parent resource parent_resource.requires.append(indexed_resource_name) @@ -382,21 +380,20 @@ def get_answer( self._current_resource, self, result ) return ans - # Iterate through resources (inorder traversal) + # Iterate through resources (postorder traversal) # until one generates an answer - self._answer_tuple = self._get_answer_postorder( - self._current_resource, result, set() - ) + self._answer_tuple = self._get_answer(self._current_resource, result, set()) return self._answer_tuple - def _get_answer_postorder( + # TODO: Can we remove this function? + def _get_answer( self, curr_resource: Resource, result: Any, finished: Set[Resource] ) -> Optional[AnswerTuple]: for resource in self._resource_graph[curr_resource]["children"]: if resource not in finished: finished.add(resource) - ans = self._get_answer_postorder(resource, result, finished) + ans = self._get_answer(resource, result, finished) if ans: return ans if curr_resource.name in self._answering_functions: From 29c1fc103236d49da6f595c3a2655bcabce5c2c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Wed, 20 Jul 2022 17:45:18 +0000 Subject: [PATCH 246/371] Added update_wrapper_state method to dsm --- queries/dialogue.py | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/queries/dialogue.py b/queries/dialogue.py index 0cb02a2c..2d610471 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -23,6 +23,7 @@ from queries import AnswerTuple from queries.resources import ( RESOURCE_MAP, + OrResource, Resource, DialogueJSONDecoder, DialogueJSONEncoder, @@ -470,6 +471,47 @@ def _recurse_resources(resource: Resource) -> None: _recurse_resources(self._resources["Final"]) return curr_res or self._resources["Final"] + def skip_other_resources(self, or_resource: OrResource, resource: Resource) -> None: + """Skips other resources in the or resource""" + assert isinstance( + or_resource, OrResource + ), f"{or_resource} is not an OrResource" + for res in or_resource.requires: + if res != resource.name: + self.set_resource_state(res, ResourceState.SKIPPED) + + def update_wrapper_state(self, wrapper: WrapperResource) -> None: + """ + Updates the state of the wrapper resource + based on the state of its children. + """ + print("UPDATING WRAPPER STATE", wrapper.state) + if wrapper.state == ResourceState.UNFULFILLED: + print("Wrapper is unfulfilled") + if all( + [ + child.state == ResourceState.UNFULFILLED + for child in self._resource_graph[wrapper]["children"] + ] + ): + print("All children are unfulfilled") + return + print("At least one child is fulfilled") + self.set_resource_state(wrapper.name, ResourceState.PARTIALLY_FULFILLED) + elif wrapper.state == ResourceState.PARTIALLY_FULFILLED: + print("Wrapper is partially fulfilled") + if any( + [ + child.state != ResourceState.CONFIRMED + for child in self._resource_graph[wrapper]["children"] + ] + ): + print("At least one child is not confirmed") + self.set_resource_state(wrapper.name, ResourceState.PARTIALLY_FULFILLED) + return + print("All children are confirmed") + self.set_resource_state(wrapper.name, ResourceState.FULFILLED) + def finish_dialogue(self) -> None: """Set the dialogue as finished.""" self._finished = True From 27f7be3cde7a0c6e5f57997ba7b36c31ea92372e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Wed, 20 Jul 2022 17:46:14 +0000 Subject: [PATCH 247/371] Moved grammar to specific grammar files for theater and fruitseller --- queries/fruitseller.py | 127 +------------- queries/grammars/fruitseller.grammar | 113 +++++++++++++ queries/grammars/theater.grammar | 220 ++++++++++++++++++++++++ queries/theater.py | 239 ++------------------------- 4 files changed, 353 insertions(+), 346 deletions(-) create mode 100644 queries/grammars/fruitseller.grammar create mode 100644 queries/grammars/theater.grammar diff --git a/queries/fruitseller.py b/queries/fruitseller.py index c37f9417..55d99e80 100644 --- a/queries/fruitseller.py +++ b/queries/fruitseller.py @@ -6,7 +6,14 @@ from query import Query, QueryStateDict from tree import Result, Node, TerminalNode from reynir import NounPhrase -from queries import gen_answer, AnswerTuple, parse_num, natlang_seq, sing_or_plur +from queries import ( + gen_answer, + AnswerTuple, + parse_num, + natlang_seq, + read_grammar_file, + sing_or_plur, +) from queries.dialogue import ( AnsweringFunctionMap, DialogueStateManager, @@ -36,123 +43,7 @@ # The context-free grammar for the queries recognized by this plug-in module -GRAMMAR = """ - -Query → - QFruitStartQuery | QFruitSeller - -QFruitSeller → - QFruitSellerQuery - -QFruitSellerQuery → - QFruitQuery '?'? - | QFruitDateQuery '?'? - | QFruitInfoQuery '?'? - -QFruitInfoQuery → - "hver"? "er"? "staðan" "á"? "ávaxtapöntuninni"? - -QFruitStartQuery → - "ávöxtur" '?'? - | "postur" '?'? - | "póstur" '?'? - | "ég" "vill" "kaupa"? "ávexti" '?'? - | "ég" "vil" "kaupa"? "ávexti" '?'? - | "mig" "langar" "að" "kaupa" "ávexti" "hjá"? "þér"? '?'? - | "mig" "langar" "að" "panta" "ávexti" "hjá"? "þér"? '?'? - | "get" "ég" "keypt" "ávexti" "hjá" "þér" '?'? - -QFruitQuery → - QAddFruitQuery - | QRemoveFruitQuery - | QChangeFruitQuery - | QFruitOptionsQuery - | QFruitYes - | QFruitNo - | QFruitCancelOrder - -QAddFruitQuery → - "já"? "má"? "ég"? "fá"? QFruitList - | "já"? "get" "ég" "fengið" QFruitList - | "já"? "gæti" "ég" "fengið" QFruitList - | "já"? "ég" "vil" "fá" QFruitList - | "já"? "ég" "vill" "fá" QFruitList - | "já"? "ég" "vil" "panta" QFruitList - | "já"? "ég" "vill" "panta" QFruitList - | "já"? "ég" "vil" "kaupa" QFruitList - | "já"? "ég" "vill" "kaupa" QFruitList - | "já"? "mig" "langar" "að" "fá" QFruitList - | "já"? "mig" "langar" "að" "kaupa" QFruitList - | "já"? "mig" "langar" "að" "panta" QFruitList - -QRemoveFruitQuery → - "taktu" "út" QFruitList - | "slepptu" QFruitList - | "ég"? "vil"? "sleppa" QFruitList - | "ég" "vill" "sleppa" QFruitList - | "ég" "hætti" "við" QFruitList - | "ég" "vil" "ekki" QFruitList - | "ég" "vill" "ekki" QFruitList - -QChangeFruitQuery → - QChangeStart QFruitList QChangeConnector QFruitList - -QChangeStart → - "breyttu" - | "ég" "vil" "frekar" - | "ég" "vill" "frekar" - | "ég" "vil" "skipta" "út" - | "ég" "vill" "skipta" "út" - | "ég" "vil" "breyta" - | "ég" "vill" "breyta" - -QChangeConnector → - "en" | "í" "staðinn" "fyrir" - -QFruitOptionsQuery → - "hvað" "er" "í" "boði" - | "hverjir" "eru" "valmöguleikarnir" - | "hvaða" "valmöguleikar" "eru" "í" "boði" - | "hvaða" "valmöguleikar" "eru" "til" - | "hvaða" "ávexti" "ertu" "með" - | "hvaða" "ávextir" "eru" "í" "boði" - -QFruitList → QFruitNumOfFruit QFruitNumOfFruit* - -QFruitNumOfFruit → QFruitNum? QFruit "og"? - -QFruitNum → - # to is a declinable number word ('tveir/tvo/tveim/tveggja') - # töl is an undeclinable number word ('sautján') - # tala is a number ('17') - to | töl | tala - -QFruit → 'banani' | 'epli' | 'pera' | 'appelsína' - -QFruitYes → "já" "já"* | "endilega" | "já" "takk" | "játakk" | "já" "þakka" "þér" "fyrir" | "já" "takk" "kærlega" "fyrir"? | "jább" "takk"? - -QFruitNo → "nei" "takk"? | "nei" "nei"* | "neitakk" | "ómögulega" - -QFruitCancelOrder → "ég" "hætti" "við" - | "ég" "vil" "hætta" "við" "pöntunina"? - | "ég" "vill" "hætta" "við" "pöntunina" - -QFruitDateQuery → - QFruitDateTime - | QFruitDate - | QFruitTime - -QFruitDateTime → - tímapunkturafs - -QFruitDate → - dagsafs - | dagsföst - -QFruitTime → - "klukkan"? tími - -""" +GRAMMAR = read_grammar_file("fruitseller") def banned_nonterminals(q: Query) -> Set[str]: diff --git a/queries/grammars/fruitseller.grammar b/queries/grammars/fruitseller.grammar new file mode 100644 index 00000000..5cb54a43 --- /dev/null +++ b/queries/grammars/fruitseller.grammar @@ -0,0 +1,113 @@ +Query → + QFruitStartQuery | QFruitSeller + +QFruitSeller → + QFruitSellerQuery + +QFruitSellerQuery → + QFruitQuery '?'? + | QFruitDateQuery '?'? + | QFruitInfoQuery '?'? + +QFruitInfoQuery → + "hver"? "er"? "staðan" "á"? "ávaxtapöntuninni"? + +QFruitStartQuery → + "ávöxtur" '?'? + | "postur" '?'? + | "póstur" '?'? + | "ég" "vill" "kaupa"? "ávexti" '?'? + | "ég" "vil" "kaupa"? "ávexti" '?'? + | "mig" "langar" "að" "kaupa" "ávexti" "hjá"? "þér"? '?'? + | "mig" "langar" "að" "panta" "ávexti" "hjá"? "þér"? '?'? + | "get" "ég" "keypt" "ávexti" "hjá" "þér" '?'? + +QFruitQuery → + QAddFruitQuery + | QRemoveFruitQuery + | QChangeFruitQuery + | QFruitOptionsQuery + | QFruitYes + | QFruitNo + | QFruitCancelOrder + +QAddFruitQuery → + "já"? "má"? "ég"? "fá"? QFruitList + | "já"? "get" "ég" "fengið" QFruitList + | "já"? "gæti" "ég" "fengið" QFruitList + | "já"? "ég" "vil" "fá" QFruitList + | "já"? "ég" "vill" "fá" QFruitList + | "já"? "ég" "vil" "panta" QFruitList + | "já"? "ég" "vill" "panta" QFruitList + | "já"? "ég" "vil" "kaupa" QFruitList + | "já"? "ég" "vill" "kaupa" QFruitList + | "já"? "mig" "langar" "að" "fá" QFruitList + | "já"? "mig" "langar" "að" "kaupa" QFruitList + | "já"? "mig" "langar" "að" "panta" QFruitList + +QRemoveFruitQuery → + "taktu" "út" QFruitList + | "slepptu" QFruitList + | "ég"? "vil"? "sleppa" QFruitList + | "ég" "vill" "sleppa" QFruitList + | "ég" "hætti" "við" QFruitList + | "ég" "vil" "ekki" QFruitList + | "ég" "vill" "ekki" QFruitList + +QChangeFruitQuery → + QChangeStart QFruitList QChangeConnector QFruitList + +QChangeStart → + "breyttu" + | "ég" "vil" "frekar" + | "ég" "vill" "frekar" + | "ég" "vil" "skipta" "út" + | "ég" "vill" "skipta" "út" + | "ég" "vil" "breyta" + | "ég" "vill" "breyta" + +QChangeConnector → + "en" | "í" "staðinn" "fyrir" + +QFruitOptionsQuery → + "hvað" "er" "í" "boði" + | "hverjir" "eru" "valmöguleikarnir" + | "hvaða" "valmöguleikar" "eru" "í" "boði" + | "hvaða" "valmöguleikar" "eru" "til" + | "hvaða" "ávexti" "ertu" "með" + | "hvaða" "ávextir" "eru" "í" "boði" + +QFruitList → QFruitNumOfFruit QFruitNumOfFruit* + +QFruitNumOfFruit → QFruitNum? QFruit "og"? + +QFruitNum → + # to is a declinable number word ('tveir/tvo/tveim/tveggja') + # töl is an undeclinable number word ('sautján') + # tala is a number ('17') + to | töl | tala + +QFruit → 'banani' | 'epli' | 'pera' | 'appelsína' + +QFruitYes → "já" "já"* | "endilega" | "já" "takk" | "játakk" | "já" "þakka" "þér" "fyrir" | "já" "takk" "kærlega" "fyrir"? | "jább" "takk"? + +QFruitNo → "nei" "takk"? | "nei" "nei"* | "neitakk" | "ómögulega" + +QFruitCancelOrder → "ég" "hætti" "við" + | "ég" "vil" "hætta" "við" "pöntunina"? + | "ég" "vill" "hætta" "við" "pöntunina" + +QFruitDateQuery → + QFruitDateTime + | QFruitDate + | QFruitTime + +QFruitDateTime → + tímapunkturafs + +QFruitDate → + dagsafs + | dagsföst + +QFruitTime → + "klukkan"? tími \ No newline at end of file diff --git a/queries/grammars/theater.grammar b/queries/grammars/theater.grammar new file mode 100644 index 00000000..a7c02f3c --- /dev/null +++ b/queries/grammars/theater.grammar @@ -0,0 +1,220 @@ +Query → + QTheaterHotWord | QTheater + +QTheater → QTheaterQuery + +QTheaterQuery → + QTheaterDialogue '?'? + +QTheaterHotWord → + QTheaterNames '?'? + | QTheaterEgVil? QTheaterKaupaFaraFaPanta "leikhúsmiða" '?'? + | QTheaterEgVil? QTheaterKaupaFaraFaPanta "miða" "í" QTheaterNames '?'? + | QTheaterEgVil? QTheaterKaupaFaraFaPanta "miða" "á" QTheaterNames "sýningu" '?'? + | QTheaterEgVil? QTheaterKaupaFaraFaPanta QTheaterNames '?'? + | QTheaterEgVil? QTheaterKaupaFaraFaPanta "leikhússýningu" '?'? + +QTheaterNames → + 'leikhús' + | 'þjóðleikhús' + | 'Þjóðleikhús' + | 'Borgarleikhús' + | 'borgarleikhús' + + +QTheaterKaupaFaraFaPanta → + "kaupa" "mér"? + | "fara" "á" + | "fara" "í" + | "fá" + | "panta" + +QTheaterDialogue → + QTheaterShowQuery + | QTheaterShowDateQuery + | QTheaterMoreDates + | QTheaterPreviousDates + | QTheaterShowSeatCountQuery + | QTheaterShowLocationQuery + | QTheaterShowPrice + | QTheaterShowLength + | QTheaterOptions + | QTheaterYes + | QTheaterNo + | QTheaterCancel + | QTheaterStatus + +QTheaterOptions → + QTheaterGeneralOptions + | QTheaterShowOptions + | QTheaterDateOptions + | QTheaterRowOptions + | QTheaterSeatOptions + +QTheaterGeneralOptions → + "hverjir"? "eru"? "valmöguleikarnir" + | "hvert" "er" "úrvalið" + | "hvað" "er" "í" "boði" + +QTheaterShowOptions → + "hvaða" "sýningar" "eru" "í" "boði" + +QTheaterDateOptions → + "hvaða" "dagsetningar" "eru" "í" "boði" + | "hvaða" "dagar" "eru" "í" "boði" + | "hvaða" "dagsetningar" "er" "hægt" "að" "velja" "á" "milli" + +QTheaterRowOptions → + "hvaða" "raðir" "eru" QTheaterIBodiLausar + | "hvaða" "röð" "er" QTheaterIBodiLausar + | "hvaða" "bekkir" "eru" QTheaterIBodiLausar + | "hvaða" "bekkur" "er" QTheaterIBodiLausar + +QTheaterSeatOptions → + "hvaða" "sæti" "eru" QTheaterIBodiLausar + | "hverjir" "eru" "sæta" "valmöguleikarnir" + +QTheaterIBodiLausar → + "í" "boði" + | "lausar" + | "lausir" + | "laus" + +QTheaterShowQuery → QTheaterEgVil? "velja" 'sýning' QTheaterShowName + > QTheaterEgVil? "fara" "á" 'sýning' QTheaterShowName + > QTheaterShowOnlyName + +QTheaterShowOnlyName → QTheaterShowName + +QTheaterShowName → Nl + + +QTheaterShowPrice → + "hvað" "kostar" "einn"? 'miði' + | "hvað" "kostar" "1"? 'miði' + | "hvað" "kostar" "einn"? 'miðinn' "á" "sýninguna" + +QTheaterShowLength → + "hvað" "er" "sýningin" "löng" + +QTheaterShowDateQuery → + QTheaterEgVil? "fara"? "á"? 'sýning'? QTheaterShowDate + +QTheaterShowDate → + QTheaterDateTime | QTheaterDate | QTheaterTime + +QTheaterDateTime → + tímapunkturafs + +QTheaterDate → + dagsafs + | dagsföst + +QTheaterTime → + "klukkan"? tími + +QTheaterMoreDates → + "hverjar"? "eru"? "næstu" "þrjár"? QSyningarTimar + | "hverjir" "eru" "næstu" "þrír"? QSyningarTimar + | "get" "ég" "fengið" "að" "sjá" "næstu" "þrjá"? QSyningarTimar + | QTheaterEgVil? "sjá"? "fleiri" QSyningarTimar + | QTheaterEgVil? "sjá"? "næstu" "þrjá"? QSyningarTimar + +QTheaterPreviousDates → + QTheaterEgVil "sjá" "fyrri" QSyningarTimar + | "hvaða" QSyningarTimar "eru" "á" "undan" "þessum"? + | "get" "ég" "fengið" "að" "sjá" QSyningarTimar "á" "undan" "þessum"? + | QTheaterEgVil? "sjá"? QSyningarTimar "á" "undan" "þessum"? + +QSyningarTimar → + 'sýningartíma' + | "dagsetningar" + | "sýningartímana" + +QTheaterShowSeatCountQuery → + QTheaterSeatCountNum + | QTheaterEgVil? "fá"? QTheaterNum "sæti" + | QTheaterEgVil? "fá"? QTheaterNum "miða" + | QTheaterEgVil? "fá"? QTheaterNum "miða" "á" "sýninguna" + +QTheaterSeatCountNum → + to | töl | tala + +QTheaterShowLocationQuery → + QTheaterShowRow + | QTheaterShowSeats + +QTheaterShowRow → + QTheaterRodBekkur + | QTheaterEgVil QTheaterVeljaRod QTheaterRodBekkur + +QTheaterVeljaRod → + "velja" "sæti"? "í"? + | "sitja" "í" + | "fá" "sæti" "í" + | "fá" "sæti" "á" + +QTheaterRodBekkur → + QTheaterRowNum + | QTheaterRodBekk "númer"? QTheaterNum + | QTheaterNum "bekk" + | QTheaterNum "röð" + +QTheaterRowNum → + to | töl | tala + +QTheaterShowSeats → + QTheaterShowSeatsNum + | QTheaterEgVil? "sæti"? "númer"? QTheaterNum "til" QTheaterNum? + | QTheaterEgVil? "sæti" "númer"? QTheaterNum "og" QTheaterNum? + | "ég" "vil" "sitja" "í" "röð" "númer" QTheaterNum + +QTheaterShowSeatsNum → + to | töl | tala + +QTheaterDateOptions → + "hvaða" "dagsetningar" "eru" "í" "boði" + +QTheaterRodBekk → "röð" | "bekk" + +QTheaterEgVil → + "ég"? "vil" + | "ég" "vill" + | "mig" "langar" "að" + | "mig" "langar" "í" + +QTheaterNum → + # to is a declinable number word ('tveir/tvo/tveim/tveggja') + # töl is an undeclinable number word ('sautján') + # tala is a number ('17') + to | töl | tala + +QTheaterYes → "já" "já"* | "endilega" | "já" "takk" | "játakk" | "já" "þakka" "þér" "fyrir" | "já" "takk" "kærlega" "fyrir"? | "jább" "takk"? + +QTheaterNo → "nei" "takk"? | "nei" "nei"* | "neitakk" | "ómögulega" + +QTheaterCancel → "ég" "hætti" "við" + | QTheaterEgVil "hætta" "við" QTheaterPontun? + +QTheaterStatus → + "staðan" + | "hver" "er" "staðan" "á" QTheaterPontun? + | "hver" "er" "staðan" + | "segðu" "mér" "stöðuna" + | "hvernig" "er" "staðan" + | "hvar" "var" "ég" + | "hvert" "var" "ég" 'kominn' + | "hvert" "var" "ég" 'kominn' "í" QTheaterPontun + | "hver" "var" "staðan" "á"? QTheaterPontun + | QTheaterEgVil "halda" "áfram" "með" QTheaterPontun + +QTheaterPontun → + "pöntuninni" + | "leikhús" "pöntuninni" + | "leikhús" "pöntunina" + | "leikhúsmiða" "pöntuninni" + | "leikhúsmiða" "pöntunina" + | "leikhúsmiðapöntunina" + | "leikhúsmiðapöntuninni" + | "leikhús" "miða" "pöntunina" + | "leikhús" "miða" "pöntuninni" \ No newline at end of file diff --git a/queries/theater.py b/queries/theater.py index 4164eab4..36d03dee 100644 --- a/queries/theater.py +++ b/queries/theater.py @@ -31,7 +31,14 @@ from settings import changedlocale from query import Query, QueryStateDict from tree import Result, Node, TerminalNode -from queries import AnswerTuple, gen_answer, natlang_seq, parse_num, time_period_desc +from queries import ( + AnswerTuple, + gen_answer, + natlang_seq, + parse_num, + read_grammar_file, + time_period_desc, +) from queries.num import number_to_text, numbers_to_ordinal, numbers_to_text from queries.resources import ( DateResource, @@ -74,237 +81,13 @@ def help_text(lemma: str) -> str: QUERY_NONTERMINALS = {"QTheater"}.union(HOTWORD_NONTERMINALS) # The context-free grammar for the queries recognized by this plug-in module -GRAMMAR = """ - -Query → - QTheaterHotWord | QTheater - -QTheater → QTheaterQuery - -QTheaterQuery → - QTheaterDialogue '?'? - -QTheaterHotWord → - QTheaterNames '?'? - | QTheaterEgVil? QTheaterKaupaFaraFaPanta "leikhúsmiða" '?'? - | QTheaterEgVil? QTheaterKaupaFaraFaPanta "miða" "í" QTheaterNames '?'? - | QTheaterEgVil? QTheaterKaupaFaraFaPanta "miða" "á" QTheaterNames "sýningu" '?'? - | QTheaterEgVil? QTheaterKaupaFaraFaPanta QTheaterNames '?'? - | QTheaterEgVil? QTheaterKaupaFaraFaPanta "leikhússýningu" '?'? - -QTheaterNames → - 'leikhús' - | 'þjóðleikhús' - | 'Þjóðleikhús' - | 'Borgarleikhús' - | 'borgarleikhús' - - -QTheaterKaupaFaraFaPanta → - "kaupa" "mér"? - | "fara" "á" - | "fara" "í" - | "fá" - | "panta" - -QTheaterDialogue → - QTheaterShowQuery - | QTheaterShowDateQuery - | QTheaterMoreDates - | QTheaterPreviousDates - | QTheaterShowSeatCountQuery - | QTheaterShowLocationQuery - | QTheaterShowPrice - | QTheaterShowLength - | QTheaterOptions - | QTheaterYes - | QTheaterNo - | QTheaterCancel - | QTheaterStatus - -QTheaterOptions → - QTheaterGeneralOptions - | QTheaterShowOptions - | QTheaterDateOptions - | QTheaterRowOptions - | QTheaterSeatOptions - -QTheaterGeneralOptions → - "hverjir"? "eru"? "valmöguleikarnir" - | "hvert" "er" "úrvalið" - | "hvað" "er" "í" "boði" - -QTheaterShowOptions → - "hvaða" "sýningar" "eru" "í" "boði" - -QTheaterDateOptions → - "hvaða" "dagsetningar" "eru" "í" "boði" - | "hvaða" "dagar" "eru" "í" "boði" - | "hvaða" "dagsetningar" "er" "hægt" "að" "velja" "á" "milli" - -QTheaterRowOptions → - "hvaða" "raðir" "eru" QTheaterIBodiLausar - | "hvaða" "röð" "er" QTheaterIBodiLausar - | "hvaða" "bekkir" "eru" QTheaterIBodiLausar - | "hvaða" "bekkur" "er" QTheaterIBodiLausar - -QTheaterSeatOptions → - "hvaða" "sæti" "eru" QTheaterIBodiLausar - | "hverjir" "eru" "sæta" "valmöguleikarnir" - -QTheaterIBodiLausar → - "í" "boði" - | "lausar" - | "lausir" - | "laus" - -QTheaterShowQuery → QTheaterEgVil? "velja" 'sýning' QTheaterShowName - > QTheaterEgVil? "fara" "á" 'sýning' QTheaterShowName - > QTheaterShowOnlyName - -QTheaterShowOnlyName → QTheaterShowName - -QTheaterShowName → Nl - - -QTheaterShowPrice → - "hvað" "kostar" "einn"? 'miði' - | "hvað" "kostar" "1"? 'miði' - | "hvað" "kostar" "einn"? 'miðinn' "á" "sýninguna" - -QTheaterShowLength → - "hvað" "er" "sýningin" "löng" - -QTheaterShowDateQuery → - QTheaterEgVil? "fara"? "á"? 'sýning'? QTheaterShowDate - -QTheaterShowDate → - QTheaterDateTime | QTheaterDate | QTheaterTime - -QTheaterDateTime → - tímapunkturafs - -QTheaterDate → - dagsafs - | dagsföst - -QTheaterTime → - "klukkan"? tími - -QTheaterMoreDates → - "hverjar"? "eru"? "næstu" "þrjár"? QSyningarTimar - | "hverjir" "eru" "næstu" "þrír"? QSyningarTimar - | "get" "ég" "fengið" "að" "sjá" "næstu" "þrjá"? QSyningarTimar - | QTheaterEgVil? "sjá"? "fleiri" QSyningarTimar - | QTheaterEgVil? "sjá"? "næstu" "þrjá"? QSyningarTimar - -QTheaterPreviousDates → - QTheaterEgVil "sjá" "fyrri" QSyningarTimar - | "hvaða" QSyningarTimar "eru" "á" "undan" "þessum"? - | "get" "ég" "fengið" "að" "sjá" QSyningarTimar "á" "undan" "þessum"? - | QTheaterEgVil? "sjá"? QSyningarTimar "á" "undan" "þessum"? - -QSyningarTimar → - 'sýningartíma' - | "dagsetningar" - | "sýningartímana" - -QTheaterShowSeatCountQuery → - QTheaterSeatCountNum - | QTheaterEgVil? "fá"? QTheaterNum "sæti" - | QTheaterEgVil? "fá"? QTheaterNum "miða" - | QTheaterEgVil? "fá"? QTheaterNum "miða" "á" "sýninguna" - -QTheaterSeatCountNum → - to | töl | tala - -QTheaterShowLocationQuery → - QTheaterShowRow - | QTheaterShowSeats - -QTheaterShowRow → - QTheaterRodBekkur - | QTheaterEgVil QTheaterVeljaRod QTheaterRodBekkur - -QTheaterVeljaRod → - "velja" "sæti"? "í"? - | "sitja" "í" - | "fá" "sæti" "í" - | "fá" "sæti" "á" - -QTheaterRodBekkur → - QTheaterRowNum - | QTheaterRodBekk "númer"? QTheaterNum - | QTheaterNum "bekk" - | QTheaterNum "röð" - -QTheaterRowNum → - to | töl | tala - -QTheaterShowSeats → - QTheaterShowSeatsNum - | QTheaterEgVil? "sæti"? "númer"? QTheaterNum "til" QTheaterNum? - | QTheaterEgVil? "sæti" "númer"? QTheaterNum "og" QTheaterNum? - | "ég" "vil" "sitja" "í" "röð" "númer" QTheaterNum - -QTheaterShowSeatsNum → - to | töl | tala - -QTheaterDateOptions → - "hvaða" "dagsetningar" "eru" "í" "boði" - -QTheaterRodBekk → "röð" | "bekk" - -QTheaterEgVil → - "ég"? "vil" - | "ég" "vill" - | "mig" "langar" "að" - | "mig" "langar" "í" - -QTheaterNum → - # to is a declinable number word ('tveir/tvo/tveim/tveggja') - # töl is an undeclinable number word ('sautján') - # tala is a number ('17') - to | töl | tala - -QTheaterYes → "já" "já"* | "endilega" | "já" "takk" | "játakk" | "já" "þakka" "þér" "fyrir" | "já" "takk" "kærlega" "fyrir"? | "jább" "takk"? - -QTheaterNo → "nei" "takk"? | "nei" "nei"* | "neitakk" | "ómögulega" - -QTheaterCancel → "ég" "hætti" "við" - | QTheaterEgVil "hætta" "við" QTheaterPontun? - -QTheaterStatus → - "staðan" - | "hver" "er" "staðan" "á" QTheaterPontun? - | "hver" "er" "staðan" - | "segðu" "mér" "stöðuna" - | "hvernig" "er" "staðan" - | "hvar" "var" "ég" - | "hvert" "var" "ég" 'kominn' - | "hvert" "var" "ég" 'kominn' "í" QTheaterPontun - | "hver" "var" "staðan" "á"? QTheaterPontun - | QTheaterEgVil "halda" "áfram" "með" QTheaterPontun - -QTheaterPontun → - "pöntuninni" - | "leikhús" "pöntuninni" - | "leikhús" "pöntunina" - | "leikhúsmiða" "pöntuninni" - | "leikhúsmiða" "pöntunina" - | "leikhúsmiðapöntunina" - | "leikhúsmiðapöntuninni" - | "leikhús" "miða" "pöntunina" - | "leikhús" "miða" "pöntuninni" - - -""" +GRAMMAR = read_grammar_file("theater") def banned_nonterminals(q: Query) -> Set[str]: """ - Returns a set of nonterminals that are not - allowed due to the state of the dialogue + Returns a set of nonterminals that are not + allowed due to the state of the dialogue """ banned_nonterminals: set[str] = set() if q.dsm.dialogue_name != DIALOGUE_NAME: From 11e16b01e12c123f4686e1254ae0a8492994ce2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Wed, 20 Jul 2022 17:46:35 +0000 Subject: [PATCH 248/371] Pizza size and crust functionality --- queries/pizza.py | 72 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 56 insertions(+), 16 deletions(-) diff --git a/queries/pizza.py b/queries/pizza.py index 0e1fa80a..f4017f28 100644 --- a/queries/pizza.py +++ b/queries/pizza.py @@ -69,9 +69,7 @@ def help_text(lemma: str) -> str: QUERY_NONTERMINALS = {"QPizza"}.union(HOTWORD_NONTERMINALS) # The context-free grammar for the queries recognized by this plug-in module -GRAMMAR = read_grammar_file( - "pizza", -) +GRAMMAR = read_grammar_file("pizza") def banned_nonterminals(query: str) -> Set[str]: @@ -157,7 +155,7 @@ def QPizzaToppingsList(node: Node, params: QueryStateDict, result: Result) -> No print("Toppings in QPizzaToppingsList: ", result.get("toppings", {})) dsm: DialogueStateManager = Query.get_dsm(result) toppings: Dict[str, int] = result.get("toppings", {}) - type_resource = dsm.current_resource + type_resource: OrResource = cast(OrResource, dsm.current_resource) print("Current resource in topping list: ", type_resource.name) index = type_resource.name.split("_")[-1] toppings_resource = dsm.get_resource("Toppings_{}".format(index)) @@ -166,10 +164,13 @@ def QPizzaToppingsList(node: Node, params: QueryStateDict, result: Result) -> No for (topping, amount) in toppings.items(): toppings_resource.data[topping] = amount print("Toppings in QPizzaToppingsList: ", toppings_resource.data) + dsm.skip_other_resources(type_resource, toppings_resource) dsm.set_resource_state(toppings_resource.name, ResourceState.CONFIRMED) dsm.set_resource_state(type_resource.name, ResourceState.CONFIRMED) - dsm.set_resource_state(pizza_resource.name, ResourceState.CONFIRMED) - # TODO: This should not be done here, only for testing purposes + print("Updating wrapper state with state: ", pizza_resource.state) + dsm.update_wrapper_state(cast(WrapperResource, pizza_resource)) + if pizza_resource.state == ResourceState.FULFILLED: + dsm.set_resource_state(pizza_resource.name, ResourceState.CONFIRMED) def QPizzaToppingsWord(node: Node, params: QueryStateDict, result: Result) -> None: @@ -188,27 +189,66 @@ def QPizzaNum(node: Node, params: QueryStateDict, result: Result) -> None: def QPizzaSizeLarge(node: Node, params: QueryStateDict, result: Result) -> None: - return + dsm: DialogueStateManager = Query.get_dsm(result) + # TODO: Maybe some wrappers should not be set as the current resource? (e.g. here, we have to go through extra steps to get the size resource) + # TODO: Better to use Pizza_1 here, as the current resource might be Type_1 instead of Pizza_1 and cause an error + size_resource: Resource = dsm.get_resource( + [i for i in dsm.current_resource.requires if i.startswith("Size")][0] + ) + size_resource.data = "stóra pítsu" + dsm.set_resource_state(size_resource.name, ResourceState.CONFIRMED) + dsm.update_wrapper_state(cast(WrapperResource, dsm.current_resource)) + if dsm.current_resource.state == ResourceState.FULFILLED: + dsm.set_resource_state(dsm.current_resource.name, ResourceState.CONFIRMED) def QPizzaSizeMedium(node: Node, params: QueryStateDict, result: Result) -> None: - return + dsm: DialogueStateManager = Query.get_dsm(result) + size_resource: Resource = dsm.get_resource( + [i for i in dsm.current_resource.requires if i.startswith("Size")][0] + ) + size_resource.data = "miðstærð af pítsu" + dsm.set_resource_state(size_resource.name, ResourceState.CONFIRMED) + dsm.update_wrapper_state(cast(WrapperResource, dsm.current_resource)) + if dsm.current_resource.state == ResourceState.FULFILLED: + dsm.set_resource_state(dsm.current_resource.name, ResourceState.CONFIRMED) def QPizzaMediumWord(node: Node, params: QueryStateDict, result: Result) -> None: - return + dsm: DialogueStateManager = Query.get_dsm(result) + size_resource: Resource = dsm.get_resource( + [i for i in dsm.current_resource.requires if i.startswith("Size")][0] + ) + size_resource.data = "miðstærð af pítsu" + dsm.set_resource_state(size_resource.name, ResourceState.CONFIRMED) + dsm.update_wrapper_state(cast(WrapperResource, dsm.current_resource)) + if dsm.current_resource.state == ResourceState.FULFILLED: + dsm.set_resource_state(dsm.current_resource.name, ResourceState.CONFIRMED) def QPizzaSizeSmall(node: Node, params: QueryStateDict, result: Result) -> None: - return - - -def QPizzaItalianWord(node: Node, params: QueryStateDict, result: Result) -> None: - return + dsm: DialogueStateManager = Query.get_dsm(result) + size_resource: Resource = dsm.get_resource( + [i for i in dsm.current_resource.requires if i.startswith("Size")][0] + ) + size_resource.data = "litla pítsu" + dsm.set_resource_state(size_resource.name, ResourceState.CONFIRMED) + dsm.update_wrapper_state(cast(WrapperResource, dsm.current_resource)) + if dsm.current_resource.state == ResourceState.FULFILLED: + dsm.set_resource_state(dsm.current_resource.name, ResourceState.CONFIRMED) -def QPizzaClassicWord(node: Node, params: QueryStateDict, result: Result) -> None: - return +def QPizzaCrustType(node: Node, params: QueryStateDict, result: Result) -> None: + dsm: DialogueStateManager = Query.get_dsm(result) + crust_resource: Resource = dsm.get_resource( + [i for i in dsm.current_resource.requires if i.startswith("Crust")][0] + ) + crust_resource.data = result._text + print("Crust resource data: ", crust_resource.data) + dsm.set_resource_state(crust_resource.name, ResourceState.CONFIRMED) + dsm.update_wrapper_state(cast(WrapperResource, dsm.current_resource)) + if dsm.current_resource.state == ResourceState.FULFILLED: + dsm.set_resource_state(dsm.current_resource.name, ResourceState.CONFIRMED) def QPizzaPepperoniWord(node: Node, params: QueryStateDict, result: Result) -> None: From 7e6936a48b526a462abe88bc7b6ca51ac31acee2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Wed, 20 Jul 2022 17:47:12 +0000 Subject: [PATCH 249/371] Made af pizzu optional in QPizzaSizeAnswer --- queries/grammars/pizza.grammar | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/queries/grammars/pizza.grammar b/queries/grammars/pizza.grammar index a600fe48..8a24069c 100644 --- a/queries/grammars/pizza.grammar +++ b/queries/grammars/pizza.grammar @@ -44,7 +44,7 @@ QPizzaToppingsAnswer -> QPizzaSizeAnswer -> QPizzaEgVil? QPizzaSize/þf QPizzaWord/þf? # e.g. "Ég vil stóra pítsu." | QPizzaSize/nf # e.g. "Tólf tommur." - | QPizzaEgVil? QPizzaMediumWord QPizzaAfPitsuPhrase # e.g. "Ég vil miðstærð af pítsu." + | QPizzaEgVil? QPizzaMediumWord QPizzaAfPitsuPhrase? # e.g. "Ég vil miðstærð af pítsu." QPizzaCrustAnswer -> QPizzaEgVil? QPizzaCrustPhrase/þf QPizzaAPitsunaPhrase? # e.g. "Ég vil klassískan botn." From 7977b955f5fe921b7b9b0bede01d2db2b129d892 Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Wed, 20 Jul 2022 17:50:22 +0000 Subject: [PATCH 250/371] menu and sides grammar --- queries/grammars/pizza.grammar | 149 ++++++++++++++++++++++++++++----- 1 file changed, 129 insertions(+), 20 deletions(-) diff --git a/queries/grammars/pizza.grammar b/queries/grammars/pizza.grammar index a600fe48..0fc26f65 100644 --- a/queries/grammars/pizza.grammar +++ b/queries/grammars/pizza.grammar @@ -2,6 +2,7 @@ # TODO: Ban more than two instances of a topping. # TODO: Fix the toppings being a set. Doesn't handle "Ég vil skinku, ólífur og auka skinku." # TODO: Add to PizzaRequestBare, start conversation with an order. +# TODO: Add the words for margherita to BinPackage /þgf = þgf /ef = ef @@ -22,34 +23,49 @@ QPizzaHotWord → QPizzaWord/nf # e.g. "Pítsa" | QPizzaRequestBare # e.g. "Ég vil panta pizzu." -# Doesn't allow for any specification, i.e. number of pizzas: "Ég vil panta tvær flatbökur." +# Doesn't allow for any order specification, e.g. the number of pizzas: "Ég vil panta tvær flatbökur." QPizzaRequestBare -> - QPizzaEgVil QPizzaKaupaFaraFaPanta? QPizzaWord/þf + QPizzaRequestPleasantries? QPizzaWord/þf QPizzaDialogue → - QPizzaNumberAnswer #Answer to the question "How many pizzas do you want?" - | QPizzaToppingsAnswer #Answer to the question "What toppings do you want?" - | QPizzaSizeAnswer #Answer to the question "What size do you want?" - | QPizzaCrustAnswer #Answer to the question "What kind of crust do you want?" + QPizzaNumberAnswer # Answer to the question "How many pizzas do you want?" + | QPizzaMenuOrToppingsAnswer # Answer to the question "What do you want to order?" + | QPizzaSizeAnswer # Answer to the question "What size do you want?" + | QPizzaCrustAnswer # Answer to the question "What kind of crust do you want?" + | QPizzaExtrasAnswer # Answer to the question "Do you want anything with your pizzas?" QPizzaNumberAnswer → - QPizzaEgVil QPizzaKaupaFaraFaPanta? QPizzaNum/þf QPizzaWord/þf # e.g. "Ég vil kaupa tvær pítsur." + QPizzaRequestPleasantries? QPizzaNum/þf QPizzaWord/þf # e.g. "Ég vil kaupa tvær pítsur." | QPizzaNum/nf QPizzaWord/nf? # e.g. "Tvær pítsur." +QPizzaMenuOrToppingsAnswer -> + QPizzaMenuAnswer # A request for a pizza off the menu. + | QPizzaToppingsAnswer # A request for a custom pizza. + +QPizzaMenuAnswer -> + QPizzaRequestPleasantries? QPizzaMenuWords/þf # e.g. "Ég vil kjötveislu." + | QPizzaMenuWords/nf # e.g. "Margaríta." + QPizzaToppingsAnswer -> - QPizzaEgVil? QPizzaToppingsList/þf QPizzaAPitsunaPhrase? # e.g. "Ég vil skinku, ólífur og pepperóní á pítsuna." - | QPizzaEgVil? QPizzaWord/þf "með" QPizzaToppingsList/þgf # e.g. "Ég vil pizzu með ólífum og ananas." + QPizzaRequestPleasantries? QPizzaToppingsList/þf QPizzaAPitsunaPhrase? # e.g. "Ég vil skinku, ólífur og pepperóní á pítsuna." + | QPizzaRequestPleasantries? QPizzaWord/þf "með" QPizzaToppingsList/þgf # e.g. "Ég vil pizzu með ólífum og ananas." # It is common to say "miðstærð af pítsu", which is handled separately here. QPizzaSizeAnswer -> - QPizzaEgVil? QPizzaSize/þf QPizzaWord/þf? # e.g. "Ég vil stóra pítsu." + QPizzaRequestPleasantries? QPizzaSize/þf QPizzaWord/þf? # e.g. "Ég vil stóra pítsu." | QPizzaSize/nf # e.g. "Tólf tommur." - | QPizzaEgVil? QPizzaMediumWord QPizzaAfPitsuPhrase # e.g. "Ég vil miðstærð af pítsu." + | QPizzaRequestPleasantries? QPizzaMediumWord/þf QPizzaAfPitsuPhrase? # e.g. "Ég vil miðstærð af pítsu." QPizzaCrustAnswer -> - QPizzaEgVil? QPizzaCrustPhrase/þf QPizzaAPitsunaPhrase? # e.g. "Ég vil klassískan botn." + QPizzaRequestPleasantries? QPizzaCrustPhrase/þf QPizzaAPitsunaPhrase? # e.g. "Ég vil klassískan botn." + | QPizzaRequestPleasantries? QPizzaWord/þf "með" QPizzaCrustPhrase/þgf # e.g. "Ég vil pizzu með ítölskum botni." | QPizzaCrustPhrase/nf # e.g. "Ítalskur botn." +QPizzaExtrasAnswer -> + QPizzaRequestPleasantries? QPizzaExtraWords/þf QPizzaMedPitsunni? # e.g. "Ég vil brauðstangir með pizzunum." + | QPizzaEgVil "líka" QPizzaExtraWords/þf QPizzaMedPitsunni? # e.g. "Ég vil líka kanilgott með pizzunum." + | QPizzaExtraWords/nf # e.g. "Kók." + QPizzaToppingsList/fall -> QPizzaToppingsWordWrapper/fall* 'og:st'? QPizzaToppingsWord/fall @@ -66,11 +82,54 @@ QPizzaToppingsWordWrapper/fall -> # Toppings that are transcribed in different ways are in separate nonterminals for clarity. # This also helps standardize the handling of each topping in the module, i.e. not reading "ólífa" and "ólíva" as separate toppings. QPizzaToppingsWord/fall -> - QPizzaMushroomWord/fall - | QPizzaPepperoniWord/fall + QPizzaMushroom/fall + | QPizzaPepperoni/fall | 'ananas:kk'/fall | 'skinka:kvk'/fall - | QPizzaOliveWord/fall + | QPizzaOlive/fall + +QPizzaMenuWords/fall -> + 'prinsessa:kvk'/fall + | 'dóttir:kvk'/fall + | QPizzaMargherita/fall + | 'kjötveisla:kvk'/fall + | 'hvítlauksbrauð:hk'/fall + | QPizzaTokyo/fall + +QPizzaExtraWords/fall -> + QPizzaSidesWords/fall + | QPizzaDrinksWords/fall + | QPizzaDipsWords/fall + +QPizzaSidesWords/fall -> + QPizzaLargeBreadsticks/fall + | QPizzaSmallBreadsticks/fall + | 'lítill:lo'_kvk_ft/fall 'brauðstöng:kvk'_ft/fall + | "ostagott" + | "kanilgott" + | "súkkulaðigott" + | 'kartöflubátur:kk'_ft/fall + | 'vængur:kk'_ft/fall + +QPizzaDrinksWords/fall -> + QPizzaCoke/fall + | QPizzaCokeZero/fall + | "fanta" + | 'toppur:kk'_et/fall + | 'sítrónutoppur:kk'_et/fall + | 'appelsínusvali:kk'_et/fall + | 'eplasvali:kk'_et/fall + | "monster" + +QPizzaDipsWords/fall -> + 'hvítlauksolía:kvk'_et/fall + | 'hvítlaukssósa:kvk'_et/fall + | 'brauðstangasósa:kvk'_et/fall + | QPizzaBlueCheese/fall + | 'sterkur:lo'_kvk_et/fall 'sósa:kvk'_et/fall + | 'kokteilsósa:kvk'_et/fall + | "súkkulaðiglassúr" + | "glassúr" QPizzaCrustPhrase/fall -> QPizzaCrustType/fall QPizzaCrustWord/fall? @@ -81,6 +140,9 @@ QPizzaAfPitsuPhrase -> QPizzaAPitsunaPhrase -> "á" QPizzaWord/þgf +QPizzaMedPitsunni -> + "með" QPizzaWord/þgf + # A large pizza at Domino's is typically thought to be 16", some believe it to be 15". # The actual size is 14.5". QPizzaSizeLarge/fall -> @@ -94,8 +156,8 @@ QPizzaSizeMedium/fall -> | 'meðalstór:lo'/fall | QPizzaTwelveWord 'tomma:kvk'/fall? -QPizzaMediumWord -> - "miðstærð" +QPizzaMediumWord/fall -> + 'miðstærð:kvk'/fall QPizzaSizeSmall/fall -> 'lítil:lo'/fall @@ -105,11 +167,15 @@ QPizzaCrustType/fall -> QPizzaItalianWord/fall | QPizzaClassicWord/fall +QPizzaRequestPleasantries -> + QPizzaEgVil QPizzaKaupaFaraFaPanta? + QPizzaEgVil → "ég"? "vil" | "ég" "vill" | "mig" "langar" "að" | "mig" "langar" "í" + | "ég" "ætla" "að" QPizzaKaupaFaraFaPanta → "kaupa" "mér"? @@ -164,16 +230,59 @@ QPizzaNum/fall → # tala is a number ('17') to | töl | tala -QPizzaPepperoniWord/fall -> +QPizzaPepperoni/fall -> 'pepperóní:hk'/fall | "pepperoni" | "pepperóni" | "pepperoní" -QPizzaOliveWord/fall -> +QPizzaOlive/fall -> 'ólífa:kvk'/fall | 'ólíva:kvk'/fall -QPizzaMushroomWord/fall -> +QPizzaMushroom/fall -> 'sveppur:kk'/fall | 'Sveppi:kk' + +QPizzaMargherita_nf -> + "margaríta" + | "margarita" + +QPizzaMargherita/fallxnf -> + "margarítu" + | "margaritu" + +QPizzaTokyo/fall -> + 'Tókýó:kvk'/fall + | "Tókíó" + | "Tokyo" + | "tókýó" + | "tókíó" + | "tokyo" + +QPizzaLargeBreadsticks/fall -> + 'stór:lo'_kvk_ft/fall? 'brauðstöng:kvk'_ft/fall + | 'brauðstöng:kvk'_ft/fall 'stór:lo'_kvk_ft/fall + +QPizzaSmallBreadsticks/fall -> + 'lítill:lo'_kvk_ft/fall 'brauðstöng:kvk'_ft/fall + | 'brauðstöng:kvk'_ft/fall 'lítill:lo'_kvk_ft/fall + +QPizzaCoke/fall -> + QPizzaCokeWord/fall + +QPizzaCokeZero/fall -> + QPizzaCokeWord/fall "zero" + | QPizzaCokeWord/fall "án" 'sykur:kk'_et_ef + +QPizzaBlueCheese/fall -> + 'gráðaostasósa:kvk'_et/fall + | 'gráðostasósa:kvk'_et/fall + | 'gráðaostur:kk'_et/fall + | 'gráðostur:kk'_et/fall + +QPizzaCokeWord/fall -> + 'kók:kvk'_et/fall + | "kóka-kóla" + | "coke" + | "coca-cola" \ No newline at end of file From e5a6988e91296fd19c4791a655d1070fe84d625f Mon Sep 17 00:00:00 2001 From: Logi E Date: Thu, 21 Jul 2022 10:24:53 +0000 Subject: [PATCH 251/371] get_descendants Added prefer_over_wrapper attribute for resources, added get_descendants and get_children functions in DSM --- queries/dialogue.py | 34 +++++++++++++++++++++++++++++++++- queries/resources.py | 3 +++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index 6177341e..cb058527 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -51,6 +51,8 @@ # TODO: Fix 'Any' in type hint (Callable args are contravariant) AnsweringFunctionMap = Mapping[str, AnsweringFunctionType[Any]] +FilterFuncType = Callable[[Resource, int], bool] +_ALLOW_ALL_FILTER: FilterFuncType = lambda r, i: True ################################ # DIALOGUE STATE MANAGER # @@ -356,6 +358,34 @@ def extras(self) -> Dict[str, Any]: def timed_out(self) -> bool: return self._timed_out + def get_descendants( + self, resource: Resource, filter_func: Optional[FilterFuncType] = None + ) -> List[Resource]: + """ + Given a resource and an optional filter function + (with a resource and the depth in tree as args, returns a boolean), + returns all descendants of the resource that match the function + (all of them if filter_func is None). + Returns the descendants in preorder + """ + descendants: List[Resource] = [] + + def _recurse_descendants( + resource: Resource, depth: int, filter_func: FilterFuncType + ) -> None: + nonlocal descendants + for child in self._resource_graph[resource]["children"]: + if filter_func(child, depth): + descendants.append(child) + _recurse_descendants(child, depth + 1, filter_func) + + _recurse_descendants(resource, 0, filter_func or _ALLOW_ALL_FILTER) + return descendants + + def get_children(self, resource: Resource) -> List[Resource]: + """Given a resource, returns all children of the resource""" + return self._resource_graph[resource]["children"] + def get_answer( self, answering_functions: AnsweringFunctionMap, result: Any ) -> Optional[AnswerTuple]: @@ -454,9 +484,11 @@ def _recurse_resources(resource: Resource) -> None: curr_res == child and not isinstance(child, WrapperResource) and wrapper_parent == resource + and not child.prefer_over_wrapper ): # If the direct child of a wrapper resource - # is the current resource and not a wrapper itself, + # is the current resource, isn't a wrapper itself, + # and isn't preferred over the wrapper, # set the wrapper as the current resource instead curr_res = resource if curr_res is not None: diff --git a/queries/resources.py b/queries/resources.py index cf7ac486..42d8513e 100644 --- a/queries/resources.py +++ b/queries/resources.py @@ -63,6 +63,9 @@ class Resource: prompts: Mapping[str, str] = field(default_factory=dict) # When this resource's state is changed, change all parent resource states as well cascade_state: bool = False + # When set to True, this resource will be used + # as the current resource instead of its wrapper + prefer_over_wrapper: bool = False # Used for comparing states (which one is earlier/later in the dialogue) order_index: int = 0 # Extra variables to be used for specific variables From 14dac196405b04ebf6ccf61e8594156bb993991e Mon Sep 17 00:00:00 2001 From: Logi E Date: Thu, 21 Jul 2022 10:45:39 +0000 Subject: [PATCH 252/371] added get_ancestors function and comments --- queries/dialogue.py | 61 ++++++++++++++++++++++++++++++--------------- 1 file changed, 41 insertions(+), 20 deletions(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index 21615031..8e462b37 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -387,6 +387,33 @@ def get_children(self, resource: Resource) -> List[Resource]: """Given a resource, returns all children of the resource""" return self._resource_graph[resource]["children"] + def get_ancestors( + self, resource: Resource, filter_func: Optional[FilterFuncType] = None + ) -> List[Resource]: + """ + Given a resource and an optional filter function + (with a resource and the depth in tree as args, returns a boolean), + returns all ancestors of the resource that match the function + (all of them if filter_func is None). + """ + ancestors: List[Resource] = [] + + def _recurse_ancestors( + resource: Resource, depth: int, filter_func: FilterFuncType + ) -> None: + nonlocal ancestors + for parent in self._resource_graph[resource]["parents"]: + if filter_func(parent, depth): + ancestors.append(parent) + _recurse_ancestors(parent, depth + 1, filter_func) + + _recurse_ancestors(resource, 0, filter_func or _ALLOW_ALL_FILTER) + return ancestors + + def get_parents(self, resource: Resource) -> List[Resource]: + """Given a resource, returns all parents of the resource""" + return self._resource_graph[resource]["parents"] + def get_answer( self, answering_functions: AnsweringFunctionMap, result: Any ) -> Optional[AnswerTuple]: @@ -447,20 +474,9 @@ def set_resource_state(self, resource_name: str, state: ResourceState): resource.state = state if resource.cascade_state and lowered_state: # Find all parent resources and set to corresponding state - parents = self._find_parent_resources(self._resources[resource_name]) - for parent in parents: - parent.state = ResourceState.UNFULFILLED - - def _find_parent_resources(self, resource: Resource) -> Set[Resource]: - """Find all parent resources of a resource""" - all_parents: Set[Resource] = set() - resource_parents: list[Resource] = self._resource_graph[resource]["parents"] - if len(resource_parents) > 0: - for parent in resource_parents: - if parent not in all_parents: - all_parents.add(parent) - all_parents.update(self._find_parent_resources(parent)) - return all_parents + ancestors = set(self.get_ancestors(resource)) + for anc in ancestors: + anc.state = ResourceState.UNFULFILLED def _find_current_resource(self) -> Resource: """ @@ -473,9 +489,10 @@ def _find_current_resource(self) -> Resource: def _recurse_resources(resource: Resource) -> None: nonlocal curr_res, wrapper_parent if resource.is_confirmed or resource.is_skipped: + # Don't set resource as current if it is confirmed or skipped return # Current resource is neither confirmed nor skipped, - # so we try to recurse further + # so we try to find candidates lower in the tree first if isinstance(resource, WrapperResource): # This resource is a wrapper, keep it in a variable wrapper_parent = resource @@ -483,25 +500,28 @@ def _recurse_resources(resource: Resource) -> None: _recurse_resources(child) if ( curr_res == child - and not isinstance(child, WrapperResource) and wrapper_parent == resource + and not isinstance(child, WrapperResource) and not child.prefer_over_wrapper ): - # If the direct child of a wrapper resource - # is the current resource, isn't a wrapper itself, - # and isn't preferred over the wrapper, + # If the direct child of a wrapper resource: + # 1. is the current resource + # 2. isn't a wrapper itself + # 3. isn't preferred as current resource over the wrapper # set the wrapper as the current resource instead curr_res = resource if curr_res is not None: - # Found a non-confirmed resource, stop looking + # Found a suitable resource, stop looking return curr_res = resource _recurse_resources(self._resources["Final"]) return curr_res or self._resources["Final"] + # TODO: Can we move this function into set_resource_state? def skip_other_resources(self, or_resource: OrResource, resource: Resource) -> None: """Skips other resources in the or resource""" + # TODO: Check whether OrResource is exclusive or not assert isinstance( or_resource, OrResource ), f"{or_resource} is not an OrResource" @@ -509,6 +529,7 @@ def skip_other_resources(self, or_resource: OrResource, resource: Resource) -> N if res != resource.name: self.set_resource_state(res, ResourceState.SKIPPED) + # TODO: Can we move this function into set_resource_state? def update_wrapper_state(self, wrapper: WrapperResource) -> None: """ Updates the state of the wrapper resource From a00041e5e71a0ec9efffa1920ea50f7a49ed755c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Thu, 21 Jul 2022 10:45:59 +0000 Subject: [PATCH 253/371] Moved size functionality to one function --- queries/grammars/pizza.grammar | 2 +- queries/pizza.py | 39 +++++++++------------------------- 2 files changed, 11 insertions(+), 30 deletions(-) diff --git a/queries/grammars/pizza.grammar b/queries/grammars/pizza.grammar index 0fc26f65..1fd50fc5 100644 --- a/queries/grammars/pizza.grammar +++ b/queries/grammars/pizza.grammar @@ -157,7 +157,7 @@ QPizzaSizeMedium/fall -> | QPizzaTwelveWord 'tomma:kvk'/fall? QPizzaMediumWord/fall -> - 'miðstærð:kvk'/fall + "miðstærð" QPizzaSizeSmall/fall -> 'lítil:lo'/fall diff --git a/queries/pizza.py b/queries/pizza.py index f4017f28..2a47c687 100644 --- a/queries/pizza.py +++ b/queries/pizza.py @@ -188,54 +188,35 @@ def QPizzaNum(node: Node, params: QueryStateDict, result: Result) -> None: result.number = number -def QPizzaSizeLarge(node: Node, params: QueryStateDict, result: Result) -> None: +def QPizzaSizeAnswer(node: Node, params: QueryStateDict, result: Result) -> None: + print("In QPizzaSizeAnswer") dsm: DialogueStateManager = Query.get_dsm(result) # TODO: Maybe some wrappers should not be set as the current resource? (e.g. here, we have to go through extra steps to get the size resource) # TODO: Better to use Pizza_1 here, as the current resource might be Type_1 instead of Pizza_1 and cause an error size_resource: Resource = dsm.get_resource( [i for i in dsm.current_resource.requires if i.startswith("Size")][0] ) - size_resource.data = "stóra pítsu" + size_resource.data = result.get("pizza_size", "") dsm.set_resource_state(size_resource.name, ResourceState.CONFIRMED) dsm.update_wrapper_state(cast(WrapperResource, dsm.current_resource)) if dsm.current_resource.state == ResourceState.FULFILLED: dsm.set_resource_state(dsm.current_resource.name, ResourceState.CONFIRMED) +def QPizzaSizeLarge(node: Node, params: QueryStateDict, result: Result) -> None: + result.pizza_size = "stóra pítsu" + + def QPizzaSizeMedium(node: Node, params: QueryStateDict, result: Result) -> None: - dsm: DialogueStateManager = Query.get_dsm(result) - size_resource: Resource = dsm.get_resource( - [i for i in dsm.current_resource.requires if i.startswith("Size")][0] - ) - size_resource.data = "miðstærð af pítsu" - dsm.set_resource_state(size_resource.name, ResourceState.CONFIRMED) - dsm.update_wrapper_state(cast(WrapperResource, dsm.current_resource)) - if dsm.current_resource.state == ResourceState.FULFILLED: - dsm.set_resource_state(dsm.current_resource.name, ResourceState.CONFIRMED) + result.pizza_size = "miðstærð af pítsu" def QPizzaMediumWord(node: Node, params: QueryStateDict, result: Result) -> None: - dsm: DialogueStateManager = Query.get_dsm(result) - size_resource: Resource = dsm.get_resource( - [i for i in dsm.current_resource.requires if i.startswith("Size")][0] - ) - size_resource.data = "miðstærð af pítsu" - dsm.set_resource_state(size_resource.name, ResourceState.CONFIRMED) - dsm.update_wrapper_state(cast(WrapperResource, dsm.current_resource)) - if dsm.current_resource.state == ResourceState.FULFILLED: - dsm.set_resource_state(dsm.current_resource.name, ResourceState.CONFIRMED) + result.pizza_size = "miðstærð af pítsu" def QPizzaSizeSmall(node: Node, params: QueryStateDict, result: Result) -> None: - dsm: DialogueStateManager = Query.get_dsm(result) - size_resource: Resource = dsm.get_resource( - [i for i in dsm.current_resource.requires if i.startswith("Size")][0] - ) - size_resource.data = "litla pítsu" - dsm.set_resource_state(size_resource.name, ResourceState.CONFIRMED) - dsm.update_wrapper_state(cast(WrapperResource, dsm.current_resource)) - if dsm.current_resource.state == ResourceState.FULFILLED: - dsm.set_resource_state(dsm.current_resource.name, ResourceState.CONFIRMED) + result.pizza_size = "litla pítsu" def QPizzaCrustType(node: Node, params: QueryStateDict, result: Result) -> None: From bcaddf359b1e6f0b907131c9fe2ca5ece9c7a4ac Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Thu, 21 Jul 2022 10:57:38 +0000 Subject: [PATCH 254/371] composite order --- queries/grammars/pizza.grammar | 72 +++++++++++++++++++++++----------- 1 file changed, 49 insertions(+), 23 deletions(-) diff --git a/queries/grammars/pizza.grammar b/queries/grammars/pizza.grammar index 0fc26f65..0408ef4a 100644 --- a/queries/grammars/pizza.grammar +++ b/queries/grammars/pizza.grammar @@ -28,6 +28,10 @@ QPizzaRequestBare -> QPizzaRequestPleasantries? QPizzaWord/þf QPizzaDialogue → + QPizzaPrimeAnswer # Requests specifying a single thing. + > QPizzaCompositeAnswer # Request specifying multiple things at once: size, toppings, crust. + +QPizzaPrimeAnswer -> QPizzaNumberAnswer # Answer to the question "How many pizzas do you want?" | QPizzaMenuOrToppingsAnswer # Answer to the question "What do you want to order?" | QPizzaSizeAnswer # Answer to the question "What size do you want?" @@ -48,17 +52,16 @@ QPizzaMenuAnswer -> QPizzaToppingsAnswer -> QPizzaRequestPleasantries? QPizzaToppingsList/þf QPizzaAPitsunaPhrase? # e.g. "Ég vil skinku, ólífur og pepperóní á pítsuna." - | QPizzaRequestPleasantries? QPizzaWord/þf "með" QPizzaToppingsList/þgf # e.g. "Ég vil pizzu með ólífum og ananas." + | QPizzaRequestPleasantries? QPizzaWord/þf QPizzaMedToppings # e.g. "Ég vil pizzu með ólífum og ananas." -# It is common to say "miðstærð af pítsu", which is handled separately here. +# This allows for "Ég vil miðstærð af kjötveislu" to make the composite answer simpler. QPizzaSizeAnswer -> - QPizzaRequestPleasantries? QPizzaSize/þf QPizzaWord/þf? # e.g. "Ég vil stóra pítsu." - | QPizzaSize/nf # e.g. "Tólf tommur." - | QPizzaRequestPleasantries? QPizzaMediumWord/þf QPizzaAfPitsuPhrase? # e.g. "Ég vil miðstærð af pítsu." + QPizzaRequestPleasantries? QPizzaSizePhrase/þf # e.g. "Ég vil stóra pítsu." or "Ég vil miðstærð af pítsu." + | QPizzaSizePhrase/nf # e.g. "Tólf tommur." QPizzaCrustAnswer -> - QPizzaRequestPleasantries? QPizzaCrustPhrase/þf QPizzaAPitsunaPhrase? # e.g. "Ég vil klassískan botn." - | QPizzaRequestPleasantries? QPizzaWord/þf "með" QPizzaCrustPhrase/þgf # e.g. "Ég vil pizzu með ítölskum botni." + QPizzaRequestPleasantries? QPizzaCrustPhrase/þf QPizzaAPitsunaPhrase? # e.g. "Ég vil klassískan botn á pítsuna." + | QPizzaRequestPleasantries? QPizzaWord/þf QPizzaMedCrust # e.g. "Ég vil pizzu með ítölskum botni." | QPizzaCrustPhrase/nf # e.g. "Ítalskur botn." QPizzaExtrasAnswer -> @@ -66,8 +69,11 @@ QPizzaExtrasAnswer -> | QPizzaEgVil "líka" QPizzaExtraWords/þf QPizzaMedPitsunni? # e.g. "Ég vil líka kanilgott með pizzunum." | QPizzaExtraWords/nf # e.g. "Kók." +QPizzaCompositeAnswer -> + QPizzaRequestPleasantries? QPizzaSizePhrase/þf QPizzaToppingsCrustPermutation? # e.g. "Ég vil stóra pítsu með ólífum og ananas." + QPizzaToppingsList/fall -> - QPizzaToppingsWordWrapper/fall* 'og:st'? QPizzaToppingsWord/fall + QPizzaToppingsWord/fall QPizzaOgToppings/fall* QPizzaSize/fall -> QPizzaSizeLarge/fall @@ -76,8 +82,39 @@ QPizzaSize/fall -> # This wrapper is necessary to prevent the module from reading toppings multiple times. # Otherwise, it reads "ananas skinku og pepperóní" as "ananas" "skinka" "pepperóní" and "ananas skinka" (2022/07/20). -QPizzaToppingsWordWrapper/fall -> - QPizzaToppingsWord/fall +QPizzaOgToppings/fall -> + 'og:st'? QPizzaToppingsWord/fall + +# It is common to say "miðstærð af pítsu", which is handled separately here. +QPizzaSizePhrase/fall -> + QPizzaSize/fall QPizzaOrMenuWord/fall? + | QPizzaMediumWord/fall QPizzaAfPitsuPhrase? + +QPizzaCrustPhrase/fall -> + QPizzaCrustType/fall QPizzaCrustWord/fall? + +QPizzaToppingsCrustPermutation -> + QPizzaMedToppings? "og"? QPizzaMedCrust + | QPizzaMedCrust? "og"? QPizzaMedToppings + +QPizzaAfPitsuPhrase -> + "af" QPizzaOrMenuWord/þgf + +QPizzaAPitsunaPhrase -> + "á" QPizzaWord/þf + +QPizzaMedPitsunni -> + "með" QPizzaWord/þgf + +QPizzaMedToppings -> + "með" QPizzaToppingsList/þgf + +QPizzaMedCrust -> + "með" QPizzaCrustPhrase/þgf + +QPizzaOrMenuWord/fall -> + QPizzaWord/fall + | QPizzaMenuWords/fall # Toppings that are transcribed in different ways are in separate nonterminals for clarity. # This also helps standardize the handling of each topping in the module, i.e. not reading "ólífa" and "ólíva" as separate toppings. @@ -131,18 +168,6 @@ QPizzaDipsWords/fall -> | "súkkulaðiglassúr" | "glassúr" -QPizzaCrustPhrase/fall -> - QPizzaCrustType/fall QPizzaCrustWord/fall? - -QPizzaAfPitsuPhrase -> - "af" QPizzaWord/þgf - -QPizzaAPitsunaPhrase -> - "á" QPizzaWord/þgf - -QPizzaMedPitsunni -> - "með" QPizzaWord/þgf - # A large pizza at Domino's is typically thought to be 16", some believe it to be 15". # The actual size is 14.5". QPizzaSizeLarge/fall -> @@ -157,7 +182,7 @@ QPizzaSizeMedium/fall -> | QPizzaTwelveWord 'tomma:kvk'/fall? QPizzaMediumWord/fall -> - 'miðstærð:kvk'/fall + "miðstærð" QPizzaSizeSmall/fall -> 'lítil:lo'/fall @@ -244,6 +269,7 @@ QPizzaMushroom/fall -> 'sveppur:kk'/fall | 'Sveppi:kk' +# "Margaríta" is not recognized by bin, so I hack the grammar here to make it work. QPizzaMargherita_nf -> "margaríta" | "margarita" From 1f4eaea3c783cacff8896813e2f2b3503a9a5c04 Mon Sep 17 00:00:00 2001 From: Logi E Date: Thu, 21 Jul 2022 10:59:13 +0000 Subject: [PATCH 255/371] removed singleton, made wrappers prefer_over_wrapper by default --- queries/dialogue.py | 21 ++++----------------- queries/resources.py | 4 +++- query.py | 8 +++----- 3 files changed, 10 insertions(+), 23 deletions(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index 8e462b37..66683159 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -107,22 +107,11 @@ class DialogueDBStructure(TypedDict): extras: Dict[str, Any] -class DialogueStateManager(object): +class DialogueStateManager: DIALOGUE_DATA_KEY = "dialogue" - _instance = None - - # TODO: Check if singleton can be done in a better way - def __new__(cls, dialogue_data: DialogueDataDict) -> "DialogueStateManager": - if cls._instance is None: - cls._instance = super(DialogueStateManager, cls).__new__(cls) - # Put any initialization here. - print(">>>>>>>>Dialogue data in NEW:", dialogue_data) - cls._dialogue_data: DialogueDataDict = dialogue_data - return cls._instance def __init__(self, dialogue_data: DialogueDataDict) -> None: self._dialogue_data: DialogueDataDict = dialogue_data - print(">>>>>>>>Dialogue data in INIT:", dialogue_data) def load_dialogue(self, dialogue_name: str): self._dialogue_name: str = dialogue_name @@ -501,15 +490,13 @@ def _recurse_resources(resource: Resource) -> None: if ( curr_res == child and wrapper_parent == resource - and not isinstance(child, WrapperResource) and not child.prefer_over_wrapper ): # If the direct child of a wrapper resource: # 1. is the current resource - # 2. isn't a wrapper itself - # 3. isn't preferred as current resource over the wrapper - # set the wrapper as the current resource instead - curr_res = resource + # 2. isn't preferred as current resource over the wrapper + # set the wrapper parent as the current resource instead + curr_res = wrapper_parent if curr_res is not None: # Found a suitable resource, stop looking return diff --git a/queries/resources.py b/queries/resources.py index 42d8513e..ff177c41 100644 --- a/queries/resources.py +++ b/queries/resources.py @@ -248,7 +248,9 @@ class NumberResource(Resource): @dataclass(eq=False, repr=False) # Wrapper when multiple resources are required class WrapperResource(Resource): - ... + # Wrappers by default prefer to be the current + # resource rather than a wrapper parent + prefer_over_wrapper: bool = True @dataclass(eq=False, repr=False) diff --git a/query.py b/query.py index 12090eea..0fa4d3e3 100755 --- a/query.py +++ b/query.py @@ -408,10 +408,10 @@ def __init__( # This should be a dict that can be represented in JSON self._context: Optional[ContextDict] = None # Dialogue state manager and dialogue data, used for dialogue modules - self._dsm: DSM = DSM( + self._all_dialogue_data = ( cast(DialogueDataDict, self.client_data(DSM.DIALOGUE_DATA_KEY)) or dict() ) - self._all_dialogue_data: Optional[ClientDataDict] = None + self._dsm: DSM = DSM(self._all_dialogue_data) def _preprocess_query_string(self, q: str) -> str: """Preprocess the query string prior to further analysis""" @@ -968,13 +968,11 @@ def client_version(self) -> Optional[str]: @staticmethod def get_dsm(result: Result) -> DSM: """Fetch DialogueStateManager instance from result object""" - dsm = cast(QueryStateDict, result.state)["query"]._dsm - assert dsm is not None, "get_dsm called in non-dialogue state" + dsm = cast(QueryStateDict, result.state)["query"].dsm return dsm @property def dsm(self) -> DSM: - assert self._dsm is not None, "dsm property used in non-dialogue state" return self._dsm @property From a97ee868cfdf8e6a3761204301a4d90ca8d6e4c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Thu, 21 Jul 2022 10:59:41 +0000 Subject: [PATCH 256/371] Type resource now has prefer_over_wrapper as false --- queries/dialogues/pizza.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/queries/dialogues/pizza.toml b/queries/dialogues/pizza.toml index 4e8fecd7..aa844cec 100644 --- a/queries/dialogues/pizza.toml +++ b/queries/dialogues/pizza.toml @@ -28,6 +28,7 @@ prompts.crust = "Hvernig botn viltu?" name = "Type" type = "OrResource" requires = ["Toppings", "FromMenu"] #, "Split"] +prefer_over_wrapper = false prompts.initial = "Hvernig pítsu viltu fá?" [[dynamic_resources]] @@ -39,6 +40,7 @@ name = "FromMenu" [[dynamic_resources]] name = "Size" +prefer_over_wrapper = true [[dynamic_resource]] name = "Split" @@ -57,6 +59,7 @@ requires = ["Toppings", "FromMenu"] [[dynamic_resources]] name = "Crust" +prefer_over_wrapper = true # [[dynamic_resources]] # name = "Sides" From 28c290664db4b052a6b00ac8e97987c2de73fbb0 Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Thu, 21 Jul 2022 11:26:28 +0000 Subject: [PATCH 257/371] cancel order and status functionality --- queries/grammars/pizza.grammar | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/queries/grammars/pizza.grammar b/queries/grammars/pizza.grammar index 0408ef4a..0fd6cfdc 100644 --- a/queries/grammars/pizza.grammar +++ b/queries/grammars/pizza.grammar @@ -37,6 +37,8 @@ QPizzaPrimeAnswer -> | QPizzaSizeAnswer # Answer to the question "What size do you want?" | QPizzaCrustAnswer # Answer to the question "What kind of crust do you want?" | QPizzaExtrasAnswer # Answer to the question "Do you want anything with your pizzas?" + | QPizzaCancel # Request to cancel the order. + | QPizzaStatus # Request for the status of the order. QPizzaNumberAnswer → QPizzaRequestPleasantries? QPizzaNum/þf QPizzaWord/þf # e.g. "Ég vil kaupa tvær pítsur." @@ -69,6 +71,24 @@ QPizzaExtrasAnswer -> | QPizzaEgVil "líka" QPizzaExtraWords/þf QPizzaMedPitsunni? # e.g. "Ég vil líka kanilgott með pizzunum." | QPizzaExtraWords/nf # e.g. "Kók." +QPizzaCancel → + "ég" "hætti" "við" + | QPizzaEgVil? "hætta" "við" 'pöntun:kvk'_et/þf? + | "hætta" 'pöntun:kvk'_et/þgf? + | "hættu" + +QPizzaStatus → + "staðan" + | "hver" "er" "staðan" "á" 'pöntun:kvk'_et/þgf? + | "hver" "er" "staðan" + | "segðu" "mér" "stöðuna" + | "hvernig" "er" "staðan" + | "hvar" "var" "ég" + | "hvert" "var" "ég" 'kominn' + | "hvert" "var" "ég" 'kominn' "í" 'pöntun:kvk'_et/þgf? + | "hver" "var" "staðan" "á"? 'pöntun:kvk'_et/þgf? + | QPizzaEgVil? "halda" "áfram" "með" 'pöntun:kvk'_et/þf? + QPizzaCompositeAnswer -> QPizzaRequestPleasantries? QPizzaSizePhrase/þf QPizzaToppingsCrustPermutation? # e.g. "Ég vil stóra pítsu með ólífum og ananas." @@ -182,7 +202,7 @@ QPizzaSizeMedium/fall -> | QPizzaTwelveWord 'tomma:kvk'/fall? QPizzaMediumWord/fall -> - "miðstærð" + 'mið-stærð:kvk'/fall QPizzaSizeSmall/fall -> 'lítil:lo'/fall From aa858390bb7ce4203fecb5ddf19079db7136e15f Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Thu, 21 Jul 2022 11:38:17 +0000 Subject: [PATCH 258/371] trying new compound word structure --- queries/grammars/pizza.grammar | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/queries/grammars/pizza.grammar b/queries/grammars/pizza.grammar index 0fd6cfdc..9f18edcb 100644 --- a/queries/grammars/pizza.grammar +++ b/queries/grammars/pizza.grammar @@ -149,7 +149,7 @@ QPizzaMenuWords/fall -> 'prinsessa:kvk'/fall | 'dóttir:kvk'/fall | QPizzaMargherita/fall - | 'kjötveisla:kvk'/fall + | 'kjöt-veisla:kvk'/fall | 'hvítlauksbrauð:hk'/fall | QPizzaTokyo/fall @@ -173,15 +173,15 @@ QPizzaDrinksWords/fall -> | QPizzaCokeZero/fall | "fanta" | 'toppur:kk'_et/fall - | 'sítrónutoppur:kk'_et/fall - | 'appelsínusvali:kk'_et/fall - | 'eplasvali:kk'_et/fall + | 'sítrónu-toppur:kk'_et/fall + | 'appelsínu-svali:kk'_et/fall + | 'epla-svali:kk'_et/fall | "monster" QPizzaDipsWords/fall -> - 'hvítlauksolía:kvk'_et/fall - | 'hvítlaukssósa:kvk'_et/fall - | 'brauðstangasósa:kvk'_et/fall + 'hvítlauks-olía:kvk'_et/fall + | 'hvítlauks-sósa:kvk'_et/fall + | 'brauð-stanga-sósa:kvk'_et/fall | QPizzaBlueCheese/fall | 'sterkur:lo'_kvk_et/fall 'sósa:kvk'_et/fall | 'kokteilsósa:kvk'_et/fall @@ -307,12 +307,12 @@ QPizzaTokyo/fall -> | "tokyo" QPizzaLargeBreadsticks/fall -> - 'stór:lo'_kvk_ft/fall? 'brauðstöng:kvk'_ft/fall - | 'brauðstöng:kvk'_ft/fall 'stór:lo'_kvk_ft/fall + 'stór:lo'_kvk_ft/fall? 'brauð-stöng:kvk'_ft/fall + | 'brauð-stöng:kvk'_ft/fall 'stór:lo'_kvk_ft/fall QPizzaSmallBreadsticks/fall -> - 'lítill:lo'_kvk_ft/fall 'brauðstöng:kvk'_ft/fall - | 'brauðstöng:kvk'_ft/fall 'lítill:lo'_kvk_ft/fall + 'lítill:lo'_kvk_ft/fall 'brauð-stöng:kvk'_ft/fall + | 'brauð-stöng:kvk'_ft/fall 'lítill:lo'_kvk_ft/fall QPizzaCoke/fall -> QPizzaCokeWord/fall @@ -322,8 +322,8 @@ QPizzaCokeZero/fall -> | QPizzaCokeWord/fall "án" 'sykur:kk'_et_ef QPizzaBlueCheese/fall -> - 'gráðaostasósa:kvk'_et/fall - | 'gráðostasósa:kvk'_et/fall + 'gráðaosta-sósa:kvk'_et/fall + | 'gráðosta-sósa:kvk'_et/fall | 'gráðaostur:kk'_et/fall | 'gráðostur:kk'_et/fall From 8d75d8489f2c01f7bcdd1e5557d934a6992140b7 Mon Sep 17 00:00:00 2001 From: Logi E Date: Thu, 21 Jul 2022 12:03:06 +0000 Subject: [PATCH 259/371] fixed prefer_over_wrapper bug --- queries/dialogue.py | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index 66683159..d440335e 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -473,34 +473,25 @@ def _find_current_resource(self) -> Resource: using a postorder traversal of the resource graph. """ curr_res: Optional[Resource] = None - wrapper_parent: Optional[Resource] = None def _recurse_resources(resource: Resource) -> None: - nonlocal curr_res, wrapper_parent + nonlocal curr_res if resource.is_confirmed or resource.is_skipped: # Don't set resource as current if it is confirmed or skipped return # Current resource is neither confirmed nor skipped, # so we try to find candidates lower in the tree first - if isinstance(resource, WrapperResource): - # This resource is a wrapper, keep it in a variable - wrapper_parent = resource for child in self._resource_graph[resource]["children"]: - _recurse_resources(child) - if ( - curr_res == child - and wrapper_parent == resource - and not child.prefer_over_wrapper - ): - # If the direct child of a wrapper resource: - # 1. is the current resource - # 2. isn't preferred as current resource over the wrapper - # set the wrapper parent as the current resource instead - curr_res = wrapper_parent + _recurse_resources(child) # TODO: Unwrap recursion? if curr_res is not None: # Found a suitable resource, stop looking return curr_res = resource + while not curr_res.prefer_over_wrapper: + wrapper_parents = [par for par in self._resource_graph[curr_res]["parents"] if isinstance(par, WrapperResource)] + assert len(wrapper_parents) <= 1, "A resource cannot have more than one wrapper parent" + if wrapper_parents: + curr_res = wrapper_parents[0] _recurse_resources(self._resources["Final"]) return curr_res or self._resources["Final"] From 36b74a500ac8faf7393a0ee663e8d92e492fe64d Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Thu, 21 Jul 2022 14:36:32 +0000 Subject: [PATCH 260/371] fixed composites and toppings --- queries/grammars/pizza.grammar | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/queries/grammars/pizza.grammar b/queries/grammars/pizza.grammar index 9f18edcb..f88e9301 100644 --- a/queries/grammars/pizza.grammar +++ b/queries/grammars/pizza.grammar @@ -29,7 +29,7 @@ QPizzaRequestBare -> QPizzaDialogue → QPizzaPrimeAnswer # Requests specifying a single thing. - > QPizzaCompositeAnswer # Request specifying multiple things at once: size, toppings, crust. + > QPizzaCompositeAnswerWrapper # Request specifying multiple things at once: size, toppings, crust. QPizzaPrimeAnswer -> QPizzaNumberAnswer # Answer to the question "How many pizzas do you want?" @@ -89,21 +89,27 @@ QPizzaStatus → | "hver" "var" "staðan" "á"? 'pöntun:kvk'_et/þgf? | QPizzaEgVil? "halda" "áfram" "með" 'pöntun:kvk'_et/þf? +# Wrapper to handle multiple pizza requests at once, "Ég vil kjötveislu með ítölskum botni og stóra margarítu." +QPizzaCompositeAnswerWrapper → + QPizzaCompositeAnswer QPizzaOgCompositeAnswer* + QPizzaCompositeAnswer -> - QPizzaRequestPleasantries? QPizzaSizePhrase/þf QPizzaToppingsCrustPermutation? # e.g. "Ég vil stóra pítsu með ólífum og ananas." + QPizzaRequestPleasantries? QPizzaNum/þf QPizzaSizePhrase/þf QPizzaToppingsCrustPermutation? # e.g. "Ég vil stóra pítsu með ólífum og ananas." + | QPizzaRequestPleasantries? QPizzaNum/þf QPizzaOrMenuWord/þf QPizzaToppingsCrustPermutation? # e.g. "Ég vil tvær margarítur með ítölskum botni." QPizzaToppingsList/fall -> - QPizzaToppingsWord/fall QPizzaOgToppings/fall* + QPizzaToppingsWord/fall QPizzaOgMedToppings/fall* QPizzaSize/fall -> QPizzaSizeLarge/fall | QPizzaSizeMedium/fall | QPizzaSizeSmall/fall -# This wrapper is necessary to prevent the module from reading toppings multiple times. -# Otherwise, it reads "ananas skinku og pepperóní" as "ananas" "skinka" "pepperóní" and "ananas skinka" (2022/07/20). +QPizzaOgCompositeAnswer -> + "og:st"? QPizzaCompositeAnswer + QPizzaOgToppings/fall -> - 'og:st'? QPizzaToppingsWord/fall + 'og:st'? 'með:fs'? QPizzaToppingsWord/fall # It is common to say "miðstærð af pítsu", which is handled separately here. QPizzaSizePhrase/fall -> @@ -114,8 +120,8 @@ QPizzaCrustPhrase/fall -> QPizzaCrustType/fall QPizzaCrustWord/fall? QPizzaToppingsCrustPermutation -> - QPizzaMedToppings? "og"? QPizzaMedCrust - | QPizzaMedCrust? "og"? QPizzaMedToppings + QPizzaMedToppings "og:st"? 'með:fs'? QPizzaCrustPhrase/þgf? + | QPizzaMedCrust "og:st"? 'með:fs'? QPizzaToppingsList/þgf? QPizzaAfPitsuPhrase -> "af" QPizzaOrMenuWord/þgf From b0fdabc8c276985132a9f447f0b270231b5f13a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Thu, 21 Jul 2022 14:46:06 +0000 Subject: [PATCH 261/371] Confirmation when pizzas added to order --- queries/dialogues/pizza.toml | 17 ++++--- queries/pizza.py | 88 +++++++++++++++++++++--------------- 2 files changed, 61 insertions(+), 44 deletions(-) diff --git a/queries/dialogues/pizza.toml b/queries/dialogues/pizza.toml index aa844cec..d6cd412b 100644 --- a/queries/dialogues/pizza.toml +++ b/queries/dialogues/pizza.toml @@ -3,6 +3,7 @@ name = "PizzaOrder" type = "WrapperResource" #requires = ["Pizzas"] #, "Sides", "Sauces", "Drinks"] prompts.initial = "Hvað má bjóða þér?" +prompts.confirmed_pizzas = "{pizzas} var bætt við pöntunina. Var það eitthvað fleira?" # [[resources]] # name = "PickupDelivery" @@ -21,15 +22,18 @@ prompts.timed_out = "Pítsupöntunin þín rann út á tíma. Vinsamlegast byrja name = "Pizza" type = "WrapperResource" requires = ["Type", "Size", "Crust"] -prompts.size = "Hvaða stærð af pítsu viltu fá?" -prompts.crust = "Hvernig botn viltu?" +prompts.initial = "Hvernig á pítsa {number} að vera?" +prompts.initial_single = "Hvernig pítsu viltu?" +prompts.toppings = "Hvað álegg viltu á pítsu {number}?" +prompts.toppings_single = "Hvað álegg viltu á pítsuna?" +prompts.size = "Hversu stór á pítsa {number} að vera?" +prompts.crust = "Hvernig botn viltu á pítsu {number}?" [[dynamic_resources]] name = "Type" type = "OrResource" -requires = ["Toppings", "FromMenu"] #, "Split"] +requires = ["Toppings", "FromMenu"] #, "Split"] prefer_over_wrapper = false -prompts.initial = "Hvernig pítsu viltu fá?" [[dynamic_resources]] name = "Toppings" @@ -40,7 +44,7 @@ name = "FromMenu" [[dynamic_resources]] name = "Size" -prefer_over_wrapper = true +prompts.initial = "Hvaða stærð af pítsu viltu fá?" [[dynamic_resource]] name = "Split" @@ -59,14 +63,13 @@ requires = ["Toppings", "FromMenu"] [[dynamic_resources]] name = "Crust" -prefer_over_wrapper = true # [[dynamic_resources]] # name = "Sides" # type = "ListResource" # [[dynamic_resources]] -# name = "Sauces" +# name = "Dips" # type = "ListResource" # [[dynamic_resources]] diff --git a/queries/pizza.py b/queries/pizza.py index 2a47c687..5d2b2a49 100644 --- a/queries/pizza.py +++ b/queries/pizza.py @@ -26,7 +26,14 @@ from query import Query, QueryStateDict from tree import Result, Node -from queries import AnswerTuple, gen_answer, natlang_seq, parse_num, read_grammar_file +from queries import ( + AnswerTuple, + gen_answer, + natlang_seq, + parse_num, + read_grammar_file, + sing_or_plur, +) from queries.num import number_to_text, numbers_to_ordinal, numbers_to_text from queries.resources import ( FinalResource, @@ -84,6 +91,21 @@ def banned_nonterminals(query: str) -> Set[str]: def _generate_order_answer( resource: WrapperResource, dsm: DialogueStateManager, result: Result ) -> Optional[AnswerTuple]: + ans: str = "" + if dsm.extras.get("confirmed_pizzas", False): + number = dsm.extras["confirmed_pizzas"] + print("Confirmed pizzas", number) + ans = ( + resource.prompts["confirmed_pizzas"] + .format( + pizzas=numbers_to_text( + sing_or_plur(number, "pizzu", "pizzum"), gender="kvk", case="þgf" + ), + ) + .capitalize() + ) + dsm.extras["confirmed_pizzas"] = 0 + return (dict(answer=ans), ans, ans) return gen_answer(resource.prompts["initial"]) @@ -92,14 +114,11 @@ def _generate_pizza_answer( ) -> Optional[AnswerTuple]: print("Generating pizza answer") print("Generate pizza resource name: ", resource.name) - index = resource.name.split("_")[-1] - type_resource: OrResource = cast( - OrResource, dsm.get_resource("Type_{}".format(index)) - ) + type_resource: OrResource = cast(OrResource, dsm.get_children(resource)[0]) print("Type state: {}".format(type_resource.state)) - size_resource: Resource = dsm.get_resource("Size_{}".format(index)) + size_resource: Resource = dsm.get_children(resource)[1] print("Size state: {}".format(size_resource.state)) - crust_resource: Resource = dsm.get_resource("Crust_{}".format(index)) + crust_resource: Resource = dsm.get_children(resource)[2] print("Crust state: {}".format(crust_resource.state)) if resource.is_unfulfilled: print("Unfulfilled pizza") @@ -117,19 +136,6 @@ def _generate_pizza_answer( return gen_answer(resource.prompts["crust"]) -def _generate_type_answer( - resource: WrapperResource, dsm: DialogueStateManager, result: Result -) -> Optional[AnswerTuple]: - print("Generating type answer") - print("Generate type resource name: ", resource.name) - index = resource.name.split("_")[-1] - pizza_resource: Resource = dsm.get_resource("Pizza_{}".format(index)) - print("Pizza state: {}".format(pizza_resource.state)) - if resource.is_unfulfilled: - print("Unfulfilled type") - return gen_answer(resource.prompts["initial"]) - - def QPizzaDialogue(node: Node, params: QueryStateDict, result: Result) -> None: if "qtype" not in result: result.qtype = _PIZZA_QTYPE @@ -146,7 +152,6 @@ def QPizzaNumberAnswer(node: Node, params: QueryStateDict, result: Result) -> No # resource = dsm.get_resource("PizzaCount") number: int = result.get("number", 1) for _ in range(number): - print("CCCCCCAAAAALLLLLLLLLLLLLL") dsm.add_dynamic_resource("Pizza", "PizzaOrder") print("Pizza Count: ", number) @@ -155,22 +160,28 @@ def QPizzaToppingsList(node: Node, params: QueryStateDict, result: Result) -> No print("Toppings in QPizzaToppingsList: ", result.get("toppings", {})) dsm: DialogueStateManager = Query.get_dsm(result) toppings: Dict[str, int] = result.get("toppings", {}) - type_resource: OrResource = cast(OrResource, dsm.current_resource) - print("Current resource in topping list: ", type_resource.name) - index = type_resource.name.split("_")[-1] - toppings_resource = dsm.get_resource("Toppings_{}".format(index)) - pizza_resource = dsm.get_resource("Pizza_{}".format(index)) - print("Toppings resource: ", toppings_resource.name) + pizza_resource = cast(WrapperResource, dsm.current_resource) + print("Pizza resource name: ", pizza_resource.name) + type_resource: OrResource = cast(OrResource, dsm.get_children(pizza_resource)[0]) + print("Type resource name: ", type_resource.name) + toppings_resource = cast(DictResource, dsm.get_children(type_resource)[0]) + print("Toppings resource name: ", toppings_resource.name) + print("Current resource in topping list: ", pizza_resource.name) for (topping, amount) in toppings.items(): toppings_resource.data[topping] = amount print("Toppings in QPizzaToppingsList: ", toppings_resource.data) dsm.skip_other_resources(type_resource, toppings_resource) + print("!!!!A") dsm.set_resource_state(toppings_resource.name, ResourceState.CONFIRMED) + print("!!!!B") dsm.set_resource_state(type_resource.name, ResourceState.CONFIRMED) print("Updating wrapper state with state: ", pizza_resource.state) - dsm.update_wrapper_state(cast(WrapperResource, pizza_resource)) + dsm.update_wrapper_state(pizza_resource) if pizza_resource.state == ResourceState.FULFILLED: dsm.set_resource_state(pizza_resource.name, ResourceState.CONFIRMED) + print("Adding to confirmed pizzas") + dsm.extras["confirmed_pizzas"] = dsm.extras.get("confirmed_pizzas", 0) + 1 + print("Confirmed pizzas: ", dsm.extras["confirmed_pizzas"]) def QPizzaToppingsWord(node: Node, params: QueryStateDict, result: Result) -> None: @@ -188,19 +199,22 @@ def QPizzaNum(node: Node, params: QueryStateDict, result: Result) -> None: result.number = number -def QPizzaSizeAnswer(node: Node, params: QueryStateDict, result: Result) -> None: - print("In QPizzaSizeAnswer") +def QPizzaSizePhrase(node: Node, params: QueryStateDict, result: Result) -> None: + print("In QPizzaSizePhrase") dsm: DialogueStateManager = Query.get_dsm(result) # TODO: Maybe some wrappers should not be set as the current resource? (e.g. here, we have to go through extra steps to get the size resource) # TODO: Better to use Pizza_1 here, as the current resource might be Type_1 instead of Pizza_1 and cause an error - size_resource: Resource = dsm.get_resource( - [i for i in dsm.current_resource.requires if i.startswith("Size")][0] - ) + print("Current resource: ", dsm.current_resource.name) + size_resource: Resource = dsm.get_children(dsm.current_resource)[1] + print("Size resource name: ", size_resource.name) size_resource.data = result.get("pizza_size", "") dsm.set_resource_state(size_resource.name, ResourceState.CONFIRMED) dsm.update_wrapper_state(cast(WrapperResource, dsm.current_resource)) if dsm.current_resource.state == ResourceState.FULFILLED: dsm.set_resource_state(dsm.current_resource.name, ResourceState.CONFIRMED) + print("Adding to confirmed pizzas") + dsm.extras["confirmed_pizzas"] = dsm.extras.get("confirmed_pizzas", 0) + 1 + print("Confirmed pizzas: ", dsm.extras["confirmed_pizzas"]) def QPizzaSizeLarge(node: Node, params: QueryStateDict, result: Result) -> None: @@ -221,15 +235,16 @@ def QPizzaSizeSmall(node: Node, params: QueryStateDict, result: Result) -> None: def QPizzaCrustType(node: Node, params: QueryStateDict, result: Result) -> None: dsm: DialogueStateManager = Query.get_dsm(result) - crust_resource: Resource = dsm.get_resource( - [i for i in dsm.current_resource.requires if i.startswith("Crust")][0] - ) + crust_resource: Resource = dsm.get_children(dsm.current_resource)[2] crust_resource.data = result._text print("Crust resource data: ", crust_resource.data) dsm.set_resource_state(crust_resource.name, ResourceState.CONFIRMED) dsm.update_wrapper_state(cast(WrapperResource, dsm.current_resource)) if dsm.current_resource.state == ResourceState.FULFILLED: dsm.set_resource_state(dsm.current_resource.name, ResourceState.CONFIRMED) + print("Adding to confirmed pizzas") + dsm.extras["confirmed_pizzas"] = dsm.extras.get("confirmed_pizzas", 0) + 1 + print("Confirmed pizzas: ", dsm.extras["confirmed_pizzas"]) def QPizzaPepperoniWord(node: Node, params: QueryStateDict, result: Result) -> None: @@ -247,7 +262,6 @@ def QPizzaMushroomWord(node: Node, params: QueryStateDict, result: Result) -> No _ANSWERING_FUNCTIONS: AnsweringFunctionMap = { "PizzaOrder": _generate_order_answer, "Pizza": _generate_pizza_answer, - "Type": _generate_type_answer, } From f8b09f8cbcd5a8557f748ae7c43998afe2b7b817 Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Thu, 21 Jul 2022 15:27:25 +0000 Subject: [PATCH 262/371] Scene bug fixed & hue grammar updated --- queries/grammars/iot_hue.grammar | 6 ++++++ queries/iot_hue.py | 12 ++++++++++-- queries/iot_spotify.py | 1 + queries/js/IoT_Embla/Philips_Hue/set_lights.js | 15 ++++++++------- 4 files changed, 25 insertions(+), 9 deletions(-) diff --git a/queries/grammars/iot_hue.grammar b/queries/grammars/iot_hue.grammar index 981297e2..141728dc 100644 --- a/queries/grammars/iot_hue.grammar +++ b/queries/grammars/iot_hue.grammar @@ -259,6 +259,9 @@ QIoTSceneWord/fall -> | 'stemning'/fall | 'stemming'/fall | 'stemmning'/fall + | 'sina'/fall + | 'Sena'/fall + | "Sena" # Need to ask Hulda how this works. QIoTSpyrjaHuldu/fall -> @@ -309,6 +312,9 @@ QIoTLessDarkerOrLower/fall -> QIoTWarmer/fall -> 'hlýr:lo'_mst/fall + | 'heitur:lo'_mst/fall + | "hlýri" + QIoTCooler/fall -> 'kaldur:lo'_mst/fall diff --git a/queries/iot_hue.py b/queries/iot_hue.py index 3117a2c9..c5ffc15a 100755 --- a/queries/iot_hue.py +++ b/queries/iot_hue.py @@ -194,6 +194,7 @@ def QIoTIncreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None def QIoTCooler(node: Node, params: QueryStateDict, result: Result) -> None: result.action = "decrease_colortemp" + result.changing_temp = True if "hue_obj" not in result: result["hue_obj"] = {"ct_inc": -30000} else: @@ -202,6 +203,7 @@ def QIoTCooler(node: Node, params: QueryStateDict, result: Result) -> None: def QIoTWarmer(node: Node, params: QueryStateDict, result: Result) -> None: result.action = "increase_colortemp" + result.changing_temp = True if "hue_obj" not in result: result["hue_obj"] = {"ct_inc": 30000} else: @@ -217,7 +219,7 @@ def QIoTDecreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None def QIoTBrightest(node: Node, params: QueryStateDict, result: Result) -> None: - result.action = "decrease_brightness" + result.action = "increase_brightness" if "hue_obj" not in result: result["hue_obj"] = {"bri": 255} else: @@ -252,6 +254,7 @@ def QIoTColorName(node: Node, params: QueryStateDict, result: Result) -> None: def QIoTSceneName(node: Node, params: QueryStateDict, result: Result) -> None: result["scene_name"] = result._indefinite + result["changing_scene"] = True print("scene: " + result.get("scene_name", None)) @@ -343,11 +346,16 @@ def sentence(state: QueryStateDict, result: Result) -> None: changing_color = result.get("changing_color", False) changing_scene = result.get("changing_scene", False) changing_brightness = result.get("changing_brightness", False) - print("error?", sum((changing_color, changing_scene, changing_brightness)) > 1) + # changing_temp = result.get("changing_temp", False) + print( + "error?", + sum((changing_color, changing_scene, changing_brightness)) > 1, + ) if ( sum((changing_color, changing_scene, changing_brightness)) > 1 or "qtype" not in result ): + print("ERROR") q.set_error("E_QUERY_NOT_UNDERSTOOD") return diff --git a/queries/iot_spotify.py b/queries/iot_spotify.py index 4f0467aa..eae16869 100644 --- a/queries/iot_spotify.py +++ b/queries/iot_spotify.py @@ -180,6 +180,7 @@ def sentence(state: QueryStateDict, result: Result) -> None: ) song_url = spotify_client.get_song_by_artist() response = spotify_client.play_song_on_device() + # response = None print("RESPONSE FROM SPOTIFY:", response) if response is None: q.set_url(song_url) diff --git a/queries/js/IoT_Embla/Philips_Hue/set_lights.js b/queries/js/IoT_Embla/Philips_Hue/set_lights.js index 73676005..257940b5 100644 --- a/queries/js/IoT_Embla/Philips_Hue/set_lights.js +++ b/queries/js/IoT_Embla/Philips_Hue/set_lights.js @@ -1,8 +1,8 @@ "use strict"; // Constants to be used when setting lights from HTML -var BRIDGE_IP = "192.168.1.68"; -var USERNAME = "BzdNyxr6mGSHVdQN86UeZP67qp5huJ2Q6TWyTzvz"; +// var BRIDGE_IP = "192.168.1.68"; +// var USERNAME = "BzdNyxr6mGSHVdQN86UeZP67qp5huJ2Q6TWyTzvz"; // TODO: Implement a hotfix for Ikea Tradfri bulbs, since it can only take one argument at a time @@ -55,9 +55,7 @@ function setLights(target, state) { targetObject, allLights ); - console.log("isTradfriBulb:", isTradfriBulb); - if (isTradfriBulb) { - console.log("sending request again"); + if (sceneName && isTradfriBulb) { const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); sleep(450).then(() => { call_api(url, state); @@ -180,9 +178,12 @@ function check_if_if_ikea_bulb_in_group(groupsObject, all_lights) { let lightID = groupsObject.lights[key]; let light = all_lights[lightID]; if ( - light.manufacturername.includes("IKEA") | - light.modelid.includes("TRADFRI") + light.manufacturername.includes("IKEA") || + light.modelid.includes("TRADFRI") || + light.manufacturername.includes("ikea") || + light.manufacturername.includes("tradfri") ); + { return true; } From 79b205eb02204496b3dd025edbb608e665e37a02 Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Thu, 21 Jul 2022 15:47:37 +0000 Subject: [PATCH 263/371] added nominative composite order --- queries/grammars/pizza.grammar | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/queries/grammars/pizza.grammar b/queries/grammars/pizza.grammar index f88e9301..c5b1067d 100644 --- a/queries/grammars/pizza.grammar +++ b/queries/grammars/pizza.grammar @@ -94,8 +94,12 @@ QPizzaCompositeAnswerWrapper → QPizzaCompositeAnswer QPizzaOgCompositeAnswer* QPizzaCompositeAnswer -> - QPizzaRequestPleasantries? QPizzaNum/þf QPizzaSizePhrase/þf QPizzaToppingsCrustPermutation? # e.g. "Ég vil stóra pítsu með ólífum og ananas." - | QPizzaRequestPleasantries? QPizzaNum/þf QPizzaOrMenuWord/þf QPizzaToppingsCrustPermutation? # e.g. "Ég vil tvær margarítur með ítölskum botni." + QPizzaRequestPleasantries? QPizzaCompositeRest/þf # e.g. "Ég vil tólf tommu margarítu með ítölskum botni." + | QPizzaCompositeRest/nf # e.g. "Tólf tommu pítsa með skinku ananas og með ítölskum botni'." + +QPizzaCompositeRest/fall -> + QPizzaNum/fall QPizzaSizePhrase/fall QPizzaToppingsCrustPermutation? # e.g. "Ég vil stóra pítsu með ólífum og ananas." + | QPizzaNum/fall QPizzaOrMenuWord/fall QPizzaToppingsCrustPermutation? # e.g. "Ég vil tvær margarítur með ítölskum botni." QPizzaToppingsList/fall -> QPizzaToppingsWord/fall QPizzaOgMedToppings/fall* @@ -198,21 +202,21 @@ QPizzaDipsWords/fall -> # The actual size is 14.5". QPizzaSizeLarge/fall -> 'stór:lo'/fall - | QPizzaSixteenWord 'tomma:kvk'/fall? - | QPizzaFifteenWord 'tomma:kvk'/fall? - | QPizzaFourteenPointFiveWord 'tomma:kvk'/fall? + | QPizzaSixteenWord 'tomma:kvk'/ef? + | QPizzaFifteenWord 'tomma:kvk'/ef? + | QPizzaFourteenPointFiveWord 'tomma:kvk'/ef? QPizzaSizeMedium/fall -> 'millistór:lo'/fall | 'meðalstór:lo'/fall - | QPizzaTwelveWord 'tomma:kvk'/fall? + | QPizzaTwelveWord 'tomma:kvk'/ef? QPizzaMediumWord/fall -> 'mið-stærð:kvk'/fall QPizzaSizeSmall/fall -> 'lítil:lo'/fall - | QPizzaNineWord 'tomma:kvk'/fall? + | QPizzaNineWord 'tomma:kvk'/ef? QPizzaCrustType/fall -> QPizzaItalianWord/fall From 274cad7aa7ff8c6df768589c3bea2e8dc5205142 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Thu, 21 Jul 2022 15:52:05 +0000 Subject: [PATCH 264/371] Added handling for pizza menu items --- queries/dialogues/pizza.toml | 5 +++ queries/pizza.py | 66 ++++++++++++++++++++++++------------ queries/resources.py | 8 +++++ 3 files changed, 58 insertions(+), 21 deletions(-) diff --git a/queries/dialogues/pizza.toml b/queries/dialogues/pizza.toml index d6cd412b..c2a34167 100644 --- a/queries/dialogues/pizza.toml +++ b/queries/dialogues/pizza.toml @@ -27,7 +27,9 @@ prompts.initial_single = "Hvernig pítsu viltu?" prompts.toppings = "Hvað álegg viltu á pítsu {number}?" prompts.toppings_single = "Hvað álegg viltu á pítsuna?" prompts.size = "Hversu stór á pítsa {number} að vera?" +prompts.size_single = "Hversu stór á pítsan að vera?" prompts.crust = "Hvernig botn viltu á pítsu {number}?" +prompts.crust_single = "Hvernig botn viltu á pítsuna?" [[dynamic_resources]] name = "Type" @@ -41,9 +43,11 @@ type = "DictResource" [[dynamic_resources]] name = "FromMenu" +type = "StringResource" [[dynamic_resources]] name = "Size" +type = "StringResource" prompts.initial = "Hvaða stærð af pítsu viltu fá?" [[dynamic_resource]] @@ -63,6 +67,7 @@ requires = ["Toppings", "FromMenu"] [[dynamic_resources]] name = "Crust" +type = "StringResource" # [[dynamic_resources]] # name = "Sides" diff --git a/queries/pizza.py b/queries/pizza.py index 5d2b2a49..290f24e9 100644 --- a/queries/pizza.py +++ b/queries/pizza.py @@ -39,10 +39,9 @@ FinalResource, ListResource, DictResource, - NumberResource, OrResource, - Resource, ResourceState, + StringResource, WrapperResource, ) from queries.dialogue import ( @@ -116,24 +115,38 @@ def _generate_pizza_answer( print("Generate pizza resource name: ", resource.name) type_resource: OrResource = cast(OrResource, dsm.get_children(resource)[0]) print("Type state: {}".format(type_resource.state)) - size_resource: Resource = dsm.get_children(resource)[1] + size_resource: StringResource = cast(StringResource, dsm.get_children(resource)[1]) print("Size state: {}".format(size_resource.state)) - crust_resource: Resource = dsm.get_children(resource)[2] + crust_resource: StringResource = cast(StringResource, dsm.get_children(resource)[2]) print("Crust state: {}".format(crust_resource.state)) + index = dsm.current_resource.name.split("_")[-1] + number: int = cast(int, index) if resource.is_unfulfilled: print("Unfulfilled pizza") - return gen_answer(resource.prompts["initial"]) + if number == 1: + return gen_answer(resource.prompts["initial_single"]) + return gen_answer( + resource.prompts["initial"].format(number=number_to_text(number)) + ) if resource.is_partially_fulfilled: print("Partially fulfilled pizza") if type_resource.is_confirmed and size_resource.is_unfulfilled: print("Confirmed type, unfulfilled size") - return gen_answer(resource.prompts["size"]) + if number == 1: + return gen_answer(resource.prompts["size_single"]) + return gen_answer( + resource.prompts["size"].format(number=number_to_text(number)) + ) if ( type_resource.is_confirmed and size_resource.is_confirmed and crust_resource.is_unfulfilled ): - return gen_answer(resource.prompts["crust"]) + if number == 1: + return gen_answer(resource.prompts["crust_single"]) + return gen_answer( + resource.prompts["crust"].format(number=number_to_text(number)) + ) def QPizzaDialogue(node: Node, params: QueryStateDict, result: Result) -> None: @@ -157,31 +170,20 @@ def QPizzaNumberAnswer(node: Node, params: QueryStateDict, result: Result) -> No def QPizzaToppingsList(node: Node, params: QueryStateDict, result: Result) -> None: - print("Toppings in QPizzaToppingsList: ", result.get("toppings", {})) dsm: DialogueStateManager = Query.get_dsm(result) toppings: Dict[str, int] = result.get("toppings", {}) - pizza_resource = cast(WrapperResource, dsm.current_resource) - print("Pizza resource name: ", pizza_resource.name) + pizza_resource: WrapperResource = cast(WrapperResource, dsm.current_resource) type_resource: OrResource = cast(OrResource, dsm.get_children(pizza_resource)[0]) - print("Type resource name: ", type_resource.name) toppings_resource = cast(DictResource, dsm.get_children(type_resource)[0]) - print("Toppings resource name: ", toppings_resource.name) - print("Current resource in topping list: ", pizza_resource.name) for (topping, amount) in toppings.items(): toppings_resource.data[topping] = amount - print("Toppings in QPizzaToppingsList: ", toppings_resource.data) dsm.skip_other_resources(type_resource, toppings_resource) - print("!!!!A") dsm.set_resource_state(toppings_resource.name, ResourceState.CONFIRMED) - print("!!!!B") dsm.set_resource_state(type_resource.name, ResourceState.CONFIRMED) - print("Updating wrapper state with state: ", pizza_resource.state) dsm.update_wrapper_state(pizza_resource) if pizza_resource.state == ResourceState.FULFILLED: dsm.set_resource_state(pizza_resource.name, ResourceState.CONFIRMED) - print("Adding to confirmed pizzas") dsm.extras["confirmed_pizzas"] = dsm.extras.get("confirmed_pizzas", 0) + 1 - print("Confirmed pizzas: ", dsm.extras["confirmed_pizzas"]) def QPizzaToppingsWord(node: Node, params: QueryStateDict, result: Result) -> None: @@ -191,6 +193,24 @@ def QPizzaToppingsWord(node: Node, params: QueryStateDict, result: Result) -> No result["toppings"][topping] = 1 # TODO: Add support for extra toppings +def QPizzaMenuWords(node: Node, params: QueryStateDict, result: Result) -> None: + dsm: DialogueStateManager = Query.get_dsm(result) + pizza_resource: WrapperResource = cast(WrapperResource, dsm.current_resource) + type_resource: OrResource = cast(OrResource, dsm.get_children(pizza_resource)[0]) + menu_resource: StringResource = cast( + StringResource, dsm.get_children(type_resource)[1] + ) + menu_resource.data = result._nominative + print("Menu: ", menu_resource.data) + dsm.skip_other_resources(type_resource, menu_resource) + dsm.set_resource_state(menu_resource.name, ResourceState.CONFIRMED) + dsm.set_resource_state(type_resource.name, ResourceState.CONFIRMED) + dsm.update_wrapper_state(pizza_resource) + if pizza_resource.state == ResourceState.FULFILLED: + dsm.set_resource_state(pizza_resource.name, ResourceState.CONFIRMED) + dsm.extras["confirmed_pizzas"] = dsm.extras.get("confirmed_pizzas", 0) + 1 + + def QPizzaNum(node: Node, params: QueryStateDict, result: Result) -> None: number: int = int(parse_num(node, result._nominative)) if "numbers" not in result: @@ -205,7 +225,9 @@ def QPizzaSizePhrase(node: Node, params: QueryStateDict, result: Result) -> None # TODO: Maybe some wrappers should not be set as the current resource? (e.g. here, we have to go through extra steps to get the size resource) # TODO: Better to use Pizza_1 here, as the current resource might be Type_1 instead of Pizza_1 and cause an error print("Current resource: ", dsm.current_resource.name) - size_resource: Resource = dsm.get_children(dsm.current_resource)[1] + size_resource: StringResource = cast( + StringResource, dsm.get_children(dsm.current_resource)[1] + ) print("Size resource name: ", size_resource.name) size_resource.data = result.get("pizza_size", "") dsm.set_resource_state(size_resource.name, ResourceState.CONFIRMED) @@ -235,7 +257,9 @@ def QPizzaSizeSmall(node: Node, params: QueryStateDict, result: Result) -> None: def QPizzaCrustType(node: Node, params: QueryStateDict, result: Result) -> None: dsm: DialogueStateManager = Query.get_dsm(result) - crust_resource: Resource = dsm.get_children(dsm.current_resource)[2] + crust_resource: StringResource = cast( + StringResource, dsm.get_children(dsm.current_resource)[2] + ) crust_resource.data = result._text print("Crust resource data: ", crust_resource.data) dsm.set_resource_state(crust_resource.name, ResourceState.CONFIRMED) diff --git a/queries/resources.py b/queries/resources.py index ff177c41..88c96eb6 100644 --- a/queries/resources.py +++ b/queries/resources.py @@ -246,6 +246,13 @@ class NumberResource(Resource): data: int = 0 +@dataclass(eq=False, repr=False) +class StringResource(Resource): + """Resource representing a string.""" + + data: str = "" + + @dataclass(eq=False, repr=False) # Wrapper when multiple resources are required class WrapperResource(Resource): # Wrappers by default prefer to be the current @@ -284,6 +291,7 @@ class FinalResource(Resource): "TimeResource": TimeResource, "WrapperResource": WrapperResource, "YesNoResource": YesNoResource, + "StringResource": StringResource, } From 0402e99fb6dfe27dd41d438ed2f6f96f7dee236f Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Thu, 21 Jul 2022 15:52:17 +0000 Subject: [PATCH 265/371] optional to specify pizza number --- queries/grammars/pizza.grammar | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/queries/grammars/pizza.grammar b/queries/grammars/pizza.grammar index c5b1067d..97d37002 100644 --- a/queries/grammars/pizza.grammar +++ b/queries/grammars/pizza.grammar @@ -98,8 +98,8 @@ QPizzaCompositeAnswer -> | QPizzaCompositeRest/nf # e.g. "Tólf tommu pítsa með skinku ananas og með ítölskum botni'." QPizzaCompositeRest/fall -> - QPizzaNum/fall QPizzaSizePhrase/fall QPizzaToppingsCrustPermutation? # e.g. "Ég vil stóra pítsu með ólífum og ananas." - | QPizzaNum/fall QPizzaOrMenuWord/fall QPizzaToppingsCrustPermutation? # e.g. "Ég vil tvær margarítur með ítölskum botni." + QPizzaNum/fall? QPizzaSizePhrase/fall QPizzaToppingsCrustPermutation? # e.g. "Ég vil stóra pítsu með ólífum og ananas." + | QPizzaNum/fall? QPizzaOrMenuWord/fall QPizzaToppingsCrustPermutation? # e.g. "Ég vil tvær margarítur með ítölskum botni." QPizzaToppingsList/fall -> QPizzaToppingsWord/fall QPizzaOgMedToppings/fall* From 9b9737776e50319701f37fe63445d6fdaa4bd6cc Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Thu, 21 Jul 2022 15:53:15 +0000 Subject: [PATCH 266/371] name fix --- queries/grammars/pizza.grammar | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/queries/grammars/pizza.grammar b/queries/grammars/pizza.grammar index 97d37002..8ba63df9 100644 --- a/queries/grammars/pizza.grammar +++ b/queries/grammars/pizza.grammar @@ -112,7 +112,7 @@ QPizzaSize/fall -> QPizzaOgCompositeAnswer -> "og:st"? QPizzaCompositeAnswer -QPizzaOgToppings/fall -> +QPizzaOgMedToppings/fall -> 'og:st'? 'með:fs'? QPizzaToppingsWord/fall # It is common to say "miðstærð af pítsu", which is handled separately here. From 992b6e98142ac8122debf00307f7080a79dd81d3 Mon Sep 17 00:00:00 2001 From: Logi E Date: Thu, 21 Jul 2022 16:25:27 +0000 Subject: [PATCH 267/371] added comments --- queries/dialogue.py | 3 ++- queries/resources.py | 17 +---------------- 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index d440335e..11ead69e 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -265,6 +265,7 @@ def add_dynamic_resource(self, resource_name: str, parent_name: str) -> None: ) + 1 ) + # TODO: Only update index for added dynamic resources (don't loop through all, just the added ones) # Adding all dynamic resources to a list for dynamic_resource in obj[_DYNAMIC_RESOURCES_KEY]: assert "name" in dynamic_resource, f"Name missing for dynamic resource" @@ -473,7 +474,7 @@ def _find_current_resource(self) -> Resource: using a postorder traversal of the resource graph. """ curr_res: Optional[Resource] = None - + # TODO: Do we need to prevent repeated reading of the same resource? def _recurse_resources(resource: Resource) -> None: nonlocal curr_res if resource.is_confirmed or resource.is_skipped: diff --git a/queries/resources.py b/queries/resources.py index ff177c41..8cfd3abc 100644 --- a/queries/resources.py +++ b/queries/resources.py @@ -68,7 +68,7 @@ class Resource: prefer_over_wrapper: bool = False # Used for comparing states (which one is earlier/later in the dialogue) order_index: int = 0 - # Extra variables to be used for specific variables + # Extra variables to be used for specific situations extras: Dict[str, Any] = field(default_factory=dict) @property @@ -178,21 +178,6 @@ def set_no(self): self.data = False self.state = ResourceState.CANCELLED # TODO: ? - # def confirm_children(self, dsm: "DialogueStateManager") -> None: - # """Confirm all child/required resources.""" - # ConfirmResource._confirm_children(self, dsm) - - # @staticmethod - # def _confirm_children( - # res: Resource, - # dsm: "DialogueStateManager", - # ) -> None: - # for req in res.requires: - # req_res = dsm.get_resource(req) - # if not isinstance(req_res, ConfirmResource): - # ConfirmResource._confirm_children(req_res, dsm) - # req_res.state = ResourceState.CONFIRMED - @dataclass(eq=False, repr=False) class DateResource(Resource): From b125714932ed15291cd52aebf0728463bdb5b3b2 Mon Sep 17 00:00:00 2001 From: Logi E Date: Thu, 21 Jul 2022 16:31:18 +0000 Subject: [PATCH 268/371] fixed _find_current_resource bug and added finished set --- queries/dialogue.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index 11ead69e..9b9116ef 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -474,16 +474,19 @@ def _find_current_resource(self) -> Resource: using a postorder traversal of the resource graph. """ curr_res: Optional[Resource] = None - # TODO: Do we need to prevent repeated reading of the same resource? + finished_resources: Set[Resource] = set() + def _recurse_resources(resource: Resource) -> None: - nonlocal curr_res + nonlocal curr_res, finished_resources + finished_resources.add(resource) if resource.is_confirmed or resource.is_skipped: # Don't set resource as current if it is confirmed or skipped return # Current resource is neither confirmed nor skipped, # so we try to find candidates lower in the tree first for child in self._resource_graph[resource]["children"]: - _recurse_resources(child) # TODO: Unwrap recursion? + if child not in finished_resources: + _recurse_resources(child) # TODO: Unwrap recursion? if curr_res is not None: # Found a suitable resource, stop looking return @@ -493,6 +496,8 @@ def _recurse_resources(resource: Resource) -> None: assert len(wrapper_parents) <= 1, "A resource cannot have more than one wrapper parent" if wrapper_parents: curr_res = wrapper_parents[0] + else: + break _recurse_resources(self._resources["Final"]) return curr_res or self._resources["Final"] From 44a3b6c28033c1baa93cb4eaa8e68114c560e006 Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Thu, 21 Jul 2022 16:40:46 +0000 Subject: [PATCH 269/371] =?UTF-8?q?sk=C3=ADtamix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- queries/grammars/pizza.grammar | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/queries/grammars/pizza.grammar b/queries/grammars/pizza.grammar index 8ba63df9..2e0daafe 100644 --- a/queries/grammars/pizza.grammar +++ b/queries/grammars/pizza.grammar @@ -97,10 +97,14 @@ QPizzaCompositeAnswer -> QPizzaRequestPleasantries? QPizzaCompositeRest/þf # e.g. "Ég vil tólf tommu margarítu með ítölskum botni." | QPizzaCompositeRest/nf # e.g. "Tólf tommu pítsa með skinku ananas og með ítölskum botni'." -QPizzaCompositeRest/fall -> +# Skítamix. Temporary. +QPizzaCompositeRest/fallxnf -> QPizzaNum/fall? QPizzaSizePhrase/fall QPizzaToppingsCrustPermutation? # e.g. "Ég vil stóra pítsu með ólífum og ananas." | QPizzaNum/fall? QPizzaOrMenuWord/fall QPizzaToppingsCrustPermutation? # e.g. "Ég vil tvær margarítur með ítölskum botni." +QPizzaCompositeRest/nf -> + QPizzaNum/fall? QPizzaSizePhrase/fall QPizzaToppingsCrustPermutation? # e.g. "Ég vil stóra pítsu með ólífum og ananas." + QPizzaToppingsList/fall -> QPizzaToppingsWord/fall QPizzaOgMedToppings/fall* From 8037094096cc44625bc2e72b6fd7d9b1e5075d53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Thu, 21 Jul 2022 16:47:35 +0000 Subject: [PATCH 270/371] Grammar changes to help with pizza hotword --- queries/grammars/pizza.grammar | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/queries/grammars/pizza.grammar b/queries/grammars/pizza.grammar index 2e0daafe..4d71eb27 100644 --- a/queries/grammars/pizza.grammar +++ b/queries/grammars/pizza.grammar @@ -9,19 +9,19 @@ # Currently, the Hotword needs to be top-level, because the QPizza nonterminal is banned. Query → - QPizzaHotWord '?'? - | QPizza '?'? + QPizzaHotWord + | QPizza QPizza → QPizzaQuery QPizzaQuery → - QPizzaDialogue + QPizzaDialogue '?'? # Hotwords are used to initialize the conversation. QPizzaHotWord → - QPizzaWord/nf # e.g. "Pítsa" - | QPizzaRequestBare # e.g. "Ég vil panta pizzu." + QPizzaWord/nf '?'? # e.g. "Pítsa" + | QPizzaRequestBare '?'? # e.g. "Ég vil panta pizzu." # Doesn't allow for any order specification, e.g. the number of pizzas: "Ég vil panta tvær flatbökur." QPizzaRequestBare -> @@ -95,15 +95,16 @@ QPizzaCompositeAnswerWrapper → QPizzaCompositeAnswer -> QPizzaRequestPleasantries? QPizzaCompositeRest/þf # e.g. "Ég vil tólf tommu margarítu með ítölskum botni." - | QPizzaCompositeRest/nf # e.g. "Tólf tommu pítsa með skinku ananas og með ítölskum botni'." + | QPizzaCompositeRestTemp/nf # e.g. "Tólf tommu pítsa með skinku ananas og með ítölskum botni'." # Skítamix. Temporary. -QPizzaCompositeRest/fallxnf -> +QPizzaCompositeRest/fall -> QPizzaNum/fall? QPizzaSizePhrase/fall QPizzaToppingsCrustPermutation? # e.g. "Ég vil stóra pítsu með ólífum og ananas." | QPizzaNum/fall? QPizzaOrMenuWord/fall QPizzaToppingsCrustPermutation? # e.g. "Ég vil tvær margarítur með ítölskum botni." -QPizzaCompositeRest/nf -> +QPizzaCompositeRestTemp/nf -> QPizzaNum/fall? QPizzaSizePhrase/fall QPizzaToppingsCrustPermutation? # e.g. "Ég vil stóra pítsu með ólífum og ananas." + | QPizzaNum/fall? QPizzaOrMenuWord/fall QPizzaToppingsCrustPermutation # e.g. "Ég vil tvær margarítur með ítölskum botni." QPizzaToppingsList/fall -> QPizzaToppingsWord/fall QPizzaOgMedToppings/fall* From f68bf76c2606ba3038cb874684e0345a9a916a65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Thu, 21 Jul 2022 16:58:40 +0000 Subject: [PATCH 271/371] made QPizzaWord optional in QPizzaToppingAnswer grammar --- queries/dialogue.py | 12 +++++++++--- queries/grammars/pizza.grammar | 2 +- queries/pizza.py | 18 +++++++++++++++--- query.py | 5 +++++ 4 files changed, 30 insertions(+), 7 deletions(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index 9b9116ef..b7597baa 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -486,14 +486,20 @@ def _recurse_resources(resource: Resource) -> None: # so we try to find candidates lower in the tree first for child in self._resource_graph[resource]["children"]: if child not in finished_resources: - _recurse_resources(child) # TODO: Unwrap recursion? + _recurse_resources(child) # TODO: Unwrap recursion? if curr_res is not None: # Found a suitable resource, stop looking return curr_res = resource while not curr_res.prefer_over_wrapper: - wrapper_parents = [par for par in self._resource_graph[curr_res]["parents"] if isinstance(par, WrapperResource)] - assert len(wrapper_parents) <= 1, "A resource cannot have more than one wrapper parent" + wrapper_parents = [ + par + for par in self._resource_graph[curr_res]["parents"] + if isinstance(par, WrapperResource) + ] + assert ( + len(wrapper_parents) <= 1 + ), "A resource cannot have more than one wrapper parent" if wrapper_parents: curr_res = wrapper_parents[0] else: diff --git a/queries/grammars/pizza.grammar b/queries/grammars/pizza.grammar index 4d71eb27..aa84ca7c 100644 --- a/queries/grammars/pizza.grammar +++ b/queries/grammars/pizza.grammar @@ -54,7 +54,7 @@ QPizzaMenuAnswer -> QPizzaToppingsAnswer -> QPizzaRequestPleasantries? QPizzaToppingsList/þf QPizzaAPitsunaPhrase? # e.g. "Ég vil skinku, ólífur og pepperóní á pítsuna." - | QPizzaRequestPleasantries? QPizzaWord/þf QPizzaMedToppings # e.g. "Ég vil pizzu með ólífum og ananas." + | QPizzaRequestPleasantries? QPizzaWord/þf? QPizzaMedToppings # e.g. "Ég vil pizzu með ólífum og ananas." # This allows for "Ég vil miðstærð af kjötveislu" to make the composite answer simpler. QPizzaSizeAnswer -> diff --git a/queries/pizza.py b/queries/pizza.py index 290f24e9..62f4352f 100644 --- a/queries/pizza.py +++ b/queries/pizza.py @@ -78,13 +78,19 @@ def help_text(lemma: str) -> str: GRAMMAR = read_grammar_file("pizza") -def banned_nonterminals(query: str) -> Set[str]: +def banned_nonterminals(q: Query) -> Set[str]: """ Returns a set of nonterminals that are not allowed due to the state of the dialogue """ # TODO: Implement this - return set() + banned_nonterminals: set[str] = set() + # if q.dsm.dialogue_name != DIALOGUE_NAME: + # print("Not in pizza dialogue, BANNING QPizzaQuery") + # banned_nonterminals.add("QPizzaQuery") + # print("Banned nonterminals: ", banned_nonterminals) + # return banned_nonterminals + return banned_nonterminals def _generate_order_answer( @@ -105,6 +111,7 @@ def _generate_order_answer( ) dsm.extras["confirmed_pizzas"] = 0 return (dict(answer=ans), ans, ans) + print("!!!!!!!!!!!!!!!!!!!") return gen_answer(resource.prompts["initial"]) @@ -294,6 +301,7 @@ def sentence(state: QueryStateDict, result: Result) -> None: q: Query = state["query"] dsm: DialogueStateManager = q.dsm if dsm.not_in_dialogue(): + print("Not in dialogue") q.set_error("E_QUERY_NOT_UNDERSTOOD") return @@ -308,10 +316,14 @@ def sentence(state: QueryStateDict, result: Result) -> None: print("No answer generated") q.set_error("E_QUERY_NOT_UNDERSTOOD") return - + print("E", result.qtype) q.set_qtype(result.qtype) + print("F", ans) q.set_answer(*ans) + print("G") except Exception as e: + print("Exception: ", e) logging.warning("Exception while processing random query: {0}".format(e)) q.set_error("E_EXCEPTION: {0}".format(e)) raise + return diff --git a/query.py b/query.py index 0fa4d3e3..28ef66a2 100755 --- a/query.py +++ b/query.py @@ -735,10 +735,15 @@ def execute_from_dialogue(self) -> bool: self._tree.process_sentence(state, query_tree) except ResourceNotFoundError: pass + print( + "DO WE HAVE AN ANSWER?", self.has_answer(), self._error + ) if self.has_answer(): + print("HAS ANSWER") # The processor successfully answered the query: We're done # Also save any changes to dialogue data, if needed self.update_dialogue_data() + print("DIALOGUE DATA UPDATED") return True except Exception as e: logging.error( From 419f7ab39848ee078edf4c6d02f5bbdd03b04478 Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Fri, 22 Jul 2022 08:44:33 +0000 Subject: [PATCH 272/371] grammar fix --- queries/grammars/pizza.grammar | 31 ++++++++++++++++++------------- query.py | 4 +++- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/queries/grammars/pizza.grammar b/queries/grammars/pizza.grammar index 2e0daafe..248ebbe5 100644 --- a/queries/grammars/pizza.grammar +++ b/queries/grammars/pizza.grammar @@ -3,33 +3,40 @@ # TODO: Fix the toppings being a set. Doesn't handle "Ég vil skinku, ólífur og auka skinku." # TODO: Add to PizzaRequestBare, start conversation with an order. # TODO: Add the words for margherita to BinPackage +# TODO: Fix bug where "E_QUERY_NOT_UNDERSTOOD" is stored between trees in a single module. +# TODO: Fix composite answer limitation. /þgf = þgf /ef = ef # Currently, the Hotword needs to be top-level, because the QPizza nonterminal is banned. Query → - QPizzaHotWord '?'? - | QPizza '?'? + QPizzaHotWord + | QPizza QPizza → - QPizzaQuery + QPizzaQuery '?'? QPizzaQuery → QPizzaDialogue # Hotwords are used to initialize the conversation. QPizzaHotWord → - QPizzaWord/nf # e.g. "Pítsa" - | QPizzaRequestBare # e.g. "Ég vil panta pizzu." + QPizzaWord/nf '?'? # e.g. "Pítsa" + | QLevelZero '?'? + | QPizzaRequestBare '?'? # e.g. "Ég vil panta pizzu." + +QLevelZero → + "pizza" # Doesn't allow for any order specification, e.g. the number of pizzas: "Ég vil panta tvær flatbökur." QPizzaRequestBare -> QPizzaRequestPleasantries? QPizzaWord/þf + | "rassgat" QPizzaDialogue → QPizzaPrimeAnswer # Requests specifying a single thing. - > QPizzaCompositeAnswerWrapper # Request specifying multiple things at once: size, toppings, crust. + | QPizzaCompositeAnswerWrapper # Request specifying multiple things at once: size, toppings, crust. QPizzaPrimeAnswer -> QPizzaNumberAnswer # Answer to the question "How many pizzas do you want?" @@ -97,13 +104,11 @@ QPizzaCompositeAnswer -> QPizzaRequestPleasantries? QPizzaCompositeRest/þf # e.g. "Ég vil tólf tommu margarítu með ítölskum botni." | QPizzaCompositeRest/nf # e.g. "Tólf tommu pítsa með skinku ananas og með ítölskum botni'." -# Skítamix. Temporary. -QPizzaCompositeRest/fallxnf -> - QPizzaNum/fall? QPizzaSizePhrase/fall QPizzaToppingsCrustPermutation? # e.g. "Ég vil stóra pítsu með ólífum og ananas." - | QPizzaNum/fall? QPizzaOrMenuWord/fall QPizzaToppingsCrustPermutation? # e.g. "Ég vil tvær margarítur með ítölskum botni." - -QPizzaCompositeRest/nf -> - QPizzaNum/fall? QPizzaSizePhrase/fall QPizzaToppingsCrustPermutation? # e.g. "Ég vil stóra pítsu með ólífum og ananas." +# Currently, "I want two large pizzas is not accepted here." +QPizzaCompositeRest/fall -> + QPizzaNum/fall? QPizzaSizePhrase/fall QPizzaToppingsCrustPermutation # e.g. "Ég vil stóra pítsu með ólífum og ananas." + | QPizzaNum/fall? QPizzaMenuWords/fall QPizzaToppingsCrustPermutation # e.g. "Ég vil tvær margarítur með ítölskum botni." + | QPizzaNum/fall QPizzaWord/fall QPizzaToppingsCrustPermutation # e.g. "Ég vil tvær margarítur með ítölskum botni." QPizzaToppingsList/fall -> QPizzaToppingsWord/fall QPizzaOgMedToppings/fall* diff --git a/query.py b/query.py index 0fa4d3e3..ae7654ed 100755 --- a/query.py +++ b/query.py @@ -537,8 +537,10 @@ def _parse( num = Fast_Parser.num_combinations(forest) if num > 1: # Reduce the resulting forest + print(forest._repr(1)) forest = rdc.go(forest) - except ParseError: + except ParseError as e: + print("ParseError: ", e) forest = None num = 0 if num > 0: From a5030354fe21efb821d15dfac9ea87b4fd0d5284 Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Fri, 22 Jul 2022 10:04:04 +0000 Subject: [PATCH 273/371] composite fix --- queries/grammars/pizza.grammar | 29 +++++++++++++++++++++++------ query.py | 1 - 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/queries/grammars/pizza.grammar b/queries/grammars/pizza.grammar index 04234b49..5af43394 100644 --- a/queries/grammars/pizza.grammar +++ b/queries/grammars/pizza.grammar @@ -94,18 +94,23 @@ QPizzaStatus → # Wrapper to handle multiple pizza requests at once, "Ég vil kjötveislu með ítölskum botni og stóra margarítu." QPizzaCompositeAnswerWrapper → - QPizzaCompositeAnswer QPizzaOgCompositeAnswer* + QPizzaCompositeAnswer QPizzaOgCompositeAnswer* QPizzaIVidbot? QPizzaCompositeAnswer -> QPizzaRequestPleasantries? QPizzaCompositeRest/þf # e.g. "Ég vil tólf tommu margarítu með ítölskum botni." - | QPizzaCompositeRestTemp/nf # e.g. "Tólf tommu pítsa með skinku ananas og með ítölskum botni'." + | QPizzaCompositeRest/nf # e.g. "Tólf tommu pítsa með skinku ananas og með ítölskum botni'." -# Currently, "I want two large pizzas is not accepted here." QPizzaCompositeRest/fall -> - QPizzaNum/fall? QPizzaSizePhrase/fall QPizzaToppingsCrustPermutation # e.g. "Ég vil stóra pítsu með ólífum og ananas." - | QPizzaNum/fall? QPizzaMenuWords/fall QPizzaToppingsCrustPermutation # e.g. "Ég vil tvær margarítur með ítölskum botni." + QPizzaNum/fall? QPizzaSizeMenuPhrase/fall QPizzaToppingsCrustPermutation? # e.g. "Ég vil stóra margarítu." + | QPizzaNum/fall? QPizzaSizeOrMenu/fall QPizzaToppingsCrustPermutation # e.g. "Ég vil tvær margarítur með ítölskum botni." + | QPizzaNum/fall QPizzaSizeOrMenu/fall # e.g. "Ég vil tvær stórar pítsur." | QPizzaNum/fall QPizzaWord/fall QPizzaToppingsCrustPermutation # e.g. "Ég vil tvær margarítur með ítölskum botni." +# Ways of mentioning the pizza while specifying exactly one feature. +QPizzaSizeOrMenu/fall -> + QPizzaSizePhrase/fall # e.g. "Tólf tommu pitsa." + | QPizzaMenuWords/fall # e.g. "Margaríta." + QPizzaToppingsList/fall -> QPizzaToppingsWord/fall QPizzaOgMedToppings/fall* @@ -125,6 +130,11 @@ QPizzaSizePhrase/fall -> QPizzaSize/fall QPizzaOrMenuWord/fall? | QPizzaMediumWord/fall QPizzaAfPitsuPhrase? +# This duplicate is a result of difficulties with the composite logic. +QPizzaSizeMenuPhrase/fall -> + QPizzaSize/fall QPizzaMenuWords/fall? + | QPizzaMediumWord/fall QPizzaAfMenuPhrase? + QPizzaCrustPhrase/fall -> QPizzaCrustType/fall QPizzaCrustWord/fall? @@ -133,7 +143,11 @@ QPizzaToppingsCrustPermutation -> | QPizzaMedCrust "og:st"? 'með:fs'? QPizzaToppingsList/þgf? QPizzaAfPitsuPhrase -> - "af" QPizzaOrMenuWord/þgf + "af" QPizzaWord/þgf + +# This duplicate is a result of difficulties with the composite logic. +QPizzaAfMenuPhrase -> + "af" QPizzaMenuWords/þgf QPizzaAPitsunaPhrase -> "á" QPizzaWord/þf @@ -147,6 +161,9 @@ QPizzaMedToppings -> QPizzaMedCrust -> "með" QPizzaCrustPhrase/þgf +QPizzaIVidbot -> + 'í:fs' 'viðbót:kvk'_et/þf + QPizzaOrMenuWord/fall -> QPizzaWord/fall | QPizzaMenuWords/fall diff --git a/query.py b/query.py index 6b864c3f..fb41fb95 100755 --- a/query.py +++ b/query.py @@ -537,7 +537,6 @@ def _parse( num = Fast_Parser.num_combinations(forest) if num > 1: # Reduce the resulting forest - print(forest._repr(1)) forest = rdc.go(forest) except ParseError as e: print("ParseError: ", e) From cb5a15b8eb86e738aa47141d3de25d0cb3ac52c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Fri, 22 Jul 2022 10:46:56 +0000 Subject: [PATCH 274/371] Added pizza description to text answer --- queries/dialogues/pizza.toml | 4 +- queries/grammars/pizza.grammar | 4 +- queries/pizza.py | 107 ++++++++++++++++++++++++++------- 3 files changed, 90 insertions(+), 25 deletions(-) diff --git a/queries/dialogues/pizza.toml b/queries/dialogues/pizza.toml index c2a34167..e59f2c37 100644 --- a/queries/dialogues/pizza.toml +++ b/queries/dialogues/pizza.toml @@ -24,8 +24,8 @@ type = "WrapperResource" requires = ["Type", "Size", "Crust"] prompts.initial = "Hvernig á pítsa {number} að vera?" prompts.initial_single = "Hvernig pítsu viltu?" -prompts.toppings = "Hvað álegg viltu á pítsu {number}?" -prompts.toppings_single = "Hvað álegg viltu á pítsuna?" +prompts.type = "Hvað viltu hafa á pítsu {number}?" +prompts.type_single = "Hvað viltu hafa á pítsunni" prompts.size = "Hversu stór á pítsa {number} að vera?" prompts.size_single = "Hversu stór á pítsan að vera?" prompts.crust = "Hvernig botn viltu á pítsu {number}?" diff --git a/queries/grammars/pizza.grammar b/queries/grammars/pizza.grammar index 04234b49..5007d038 100644 --- a/queries/grammars/pizza.grammar +++ b/queries/grammars/pizza.grammar @@ -98,12 +98,12 @@ QPizzaCompositeAnswerWrapper → QPizzaCompositeAnswer -> QPizzaRequestPleasantries? QPizzaCompositeRest/þf # e.g. "Ég vil tólf tommu margarítu með ítölskum botni." - | QPizzaCompositeRestTemp/nf # e.g. "Tólf tommu pítsa með skinku ananas og með ítölskum botni'." + | QPizzaCompositeRest/nf # e.g. "Tólf tommu pítsa með skinku ananas og með ítölskum botni'." # Currently, "I want two large pizzas is not accepted here." QPizzaCompositeRest/fall -> QPizzaNum/fall? QPizzaSizePhrase/fall QPizzaToppingsCrustPermutation # e.g. "Ég vil stóra pítsu með ólífum og ananas." - | QPizzaNum/fall? QPizzaMenuWords/fall QPizzaToppingsCrustPermutation # e.g. "Ég vil tvær margarítur með ítölskum botni." + | QPizzaNum/fall? QPizzaMenuWords/fall QPizzaToppingsCrustPermutation? # e.g. "Ég vil tvær margarítur með ítölskum botni." | QPizzaNum/fall QPizzaWord/fall QPizzaToppingsCrustPermutation # e.g. "Ég vil tvær margarítur með ítölskum botni." QPizzaToppingsList/fall -> diff --git a/queries/pizza.py b/queries/pizza.py index 62f4352f..8a1c28b4 100644 --- a/queries/pizza.py +++ b/queries/pizza.py @@ -20,6 +20,7 @@ This query module handles dialogue related to ordering pizza. """ +from lzma import is_check_supported from typing import Dict, Optional, Set, cast import logging import random @@ -128,32 +129,74 @@ def _generate_pizza_answer( print("Crust state: {}".format(crust_resource.state)) index = dsm.current_resource.name.split("_")[-1] number: int = cast(int, index) + # Pizza text formatting + pizza_text: str = f"\n" + if any( + confirmed + for confirmed in [ + type_resource.is_confirmed, + size_resource.is_confirmed, + crust_resource.is_confirmed, + ] + ): + pizza_text += f"Pítsa {number}:\n" + if size_resource.is_confirmed: + pizza_text += f" - {size_resource.data.capitalize()}\n" + if crust_resource.is_confirmed: + pizza_text += f" - {crust_resource.data.capitalize()} botn\n" + if type_resource.is_confirmed: + toppings_resource: DictResource = cast( + DictResource, dsm.get_children(type_resource)[0] + ) + if toppings_resource.is_confirmed: + pizza_text += f" - Álegg: \n" + for topping in toppings_resource.data: + pizza_text += f" - {topping.capitalize()}\n" + else: + menu_resource: StringResource = cast( + StringResource, dsm.get_children(type_resource)[1] + ) + pizza_text += f" - Tegund: {menu_resource.data.capitalize()}\n" if resource.is_unfulfilled: print("Unfulfilled pizza") if number == 1: - return gen_answer(resource.prompts["initial_single"]) - return gen_answer( - resource.prompts["initial"].format(number=number_to_text(number)) - ) + ans = resource.prompts["initial_single"] + text_ans = ans + pizza_text + return (dict(answer=text_ans), text_ans, ans) + ans = resource.prompts["initial"].format(number=number_to_text(number)) + text_ans = ans + pizza_text + return (dict(answer=text_ans), text_ans, ans) if resource.is_partially_fulfilled: print("Partially fulfilled pizza") + if type_resource.is_unfulfilled: + if number == 1: + ans = resource.prompts["type_single"] + text_ans = ans + pizza_text + return (dict(answer=text_ans), text_ans, ans) + ans = resource.prompts["type"].format(number=number_to_text(number)) + text_ans = ans + pizza_text + return (dict(answer=text_ans), text_ans, ans) if type_resource.is_confirmed and size_resource.is_unfulfilled: print("Confirmed type, unfulfilled size") if number == 1: - return gen_answer(resource.prompts["size_single"]) - return gen_answer( - resource.prompts["size"].format(number=number_to_text(number)) - ) + ans = resource.prompts["size_single"] + text_ans = ans + pizza_text + return (dict(answer=text_ans), text_ans, ans) + ans = resource.prompts["size"].format(number=number_to_text(number)) + text_ans = ans + pizza_text + return (dict(answer=text_ans), text_ans, ans) if ( type_resource.is_confirmed and size_resource.is_confirmed and crust_resource.is_unfulfilled ): if number == 1: - return gen_answer(resource.prompts["crust_single"]) - return gen_answer( - resource.prompts["crust"].format(number=number_to_text(number)) - ) + ans = resource.prompts["crust_single"] + text_ans = ans + pizza_text + return (dict(answer=text_ans), text_ans, ans) + ans = resource.prompts["crust"].format(number=number_to_text(number)) + text_ans = ans + pizza_text + return (dict(answer=text_ans), text_ans, ans) def QPizzaDialogue(node: Node, params: QueryStateDict, result: Result) -> None: @@ -183,6 +226,7 @@ def QPizzaToppingsList(node: Node, params: QueryStateDict, result: Result) -> No type_resource: OrResource = cast(OrResource, dsm.get_children(pizza_resource)[0]) toppings_resource = cast(DictResource, dsm.get_children(type_resource)[0]) for (topping, amount) in toppings.items(): + print("Topping in for loop: ", topping) toppings_resource.data[topping] = amount dsm.skip_other_resources(type_resource, toppings_resource) dsm.set_resource_state(toppings_resource.name, ResourceState.CONFIRMED) @@ -191,11 +235,12 @@ def QPizzaToppingsList(node: Node, params: QueryStateDict, result: Result) -> No if pizza_resource.state == ResourceState.FULFILLED: dsm.set_resource_state(pizza_resource.name, ResourceState.CONFIRMED) dsm.extras["confirmed_pizzas"] = dsm.extras.get("confirmed_pizzas", 0) + 1 + print("Toppings: ", toppings_resource.name, toppings_resource.data) def QPizzaToppingsWord(node: Node, params: QueryStateDict, result: Result) -> None: topping: str = result.dict.pop("real_name", result._nominative) - if "toppings" not in result: + if "toppings in QPizzaToppingsWord" not in result: result["toppings"] = {} result["toppings"][topping] = 1 # TODO: Add support for extra toppings @@ -218,6 +263,25 @@ def QPizzaMenuWords(node: Node, params: QueryStateDict, result: Result) -> None: dsm.extras["confirmed_pizzas"] = dsm.extras.get("confirmed_pizzas", 0) + 1 +# def QPizzaCompositeRest(node: Node, params: QueryStateDict, result: Result) -> None: +# dsm: DialogueStateManager = Query.get_dsm(result) +# pizza_resource: WrapperResource = cast(WrapperResource, dsm.current_resource) +# type_resource: OrResource = cast(OrResource, dsm.get_children(pizza_resource)[0]) +# size_resource: StringResource = cast( +# StringResource, dsm.get_children(pizza_resource)[1] +# ) +# crust_resource: StringResource = cast( +# StringResource, dsm.get_children(pizza_resource)[2] +# ) +# if ( +# type_resource.is_confirmed +# and size_resource.is_confirmed +# and crust_resource.is_confirmed +# ): +# dsm.set_resource_state(pizza_resource.name, ResourceState.CONFIRMED) +# dsm.extras["confirmed_pizzas"] = dsm.extras.get("confirmed_pizzas", 0) + 1 + + def QPizzaNum(node: Node, params: QueryStateDict, result: Result) -> None: number: int = int(parse_num(node, result._nominative)) if "numbers" not in result: @@ -244,22 +308,23 @@ def QPizzaSizePhrase(node: Node, params: QueryStateDict, result: Result) -> None print("Adding to confirmed pizzas") dsm.extras["confirmed_pizzas"] = dsm.extras.get("confirmed_pizzas", 0) + 1 print("Confirmed pizzas: ", dsm.extras["confirmed_pizzas"]) + print("Size data: ", size_resource.data) def QPizzaSizeLarge(node: Node, params: QueryStateDict, result: Result) -> None: - result.pizza_size = "stóra pítsu" + result.pizza_size = "stór" def QPizzaSizeMedium(node: Node, params: QueryStateDict, result: Result) -> None: - result.pizza_size = "miðstærð af pítsu" + result.pizza_size = "miðstærð" def QPizzaMediumWord(node: Node, params: QueryStateDict, result: Result) -> None: - result.pizza_size = "miðstærð af pítsu" + result.pizza_size = "miðstærð" def QPizzaSizeSmall(node: Node, params: QueryStateDict, result: Result) -> None: - result.pizza_size = "litla pítsu" + result.pizza_size = "lítil" def QPizzaCrustType(node: Node, params: QueryStateDict, result: Result) -> None: @@ -267,7 +332,7 @@ def QPizzaCrustType(node: Node, params: QueryStateDict, result: Result) -> None: crust_resource: StringResource = cast( StringResource, dsm.get_children(dsm.current_resource)[2] ) - crust_resource.data = result._text + crust_resource.data = result._root print("Crust resource data: ", crust_resource.data) dsm.set_resource_state(crust_resource.name, ResourceState.CONFIRMED) dsm.update_wrapper_state(cast(WrapperResource, dsm.current_resource)) @@ -278,15 +343,15 @@ def QPizzaCrustType(node: Node, params: QueryStateDict, result: Result) -> None: print("Confirmed pizzas: ", dsm.extras["confirmed_pizzas"]) -def QPizzaPepperoniWord(node: Node, params: QueryStateDict, result: Result) -> None: +def QPizzaPepperoni(node: Node, params: QueryStateDict, result: Result) -> None: result.real_name = "pepperóní" -def QPizzaOliveWord(node: Node, params: QueryStateDict, result: Result) -> None: +def QPizzaOlive(node: Node, params: QueryStateDict, result: Result) -> None: result.real_name = "ólífur" -def QPizzaMushroomWord(node: Node, params: QueryStateDict, result: Result) -> None: +def QPizzaMushroom(node: Node, params: QueryStateDict, result: Result) -> None: result.real_name = "sveppir" From 0f7c8b071d46db62d40c86a95eeac51f61aea5b6 Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Fri, 22 Jul 2022 10:48:00 +0000 Subject: [PATCH 275/371] grammar fix --- queries/grammars/pizza.grammar | 210 ++++++++++++++++----------------- 1 file changed, 105 insertions(+), 105 deletions(-) diff --git a/queries/grammars/pizza.grammar b/queries/grammars/pizza.grammar index 5af43394..651f1477 100644 --- a/queries/grammars/pizza.grammar +++ b/queries/grammars/pizza.grammar @@ -4,7 +4,6 @@ # TODO: Add to PizzaRequestBare, start conversation with an order. # TODO: Add the words for margherita to BinPackage # TODO: Fix bug where "E_QUERY_NOT_UNDERSTOOD" is stored between trees in a single module. -# TODO: Fix composite answer limitation. /þgf = þgf /ef = ef @@ -25,55 +24,25 @@ QPizzaHotWord → QPizzaWord/nf '?'? # e.g. "Pítsa" | QPizzaRequestBare '?'? # e.g. "Ég vil panta pizzu." -# Doesn't allow for any order specification, e.g. the number of pizzas: "Ég vil panta tvær flatbökur." -QPizzaRequestBare -> +# Doesn't allow for any order specification, e.g. the number of pizzas, only "Ég vil pizzu." +QPizzaRequestBare → QPizzaRequestPleasantries? QPizzaWord/þf - | "rassgat" QPizzaDialogue → - QPizzaPrimeAnswer # Requests specifying a single thing. - | QPizzaCompositeAnswerWrapper # Request specifying multiple things at once: size, toppings, crust. - -QPizzaPrimeAnswer -> - QPizzaNumberAnswer # Answer to the question "How many pizzas do you want?" - | QPizzaMenuOrToppingsAnswer # Answer to the question "What do you want to order?" - | QPizzaSizeAnswer # Answer to the question "What size do you want?" - | QPizzaCrustAnswer # Answer to the question "What kind of crust do you want?" - | QPizzaExtrasAnswer # Answer to the question "Do you want anything with your pizzas?" - | QPizzaCancel # Request to cancel the order. + QPizzaCancel # Request to cancel the order. | QPizzaStatus # Request for the status of the order. + | QPizzaExtrasAnswer # Answer to the question "Do you want anything with your pizzas?" + | QPizzaNumberAndSpecificationAnswer # Answer specifying features of the pizza. -QPizzaNumberAnswer → - QPizzaRequestPleasantries? QPizzaNum/þf QPizzaWord/þf # e.g. "Ég vil kaupa tvær pítsur." - | QPizzaNum/nf QPizzaWord/nf? # e.g. "Tvær pítsur." - -QPizzaMenuOrToppingsAnswer -> - QPizzaMenuAnswer # A request for a pizza off the menu. - | QPizzaToppingsAnswer # A request for a custom pizza. - -QPizzaMenuAnswer -> - QPizzaRequestPleasantries? QPizzaMenuWords/þf # e.g. "Ég vil kjötveislu." - | QPizzaMenuWords/nf # e.g. "Margaríta." - -QPizzaToppingsAnswer -> - QPizzaRequestPleasantries? QPizzaToppingsList/þf QPizzaAPitsunaPhrase? # e.g. "Ég vil skinku, ólífur og pepperóní á pítsuna." - | QPizzaRequestPleasantries? QPizzaWord/þf? QPizzaMedToppings # e.g. "Ég vil pizzu með ólífum og ananas." - -# This allows for "Ég vil miðstærð af kjötveislu" to make the composite answer simpler. -QPizzaSizeAnswer -> - QPizzaRequestPleasantries? QPizzaSizePhrase/þf # e.g. "Ég vil stóra pítsu." or "Ég vil miðstærð af pítsu." - | QPizzaSizePhrase/nf # e.g. "Tólf tommur." - -QPizzaCrustAnswer -> - QPizzaRequestPleasantries? QPizzaCrustPhrase/þf QPizzaAPitsunaPhrase? # e.g. "Ég vil klassískan botn á pítsuna." - | QPizzaRequestPleasantries? QPizzaWord/þf QPizzaMedCrust # e.g. "Ég vil pizzu með ítölskum botni." - | QPizzaCrustPhrase/nf # e.g. "Ítalskur botn." - -QPizzaExtrasAnswer -> +QPizzaExtrasAnswer → QPizzaRequestPleasantries? QPizzaExtraWords/þf QPizzaMedPitsunni? # e.g. "Ég vil brauðstangir með pizzunum." | QPizzaEgVil "líka" QPizzaExtraWords/þf QPizzaMedPitsunni? # e.g. "Ég vil líka kanilgott með pizzunum." | QPizzaExtraWords/nf # e.g. "Kók." +QTheaterYes → "já" "já"* | "endilega" | "já" "takk" | "játakk" | "já" "þakka" "þér" "fyrir" | "já" "takk" "kærlega" "fyrir"? | "jább" "takk"? + +QTheaterNo → "nei" "takk"? | "nei" "nei"* | "neitakk" | "ómögulega" + QPizzaCancel → "ég" "hætti" "við" | QPizzaEgVil? "hætta" "við" 'pöntun:kvk'_et/þf? @@ -92,92 +61,123 @@ QPizzaStatus → | "hver" "var" "staðan" "á"? 'pöntun:kvk'_et/þgf? | QPizzaEgVil? "halda" "áfram" "með" 'pöntun:kvk'_et/þf? +QPizzaNumberAndSpecificationAnswer -> + QPizzaRequestPleasantries? QPizzaNumberAndSpecification/þf + | QPizzaNumberAndSpecification/nf + +QPizzaNumberAndSpecification/fall -> + QPizzaNum/fall? QPizzaSpecification/fall + +QPizzaSpecification/fall → + QPizzaPrimeSpecification/fall # Specifies a single thing. + | QPizzaCompositeSpecificationWrapper/fall # Specifies multiple things at once: size, toppings, crust. + +QPizzaPrimeSpecification/fall → + QPizzaMenuOrToppingsSpecification/fall # Specifying which menu item to order or custom toppings. + | QPizzaSizeSpecification/fall # Specifying the size of the pizza. + | QPizzaCrustSpecification/fall # Specifying the crust type on the pizza. + +QPizzaMenuOrToppingsSpecification/fall → + QPizzaMenuSpecification/fall # A specification for a pizza on the menu. + | QPizzaToppingsSpecification/fall # A specification for a custom pizza. + +QPizzaMenuSpecification/fall → + QPizzaMenuWords/fall # e.g. "Margaríta." + +QPizzaToppingsSpecification/fall → + QPizzaToppingsList/fall QPizzaAPitsunaPhrase? # e.g. "Skinku, ólífur og pepperóní á pítsuna." + | QPizzaWord/fall? QPizzaMedToppings # e.g. "Með ólífum og ananas." + +QPizzaSizeSpecification/fall → + QPizzaSizePhrase/fall # e.g. "Tólf tommu pítsa." + +QPizzaCrustSpecification/fall → + QPizzaCrustPhrase/fall QPizzaAPitsunaPhrase? # e.g. "Klassískan botn á pítsuna." + | QPizzaWord/fall QPizzaMedCrust # e.g. "Pizza með ítölskum botni." + # Wrapper to handle multiple pizza requests at once, "Ég vil kjötveislu með ítölskum botni og stóra margarítu." -QPizzaCompositeAnswerWrapper → - QPizzaCompositeAnswer QPizzaOgCompositeAnswer* QPizzaIVidbot? +QPizzaCompositeSpecificationWrapper/fall → + QPizzaCompositeSpecification/fall QPizzaOgCompositeSpecification/fall* QPizzaIVidbot? -QPizzaCompositeAnswer -> - QPizzaRequestPleasantries? QPizzaCompositeRest/þf # e.g. "Ég vil tólf tommu margarítu með ítölskum botni." - | QPizzaCompositeRest/nf # e.g. "Tólf tommu pítsa með skinku ananas og með ítölskum botni'." +QPizzaCompositeSpecification/fall → + QPizzaCompositeRest/fall # e.g. "Tólf tommu pítsa með skinku ananas og með ítölskum botni'." -QPizzaCompositeRest/fall -> - QPizzaNum/fall? QPizzaSizeMenuPhrase/fall QPizzaToppingsCrustPermutation? # e.g. "Ég vil stóra margarítu." - | QPizzaNum/fall? QPizzaSizeOrMenu/fall QPizzaToppingsCrustPermutation # e.g. "Ég vil tvær margarítur með ítölskum botni." - | QPizzaNum/fall QPizzaSizeOrMenu/fall # e.g. "Ég vil tvær stórar pítsur." - | QPizzaNum/fall QPizzaWord/fall QPizzaToppingsCrustPermutation # e.g. "Ég vil tvær margarítur með ítölskum botni." +QPizzaCompositeRest/fall → + QPizzaSizeMenuPhrase/fall QPizzaToppingsCrustPermutation? # e.g. "Ég vil stóra margarítu." + | QPizzaSizeOrMenu/fall QPizzaToppingsCrustPermutation # e.g. "Ég vil tvær margarítur með ítölskum botni." # Ways of mentioning the pizza while specifying exactly one feature. -QPizzaSizeOrMenu/fall -> +QPizzaSizeOrMenu/fall → QPizzaSizePhrase/fall # e.g. "Tólf tommu pitsa." | QPizzaMenuWords/fall # e.g. "Margaríta." -QPizzaToppingsList/fall -> +QPizzaToppingsList/fall → QPizzaToppingsWord/fall QPizzaOgMedToppings/fall* -QPizzaSize/fall -> +QPizzaSize/fall → QPizzaSizeLarge/fall | QPizzaSizeMedium/fall | QPizzaSizeSmall/fall -QPizzaOgCompositeAnswer -> - "og:st"? QPizzaCompositeAnswer +QPizzaOgCompositeSpecification/fall → + "og:st"? QPizzaCompositeSpecification/fall -QPizzaOgMedToppings/fall -> +QPizzaOgMedToppings/fall → 'og:st'? 'með:fs'? QPizzaToppingsWord/fall # It is common to say "miðstærð af pítsu", which is handled separately here. -QPizzaSizePhrase/fall -> +QPizzaSizePhrase/fall → QPizzaSize/fall QPizzaOrMenuWord/fall? | QPizzaMediumWord/fall QPizzaAfPitsuPhrase? # This duplicate is a result of difficulties with the composite logic. -QPizzaSizeMenuPhrase/fall -> +QPizzaSizeMenuPhrase/fall → QPizzaSize/fall QPizzaMenuWords/fall? | QPizzaMediumWord/fall QPizzaAfMenuPhrase? -QPizzaCrustPhrase/fall -> +QPizzaCrustPhrase/fall → QPizzaCrustType/fall QPizzaCrustWord/fall? -QPizzaToppingsCrustPermutation -> +QPizzaToppingsCrustPermutation → QPizzaMedToppings "og:st"? 'með:fs'? QPizzaCrustPhrase/þgf? | QPizzaMedCrust "og:st"? 'með:fs'? QPizzaToppingsList/þgf? -QPizzaAfPitsuPhrase -> - "af" QPizzaWord/þgf - # This duplicate is a result of difficulties with the composite logic. -QPizzaAfMenuPhrase -> +QPizzaAfMenuPhrase → "af" QPizzaMenuWords/þgf -QPizzaAPitsunaPhrase -> +QPizzaAPitsunaPhrase → "á" QPizzaWord/þf -QPizzaMedPitsunni -> +QPizzaMedPitsunni → "með" QPizzaWord/þgf -QPizzaMedToppings -> +QPizzaMedToppings → "með" QPizzaToppingsList/þgf -QPizzaMedCrust -> +QPizzaMedCrust → "með" QPizzaCrustPhrase/þgf -QPizzaIVidbot -> +QPizzaIVidbot → 'í:fs' 'viðbót:kvk'_et/þf -QPizzaOrMenuWord/fall -> +QPizzaAfPitsuPhrase → + "af" QPizzaWord/þgf + +QPizzaOrMenuWord/fall → QPizzaWord/fall | QPizzaMenuWords/fall # Toppings that are transcribed in different ways are in separate nonterminals for clarity. # This also helps standardize the handling of each topping in the module, i.e. not reading "ólífa" and "ólíva" as separate toppings. -QPizzaToppingsWord/fall -> +QPizzaToppingsWord/fall → QPizzaMushroom/fall | QPizzaPepperoni/fall | 'ananas:kk'/fall | 'skinka:kvk'/fall | QPizzaOlive/fall -QPizzaMenuWords/fall -> +QPizzaMenuWords/fall → 'prinsessa:kvk'/fall | 'dóttir:kvk'/fall | QPizzaMargherita/fall @@ -185,12 +185,12 @@ QPizzaMenuWords/fall -> | 'hvítlauksbrauð:hk'/fall | QPizzaTokyo/fall -QPizzaExtraWords/fall -> +QPizzaExtraWords/fall → QPizzaSidesWords/fall | QPizzaDrinksWords/fall | QPizzaDipsWords/fall -QPizzaSidesWords/fall -> +QPizzaSidesWords/fall → QPizzaLargeBreadsticks/fall | QPizzaSmallBreadsticks/fall | 'lítill:lo'_kvk_ft/fall 'brauðstöng:kvk'_ft/fall @@ -200,7 +200,7 @@ QPizzaSidesWords/fall -> | 'kartöflubátur:kk'_ft/fall | 'vængur:kk'_ft/fall -QPizzaDrinksWords/fall -> +QPizzaDrinksWords/fall → QPizzaCoke/fall | QPizzaCokeZero/fall | "fanta" @@ -210,7 +210,7 @@ QPizzaDrinksWords/fall -> | 'epla-svali:kk'_et/fall | "monster" -QPizzaDipsWords/fall -> +QPizzaDipsWords/fall → 'hvítlauks-olía:kvk'_et/fall | 'hvítlauks-sósa:kvk'_et/fall | 'brauð-stanga-sósa:kvk'_et/fall @@ -222,29 +222,29 @@ QPizzaDipsWords/fall -> # A large pizza at Domino's is typically thought to be 16", some believe it to be 15". # The actual size is 14.5". -QPizzaSizeLarge/fall -> +QPizzaSizeLarge/fall → 'stór:lo'/fall | QPizzaSixteenWord 'tomma:kvk'/ef? | QPizzaFifteenWord 'tomma:kvk'/ef? | QPizzaFourteenPointFiveWord 'tomma:kvk'/ef? -QPizzaSizeMedium/fall -> +QPizzaSizeMedium/fall → 'millistór:lo'/fall | 'meðalstór:lo'/fall | QPizzaTwelveWord 'tomma:kvk'/ef? -QPizzaMediumWord/fall -> +QPizzaMediumWord/fall → 'mið-stærð:kvk'/fall -QPizzaSizeSmall/fall -> +QPizzaSizeSmall/fall → 'lítil:lo'/fall | QPizzaNineWord 'tomma:kvk'/ef? -QPizzaCrustType/fall -> +QPizzaCrustType/fall → QPizzaItalianWord/fall | QPizzaClassicWord/fall -QPizzaRequestPleasantries -> +QPizzaRequestPleasantries → QPizzaEgVil QPizzaKaupaFaraFaPanta? QPizzaEgVil → @@ -265,40 +265,40 @@ QPizzaWord/fall → | 'pítsa:kvk'/fall | 'flatbaka:kvk'/fall -QPizzaSixteenWord -> +QPizzaSixteenWord → "16" | "sextán" -QPizzaFifteenWord -> +QPizzaFifteenWord → "15" | "fimmtán" -QPizzaFourteenPointFiveWord -> +QPizzaFourteenPointFiveWord → QPizzaFourteenWord "komma" QPizzaFiveWord -QPizzaFourteenWord -> +QPizzaFourteenWord → "14" | "fjórtán" -QPizzaFiveWord -> +QPizzaFiveWord → "5" | "fimm" -QPizzaTwelveWord -> +QPizzaTwelveWord → "12" | "tólf" -QPizzaNineWord -> +QPizzaNineWord → "9" | "níu" -QPizzaItalianWord/fall -> +QPizzaItalianWord/fall → 'ítalskur:lo'/fall -QPizzaClassicWord/fall -> +QPizzaClassicWord/fall → 'klassískur:lo'/fall -QPizzaCrustWord/fall -> +QPizzaCrustWord/fall → 'botn:kk'/fall QPizzaNum/fall → @@ -307,30 +307,30 @@ QPizzaNum/fall → # tala is a number ('17') to | töl | tala -QPizzaPepperoni/fall -> +QPizzaPepperoni/fall → 'pepperóní:hk'/fall | "pepperoni" | "pepperóni" | "pepperoní" -QPizzaOlive/fall -> +QPizzaOlive/fall → 'ólífa:kvk'/fall | 'ólíva:kvk'/fall -QPizzaMushroom/fall -> +QPizzaMushroom/fall → 'sveppur:kk'/fall | 'Sveppi:kk' # "Margaríta" is not recognized by bin, so I hack the grammar here to make it work. -QPizzaMargherita_nf -> +QPizzaMargherita_nf → "margaríta" | "margarita" -QPizzaMargherita/fallxnf -> +QPizzaMargherita/fallxnf → "margarítu" | "margaritu" -QPizzaTokyo/fall -> +QPizzaTokyo/fall → 'Tókýó:kvk'/fall | "Tókíó" | "Tokyo" @@ -338,28 +338,28 @@ QPizzaTokyo/fall -> | "tókíó" | "tokyo" -QPizzaLargeBreadsticks/fall -> +QPizzaLargeBreadsticks/fall → 'stór:lo'_kvk_ft/fall? 'brauð-stöng:kvk'_ft/fall | 'brauð-stöng:kvk'_ft/fall 'stór:lo'_kvk_ft/fall -QPizzaSmallBreadsticks/fall -> +QPizzaSmallBreadsticks/fall → 'lítill:lo'_kvk_ft/fall 'brauð-stöng:kvk'_ft/fall | 'brauð-stöng:kvk'_ft/fall 'lítill:lo'_kvk_ft/fall -QPizzaCoke/fall -> +QPizzaCoke/fall → QPizzaCokeWord/fall -QPizzaCokeZero/fall -> +QPizzaCokeZero/fall → QPizzaCokeWord/fall "zero" | QPizzaCokeWord/fall "án" 'sykur:kk'_et_ef -QPizzaBlueCheese/fall -> +QPizzaBlueCheese/fall → 'gráðaosta-sósa:kvk'_et/fall | 'gráðosta-sósa:kvk'_et/fall | 'gráðaostur:kk'_et/fall | 'gráðostur:kk'_et/fall -QPizzaCokeWord/fall -> +QPizzaCokeWord/fall → 'kók:kvk'_et/fall | "kóka-kóla" | "coke" From 112bb6a136a33b3384be4b3daa7a1497d9c487f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Fri, 22 Jul 2022 11:12:56 +0000 Subject: [PATCH 276/371] Start of being able to add a number of pizzas right from the start --- queries/grammars/pizza.grammar | 1 + queries/pizza.py | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/queries/grammars/pizza.grammar b/queries/grammars/pizza.grammar index 651f1477..84605c34 100644 --- a/queries/grammars/pizza.grammar +++ b/queries/grammars/pizza.grammar @@ -67,6 +67,7 @@ QPizzaNumberAndSpecificationAnswer -> QPizzaNumberAndSpecification/fall -> QPizzaNum/fall? QPizzaSpecification/fall + | QPizzaNum/fall QPizzaWord/fall QPizzaSpecification/fall → QPizzaPrimeSpecification/fall # Specifies a single thing. diff --git a/queries/pizza.py b/queries/pizza.py index 8a1c28b4..687cd594 100644 --- a/queries/pizza.py +++ b/queries/pizza.py @@ -210,7 +210,13 @@ def QPizzaHotWord(node: Node, params: QueryStateDict, result: Result) -> None: Query.get_dsm(result).hotword_activated() -def QPizzaNumberAnswer(node: Node, params: QueryStateDict, result: Result) -> None: +def QPizzaNumberAndSpecification( + node: Node, params: QueryStateDict, result: Result +) -> None: + """ + Dynamically adds a number of pizzas to the query + according to the specification. + """ dsm: DialogueStateManager = Query.get_dsm(result) # resource = dsm.get_resource("PizzaCount") number: int = result.get("number", 1) From 712316c18546f7e68eb7155f12159c911c668350 Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Fri, 22 Jul 2022 11:20:14 +0000 Subject: [PATCH 277/371] minor grammar fixes --- queries/grammars/pizza.grammar | 35 +++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/queries/grammars/pizza.grammar b/queries/grammars/pizza.grammar index 651f1477..5e850275 100644 --- a/queries/grammars/pizza.grammar +++ b/queries/grammars/pizza.grammar @@ -35,13 +35,13 @@ QPizzaDialogue → | QPizzaNumberAndSpecificationAnswer # Answer specifying features of the pizza. QPizzaExtrasAnswer → - QPizzaRequestPleasantries? QPizzaExtraWords/þf QPizzaMedPitsunni? # e.g. "Ég vil brauðstangir með pizzunum." - | QPizzaEgVil "líka" QPizzaExtraWords/þf QPizzaMedPitsunni? # e.g. "Ég vil líka kanilgott með pizzunum." + QPizzaRequestPleasantries? QPizzaExtraWords/þf QPizzaMedPitsunniPhrase? # e.g. "Ég vil brauðstangir með pizzunum." + | QPizzaEgVil "líka" QPizzaExtraWords/þf QPizzaMedPitsunniPhrase? # e.g. "Ég vil líka kanilgott með pizzunum." | QPizzaExtraWords/nf # e.g. "Kók." -QTheaterYes → "já" "já"* | "endilega" | "já" "takk" | "játakk" | "já" "þakka" "þér" "fyrir" | "já" "takk" "kærlega" "fyrir"? | "jább" "takk"? +QPizzaYes → "já" "já"* | "endilega" | "já" "takk" | "játakk" | "já" "þakka" "þér" "fyrir" | "já" "takk" "kærlega" "fyrir"? | "jább" "takk"? -QTheaterNo → "nei" "takk"? | "nei" "nei"* | "neitakk" | "ómögulega" +QPizzaNo → "nei" "takk"? | "nei" "nei"* | "neitakk" | "ómögulega" QPizzaCancel → "ég" "hætti" "við" @@ -61,12 +61,13 @@ QPizzaStatus → | "hver" "var" "staðan" "á"? 'pöntun:kvk'_et/þgf? | QPizzaEgVil? "halda" "áfram" "með" 'pöntun:kvk'_et/þf? -QPizzaNumberAndSpecificationAnswer -> +QPizzaNumberAndSpecificationAnswer → QPizzaRequestPleasantries? QPizzaNumberAndSpecification/þf | QPizzaNumberAndSpecification/nf -QPizzaNumberAndSpecification/fall -> +QPizzaNumberAndSpecification/fall → QPizzaNum/fall? QPizzaSpecification/fall + | QPizzaNum/fall QPizzaWord/fall QPizzaSpecification/fall → QPizzaPrimeSpecification/fall # Specifies a single thing. @@ -86,18 +87,18 @@ QPizzaMenuSpecification/fall → QPizzaToppingsSpecification/fall → QPizzaToppingsList/fall QPizzaAPitsunaPhrase? # e.g. "Skinku, ólífur og pepperóní á pítsuna." - | QPizzaWord/fall? QPizzaMedToppings # e.g. "Með ólífum og ananas." + | QPizzaWord/fall? QPizzaMedToppingsPhrase # e.g. "Með ólífum og ananas." QPizzaSizeSpecification/fall → QPizzaSizePhrase/fall # e.g. "Tólf tommu pítsa." QPizzaCrustSpecification/fall → QPizzaCrustPhrase/fall QPizzaAPitsunaPhrase? # e.g. "Klassískan botn á pítsuna." - | QPizzaWord/fall QPizzaMedCrust # e.g. "Pizza með ítölskum botni." + | QPizzaWord/fall QPizzaMedCrustPhrase # e.g. "Pizza með ítölskum botni." # Wrapper to handle multiple pizza requests at once, "Ég vil kjötveislu með ítölskum botni og stóra margarítu." QPizzaCompositeSpecificationWrapper/fall → - QPizzaCompositeSpecification/fall QPizzaOgCompositeSpecification/fall* QPizzaIVidbot? + QPizzaCompositeSpecification/fall QPizzaOgCompositeSpecification/fall* QPizzaIVidbotPhrase? QPizzaCompositeSpecification/fall → QPizzaCompositeRest/fall # e.g. "Tólf tommu pítsa með skinku ananas og með ítölskum botni'." @@ -112,7 +113,7 @@ QPizzaSizeOrMenu/fall → | QPizzaMenuWords/fall # e.g. "Margaríta." QPizzaToppingsList/fall → - QPizzaToppingsWord/fall QPizzaOgMedToppings/fall* + QPizzaToppingsWord/fall QPizzaOgMedToppingsPhrase/fall* QPizzaSize/fall → QPizzaSizeLarge/fall @@ -122,7 +123,7 @@ QPizzaSize/fall → QPizzaOgCompositeSpecification/fall → "og:st"? QPizzaCompositeSpecification/fall -QPizzaOgMedToppings/fall → +QPizzaOgMedToppingsPhrase/fall → 'og:st'? 'með:fs'? QPizzaToppingsWord/fall # It is common to say "miðstærð af pítsu", which is handled separately here. @@ -139,8 +140,8 @@ QPizzaCrustPhrase/fall → QPizzaCrustType/fall QPizzaCrustWord/fall? QPizzaToppingsCrustPermutation → - QPizzaMedToppings "og:st"? 'með:fs'? QPizzaCrustPhrase/þgf? - | QPizzaMedCrust "og:st"? 'með:fs'? QPizzaToppingsList/þgf? + QPizzaMedToppingsPhrase "og:st"? 'með:fs'? QPizzaCrustPhrase/þgf? + | QPizzaMedCrustPhrase "og:st"? 'með:fs'? QPizzaToppingsList/þgf? # This duplicate is a result of difficulties with the composite logic. QPizzaAfMenuPhrase → @@ -149,16 +150,16 @@ QPizzaAfMenuPhrase → QPizzaAPitsunaPhrase → "á" QPizzaWord/þf -QPizzaMedPitsunni → +QPizzaMedPitsunniPhrase → "með" QPizzaWord/þgf -QPizzaMedToppings → +QPizzaMedToppingsPhrase → "með" QPizzaToppingsList/þgf -QPizzaMedCrust → +QPizzaMedCrustPhrase → "með" QPizzaCrustPhrase/þgf -QPizzaIVidbot → +QPizzaIVidbotPhrase → 'í:fs' 'viðbót:kvk'_et/þf QPizzaAfPitsuPhrase → From 23b4534f284741185a61808022e689effb276bff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Fri, 22 Jul 2022 11:41:45 +0000 Subject: [PATCH 278/371] Changed DSM to save the current resource when you call find_current_resource and made pizza dialogue accept adding pizzas directly without having to specify the number first --- queries/dialogue.py | 10 ++- queries/grammars/pizza.grammar | 2 +- queries/pizza.py | 150 +++++++++++++++------------------ 3 files changed, 74 insertions(+), 88 deletions(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index b7597baa..bebc4a4e 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -332,7 +332,7 @@ def dialogue_name(self) -> Optional[str]: @property def current_resource(self) -> Resource: if self._current_resource is None: - self._current_resource = self._find_current_resource() + self._find_current_resource() return self._current_resource def get_resource(self, name: str) -> Resource: @@ -409,7 +409,7 @@ def get_answer( ) -> Optional[AnswerTuple]: if self._answer_tuple is not None: return self._answer_tuple - self._current_resource = self._find_current_resource() + self._find_current_resource() self._answering_functions = answering_functions # Check if dialogue was cancelled # TODO: Change this (have separate cancel method) @@ -468,7 +468,7 @@ def set_resource_state(self, resource_name: str, state: ResourceState): for anc in ancestors: anc.state = ResourceState.UNFULFILLED - def _find_current_resource(self) -> Resource: + def _find_current_resource(self) -> None: """ Finds the current resource in the resource graph using a postorder traversal of the resource graph. @@ -506,7 +506,9 @@ def _recurse_resources(resource: Resource) -> None: break _recurse_resources(self._resources["Final"]) - return curr_res or self._resources["Final"] + if curr_res is not None: + print("CURRENT RESOURCE IN FIND CURRENT RESOURCE: ", curr_res.name) + self._current_resource = curr_res or self._resources["Final"] # TODO: Can we move this function into set_resource_state? def skip_other_resources(self, or_resource: OrResource, resource: Resource) -> None: diff --git a/queries/grammars/pizza.grammar b/queries/grammars/pizza.grammar index 84605c34..a3dc03c1 100644 --- a/queries/grammars/pizza.grammar +++ b/queries/grammars/pizza.grammar @@ -238,7 +238,7 @@ QPizzaMediumWord/fall → 'mið-stærð:kvk'/fall QPizzaSizeSmall/fall → - 'lítil:lo'/fall + 'lítill:lo'/fall | QPizzaNineWord 'tomma:kvk'/ef? QPizzaCrustType/fall → diff --git a/queries/pizza.py b/queries/pizza.py index 687cd594..b8548421 100644 --- a/queries/pizza.py +++ b/queries/pizza.py @@ -215,33 +215,83 @@ def QPizzaNumberAndSpecification( ) -> None: """ Dynamically adds a number of pizzas to the query - according to the specification. + according to the specification that was added in + QPizzaSpecification. """ dsm: DialogueStateManager = Query.get_dsm(result) # resource = dsm.get_resource("PizzaCount") - number: int = result.get("number", 1) - for _ in range(number): - dsm.add_dynamic_resource("Pizza", "PizzaOrder") - print("Pizza Count: ", number) + # number: int = result.get("number", 1) + # for _ in range(number): + # dsm.add_dynamic_resource("Pizza", "PizzaOrder") + # print("Pizza Count: ", number) -def QPizzaToppingsList(node: Node, params: QueryStateDict, result: Result) -> None: +def QPizzaSpecification(node: Node, params: QueryStateDict, result: Result) -> None: + """ + Creates a pizza if there is no current pizza, + otherwise adds ingredients to the current pizza. + """ + print("In QPizzaSpecification") dsm: DialogueStateManager = Query.get_dsm(result) - toppings: Dict[str, int] = result.get("toppings", {}) + resource: WrapperResource = cast(WrapperResource, dsm.current_resource) + print("Current resource: ", resource.name) + if resource.name == "PizzaOrder": + # Create a new pizza + print("Adding new pizza") + dsm.add_dynamic_resource("Pizza", "PizzaOrder") + print("Done adding new pizza") + # Add to the pizza pizza_resource: WrapperResource = cast(WrapperResource, dsm.current_resource) + print("Pizza resource: ", pizza_resource.name) type_resource: OrResource = cast(OrResource, dsm.get_children(pizza_resource)[0]) - toppings_resource = cast(DictResource, dsm.get_children(type_resource)[0]) - for (topping, amount) in toppings.items(): - print("Topping in for loop: ", topping) - toppings_resource.data[topping] = amount - dsm.skip_other_resources(type_resource, toppings_resource) - dsm.set_resource_state(toppings_resource.name, ResourceState.CONFIRMED) - dsm.set_resource_state(type_resource.name, ResourceState.CONFIRMED) + print("Type resource: ", type_resource.name) + toppings: Optional[Dict[str, int]] = result.get("toppings", None) + print("Toppings: ", toppings) + if toppings: + toppings_resource = cast(DictResource, dsm.get_children(type_resource)[0]) + for (topping, amount) in toppings.items(): + print("Topping in for loop: ", topping) + toppings_resource.data[topping] = amount + dsm.skip_other_resources(type_resource, toppings_resource) + dsm.set_resource_state(toppings_resource.name, ResourceState.CONFIRMED) + dsm.set_resource_state(type_resource.name, ResourceState.CONFIRMED) + + menu: Optional[str] = result.get("menu", None) + print("Menu: ", menu) + if menu: + menu_resource: StringResource = cast( + StringResource, dsm.get_children(type_resource)[1] + ) + menu_resource.data = menu + print("Menu: ", menu_resource.data) + dsm.skip_other_resources(type_resource, menu_resource) + dsm.set_resource_state(menu_resource.name, ResourceState.CONFIRMED) + dsm.set_resource_state(type_resource.name, ResourceState.CONFIRMED) + + size: Optional[str] = result.get("pizza_size", None) + print("Size: ", size) + if size: + size_resource: StringResource = cast( + StringResource, dsm.get_children(dsm.current_resource)[1] + ) + print("Size resource name: ", size_resource.name) + size_resource.data = size + dsm.set_resource_state(size_resource.name, ResourceState.CONFIRMED) + + crust: Optional[str] = result.get("crust", None) + print("Crust: ", crust) + if crust: + crust_resource: StringResource = cast( + StringResource, dsm.get_children(dsm.current_resource)[2] + ) + crust_resource.data = crust + print("Crust resource data: ", crust_resource.data) + dsm.set_resource_state(crust_resource.name, ResourceState.CONFIRMED) + dsm.update_wrapper_state(pizza_resource) if pizza_resource.state == ResourceState.FULFILLED: dsm.set_resource_state(pizza_resource.name, ResourceState.CONFIRMED) dsm.extras["confirmed_pizzas"] = dsm.extras.get("confirmed_pizzas", 0) + 1 - print("Toppings: ", toppings_resource.name, toppings_resource.data) def QPizzaToppingsWord(node: Node, params: QueryStateDict, result: Result) -> None: @@ -252,40 +302,7 @@ def QPizzaToppingsWord(node: Node, params: QueryStateDict, result: Result) -> No def QPizzaMenuWords(node: Node, params: QueryStateDict, result: Result) -> None: - dsm: DialogueStateManager = Query.get_dsm(result) - pizza_resource: WrapperResource = cast(WrapperResource, dsm.current_resource) - type_resource: OrResource = cast(OrResource, dsm.get_children(pizza_resource)[0]) - menu_resource: StringResource = cast( - StringResource, dsm.get_children(type_resource)[1] - ) - menu_resource.data = result._nominative - print("Menu: ", menu_resource.data) - dsm.skip_other_resources(type_resource, menu_resource) - dsm.set_resource_state(menu_resource.name, ResourceState.CONFIRMED) - dsm.set_resource_state(type_resource.name, ResourceState.CONFIRMED) - dsm.update_wrapper_state(pizza_resource) - if pizza_resource.state == ResourceState.FULFILLED: - dsm.set_resource_state(pizza_resource.name, ResourceState.CONFIRMED) - dsm.extras["confirmed_pizzas"] = dsm.extras.get("confirmed_pizzas", 0) + 1 - - -# def QPizzaCompositeRest(node: Node, params: QueryStateDict, result: Result) -> None: -# dsm: DialogueStateManager = Query.get_dsm(result) -# pizza_resource: WrapperResource = cast(WrapperResource, dsm.current_resource) -# type_resource: OrResource = cast(OrResource, dsm.get_children(pizza_resource)[0]) -# size_resource: StringResource = cast( -# StringResource, dsm.get_children(pizza_resource)[1] -# ) -# crust_resource: StringResource = cast( -# StringResource, dsm.get_children(pizza_resource)[2] -# ) -# if ( -# type_resource.is_confirmed -# and size_resource.is_confirmed -# and crust_resource.is_confirmed -# ): -# dsm.set_resource_state(pizza_resource.name, ResourceState.CONFIRMED) -# dsm.extras["confirmed_pizzas"] = dsm.extras.get("confirmed_pizzas", 0) + 1 + result.menu = result._nominative def QPizzaNum(node: Node, params: QueryStateDict, result: Result) -> None: @@ -296,27 +313,6 @@ def QPizzaNum(node: Node, params: QueryStateDict, result: Result) -> None: result.number = number -def QPizzaSizePhrase(node: Node, params: QueryStateDict, result: Result) -> None: - print("In QPizzaSizePhrase") - dsm: DialogueStateManager = Query.get_dsm(result) - # TODO: Maybe some wrappers should not be set as the current resource? (e.g. here, we have to go through extra steps to get the size resource) - # TODO: Better to use Pizza_1 here, as the current resource might be Type_1 instead of Pizza_1 and cause an error - print("Current resource: ", dsm.current_resource.name) - size_resource: StringResource = cast( - StringResource, dsm.get_children(dsm.current_resource)[1] - ) - print("Size resource name: ", size_resource.name) - size_resource.data = result.get("pizza_size", "") - dsm.set_resource_state(size_resource.name, ResourceState.CONFIRMED) - dsm.update_wrapper_state(cast(WrapperResource, dsm.current_resource)) - if dsm.current_resource.state == ResourceState.FULFILLED: - dsm.set_resource_state(dsm.current_resource.name, ResourceState.CONFIRMED) - print("Adding to confirmed pizzas") - dsm.extras["confirmed_pizzas"] = dsm.extras.get("confirmed_pizzas", 0) + 1 - print("Confirmed pizzas: ", dsm.extras["confirmed_pizzas"]) - print("Size data: ", size_resource.data) - - def QPizzaSizeLarge(node: Node, params: QueryStateDict, result: Result) -> None: result.pizza_size = "stór" @@ -334,19 +330,7 @@ def QPizzaSizeSmall(node: Node, params: QueryStateDict, result: Result) -> None: def QPizzaCrustType(node: Node, params: QueryStateDict, result: Result) -> None: - dsm: DialogueStateManager = Query.get_dsm(result) - crust_resource: StringResource = cast( - StringResource, dsm.get_children(dsm.current_resource)[2] - ) - crust_resource.data = result._root - print("Crust resource data: ", crust_resource.data) - dsm.set_resource_state(crust_resource.name, ResourceState.CONFIRMED) - dsm.update_wrapper_state(cast(WrapperResource, dsm.current_resource)) - if dsm.current_resource.state == ResourceState.FULFILLED: - dsm.set_resource_state(dsm.current_resource.name, ResourceState.CONFIRMED) - print("Adding to confirmed pizzas") - dsm.extras["confirmed_pizzas"] = dsm.extras.get("confirmed_pizzas", 0) + 1 - print("Confirmed pizzas: ", dsm.extras["confirmed_pizzas"]) + result.crust = result._root def QPizzaPepperoni(node: Node, params: QueryStateDict, result: Result) -> None: From 4fe227cf0e09765a37c548bcbce0afd3625a9e07 Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Fri, 22 Jul 2022 12:05:39 +0000 Subject: [PATCH 279/371] added multiple pizzas --- queries/grammars/pizza.grammar | 75 ++++++++++++++++++++++++---------- 1 file changed, 54 insertions(+), 21 deletions(-) diff --git a/queries/grammars/pizza.grammar b/queries/grammars/pizza.grammar index 5e850275..02a67004 100644 --- a/queries/grammars/pizza.grammar +++ b/queries/grammars/pizza.grammar @@ -4,6 +4,7 @@ # TODO: Add to PizzaRequestBare, start conversation with an order. # TODO: Add the words for margherita to BinPackage # TODO: Fix bug where "E_QUERY_NOT_UNDERSTOOD" is stored between trees in a single module. +# TODO: Fix ugly inches hotfix. /þgf = þgf /ef = ef @@ -29,8 +30,11 @@ QPizzaRequestBare → QPizzaRequestPleasantries? QPizzaWord/þf QPizzaDialogue → - QPizzaCancel # Request to cancel the order. + QPizzaYes + | QPizzaNo + | QPizzaCancel # Request to cancel the order. | QPizzaStatus # Request for the status of the order. + | QPizzaQuestion # Question about the features of a particular pizza. | QPizzaExtrasAnswer # Answer to the question "Do you want anything with your pizzas?" | QPizzaNumberAndSpecificationAnswer # Answer specifying features of the pizza. @@ -61,17 +65,28 @@ QPizzaStatus → | "hver" "var" "staðan" "á"? 'pöntun:kvk'_et/þgf? | QPizzaEgVil? "halda" "áfram" "með" 'pöntun:kvk'_et/þf? +QPizzaQuestion -> + "hvað" "er" "á" QPizzaWord/þgf "númer"? QPizzaNum/nf + | "hvernig" "er" QPizzaWord/þgf "númer"? QPizzaNum/nf + | QPizzaWord/þgf "númer"? QPizzaNum/nf + QPizzaNumberAndSpecificationAnswer → - QPizzaRequestPleasantries? QPizzaNumberAndSpecification/þf - | QPizzaNumberAndSpecification/nf + QPizzaRequestPleasantries? QPizzaNumberAndSpecificationWrapper/þf + | QPizzaNumberAndSpecificationWrapper/nf + +# Wrapper necessary to account for multiple pizza specifications. +QPizzaNumberAndSpecificationWrapper/fall → + QPizzaNumberAndSpecification/fall QPizzaOgNumberAndSpecification/fall* +# The number is outside of the specification as it specifies the number of pizzas with the given specifications. +# This clarity makes the handling in the pizza module easier. QPizzaNumberAndSpecification/fall → QPizzaNum/fall? QPizzaSpecification/fall | QPizzaNum/fall QPizzaWord/fall QPizzaSpecification/fall → QPizzaPrimeSpecification/fall # Specifies a single thing. - | QPizzaCompositeSpecificationWrapper/fall # Specifies multiple things at once: size, toppings, crust. + | QPizzaCompositeSpecification/fall # Specifies multiple things at once: size, toppings, crust. QPizzaPrimeSpecification/fall → QPizzaMenuOrToppingsSpecification/fall # Specifying which menu item to order or custom toppings. @@ -96,14 +111,7 @@ QPizzaCrustSpecification/fall → QPizzaCrustPhrase/fall QPizzaAPitsunaPhrase? # e.g. "Klassískan botn á pítsuna." | QPizzaWord/fall QPizzaMedCrustPhrase # e.g. "Pizza með ítölskum botni." -# Wrapper to handle multiple pizza requests at once, "Ég vil kjötveislu með ítölskum botni og stóra margarítu." -QPizzaCompositeSpecificationWrapper/fall → - QPizzaCompositeSpecification/fall QPizzaOgCompositeSpecification/fall* QPizzaIVidbotPhrase? - QPizzaCompositeSpecification/fall → - QPizzaCompositeRest/fall # e.g. "Tólf tommu pítsa með skinku ananas og með ítölskum botni'." - -QPizzaCompositeRest/fall → QPizzaSizeMenuPhrase/fall QPizzaToppingsCrustPermutation? # e.g. "Ég vil stóra margarítu." | QPizzaSizeOrMenu/fall QPizzaToppingsCrustPermutation # e.g. "Ég vil tvær margarítur með ítölskum botni." @@ -120,11 +128,11 @@ QPizzaSize/fall → | QPizzaSizeMedium/fall | QPizzaSizeSmall/fall -QPizzaOgCompositeSpecification/fall → - "og:st"? QPizzaCompositeSpecification/fall +QPizzaOgNumberAndSpecification/fall → + "og"? "svo"? "síðan"? "líka"? "einnig"? QPizzaNumberAndSpecification/fall QPizzaIVidbotPhrase? QPizzaOgMedToppingsPhrase/fall → - 'og:st'? 'með:fs'? QPizzaToppingsWord/fall + "og"? 'með:fs'? QPizzaToppingsWord/fall # It is common to say "miðstærð af pítsu", which is handled separately here. QPizzaSizePhrase/fall → @@ -225,21 +233,21 @@ QPizzaDipsWords/fall → # The actual size is 14.5". QPizzaSizeLarge/fall → 'stór:lo'/fall - | QPizzaSixteenWord 'tomma:kvk'/ef? - | QPizzaFifteenWord 'tomma:kvk'/ef? - | QPizzaFourteenPointFiveWord 'tomma:kvk'/ef? + | QPizzaSixteenWord QPizzaInchesWord? + | QPizzaFifteenWord QPizzaInchesWord? + | QPizzaFourteenPointFiveWord QPizzaInchesWord? QPizzaSizeMedium/fall → 'millistór:lo'/fall | 'meðalstór:lo'/fall - | QPizzaTwelveWord 'tomma:kvk'/ef? + | QPizzaTwelveWord QPizzaInchesWord? QPizzaMediumWord/fall → 'mið-stærð:kvk'/fall QPizzaSizeSmall/fall → - 'lítil:lo'/fall - | QPizzaNineWord 'tomma:kvk'/ef? + 'lítill:lo'/fall + | QPizzaNineWord QPizzaInchesWord? QPizzaCrustType/fall → QPizzaItalianWord/fall @@ -266,6 +274,13 @@ QPizzaWord/fall → | 'pítsa:kvk'/fall | 'flatbaka:kvk'/fall +# The size nonterminals assume that the word pizza follows them. +# This creates an issue as that requires the inches word to be in the possessive case. +# That is not the case when answering the question "Hversu stór á pizzan að vera?", answer "sextán tommur." +QPizzaInchesWord -> + 'tomma:kvk'/nf + | 'tomma:kvk'/ef + QPizzaSixteenWord → "16" | "sextán" @@ -325,11 +340,29 @@ QPizzaMushroom/fall → # "Margaríta" is not recognized by bin, so I hack the grammar here to make it work. QPizzaMargherita_nf → "margaríta" + | "margarítur" | "margarita" + | "margaritur" + +QPizzaMargherita_þf → + "margarítu" + | "margarítur" + | "margaritu" + | "margaritur" + +QPizzaMargherita_þgf → + "margarítu" + | "margarítum" + | "margaritu" + | "margaritum" -QPizzaMargherita/fallxnf → +QPizzaMargherita_ef → "margarítu" + | "margaríta" + | "margarítna" | "margaritu" + | "margarita" + | "margaritna" QPizzaTokyo/fall → 'Tókýó:kvk'/fall From daf84afe9bcaae42e330959a34c522efa108d9a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Fri, 22 Jul 2022 13:54:37 +0000 Subject: [PATCH 280/371] Status now works for pizzas --- queries/dialogue.py | 3 +- queries/dialogues/pizza.toml | 3 +- queries/pizza.py | 80 +++++++++++++++++++++++++++++------- 3 files changed, 69 insertions(+), 17 deletions(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index bebc4a4e..294d97a7 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -527,7 +527,6 @@ def update_wrapper_state(self, wrapper: WrapperResource) -> None: Updates the state of the wrapper resource based on the state of its children. """ - print("UPDATING WRAPPER STATE", wrapper.state) if wrapper.state == ResourceState.UNFULFILLED: print("Wrapper is unfulfilled") if all( @@ -540,7 +539,7 @@ def update_wrapper_state(self, wrapper: WrapperResource) -> None: return print("At least one child is fulfilled") self.set_resource_state(wrapper.name, ResourceState.PARTIALLY_FULFILLED) - elif wrapper.state == ResourceState.PARTIALLY_FULFILLED: + if wrapper.state == ResourceState.PARTIALLY_FULFILLED: print("Wrapper is partially fulfilled") if any( [ diff --git a/queries/dialogues/pizza.toml b/queries/dialogues/pizza.toml index e59f2c37..1f08f856 100644 --- a/queries/dialogues/pizza.toml +++ b/queries/dialogues/pizza.toml @@ -3,7 +3,8 @@ name = "PizzaOrder" type = "WrapperResource" #requires = ["Pizzas"] #, "Sides", "Sauces", "Drinks"] prompts.initial = "Hvað má bjóða þér?" -prompts.confirmed_pizzas = "{pizzas} var bætt við pöntunina. Var það eitthvað fleira?" +prompts.added_pizzas = "{pizzas} var bætt við pöntunina. Pöntunin inniheldur {total_pizzas}. Var það eitthvað fleira?" +prompts.confirmed_pizzas = "Var það eitthvað fleira?" # [[resources]] # name = "PickupDelivery" diff --git a/queries/pizza.py b/queries/pizza.py index b8548421..e618afd9 100644 --- a/queries/pizza.py +++ b/queries/pizza.py @@ -98,19 +98,31 @@ def _generate_order_answer( resource: WrapperResource, dsm: DialogueStateManager, result: Result ) -> Optional[AnswerTuple]: ans: str = "" - if dsm.extras.get("confirmed_pizzas", False): - number = dsm.extras["confirmed_pizzas"] - print("Confirmed pizzas", number) + + if dsm.extras.get("added_pizzas", False): + total: int = dsm.extras["confirmed_pizzas"] + number: int = dsm.extras["added_pizzas"] + print("Added pizzas", number) ans = ( - resource.prompts["confirmed_pizzas"] + resource.prompts["added_pizzas"] .format( pizzas=numbers_to_text( - sing_or_plur(number, "pizzu", "pizzum"), gender="kvk", case="þgf" + sing_or_plur(number, "pítsu", "pítsum"), gender="kvk", case="þgf" + ), + total_pizzas=numbers_to_text( + sing_or_plur(total, "fullkláraða pítsu", "fullkláraðar pítsur"), + gender="kvk", + case="þf", ), ) .capitalize() ) - dsm.extras["confirmed_pizzas"] = 0 + dsm.extras["added_pizzas"] = 0 + return (dict(answer=ans), ans, ans) + if dsm.extras.get("confirmed_pizzas", False): + total: int = dsm.extras["confirmed_pizzas"] + print("Total pizzas: ", total) + ans = resource.prompts["confirmed_pizzas"] return (dict(answer=ans), ans, ans) print("!!!!!!!!!!!!!!!!!!!") return gen_answer(resource.prompts["initial"]) @@ -240,13 +252,12 @@ def QPizzaSpecification(node: Node, params: QueryStateDict, result: Result) -> N print("Adding new pizza") dsm.add_dynamic_resource("Pizza", "PizzaOrder") print("Done adding new pizza") + dsm.extras["total_pizzas"] = dsm.extras.get("total_pizzas", 0) + 1 + print("Done adding to total pizzas") # Add to the pizza pizza_resource: WrapperResource = cast(WrapperResource, dsm.current_resource) - print("Pizza resource: ", pizza_resource.name) type_resource: OrResource = cast(OrResource, dsm.get_children(pizza_resource)[0]) - print("Type resource: ", type_resource.name) toppings: Optional[Dict[str, int]] = result.get("toppings", None) - print("Toppings: ", toppings) if toppings: toppings_resource = cast(DictResource, dsm.get_children(type_resource)[0]) for (topping, amount) in toppings.items(): @@ -257,17 +268,14 @@ def QPizzaSpecification(node: Node, params: QueryStateDict, result: Result) -> N dsm.set_resource_state(type_resource.name, ResourceState.CONFIRMED) menu: Optional[str] = result.get("menu", None) - print("Menu: ", menu) if menu: menu_resource: StringResource = cast( StringResource, dsm.get_children(type_resource)[1] ) menu_resource.data = menu - print("Menu: ", menu_resource.data) dsm.skip_other_resources(type_resource, menu_resource) dsm.set_resource_state(menu_resource.name, ResourceState.CONFIRMED) dsm.set_resource_state(type_resource.name, ResourceState.CONFIRMED) - size: Optional[str] = result.get("pizza_size", None) print("Size: ", size) if size: @@ -277,6 +285,7 @@ def QPizzaSpecification(node: Node, params: QueryStateDict, result: Result) -> N print("Size resource name: ", size_resource.name) size_resource.data = size dsm.set_resource_state(size_resource.name, ResourceState.CONFIRMED) + print("Size state: ", size_resource.state) crust: Optional[str] = result.get("crust", None) print("Crust: ", crust) @@ -285,13 +294,12 @@ def QPizzaSpecification(node: Node, params: QueryStateDict, result: Result) -> N StringResource, dsm.get_children(dsm.current_resource)[2] ) crust_resource.data = crust - print("Crust resource data: ", crust_resource.data) dsm.set_resource_state(crust_resource.name, ResourceState.CONFIRMED) - dsm.update_wrapper_state(pizza_resource) if pizza_resource.state == ResourceState.FULFILLED: dsm.set_resource_state(pizza_resource.name, ResourceState.CONFIRMED) dsm.extras["confirmed_pizzas"] = dsm.extras.get("confirmed_pizzas", 0) + 1 + dsm.extras["added_pizzas"] = dsm.extras.get("added_pizzas", 0) + 1 def QPizzaToppingsWord(node: Node, params: QueryStateDict, result: Result) -> None: @@ -345,6 +353,50 @@ def QPizzaMushroom(node: Node, params: QueryStateDict, result: Result) -> None: result.real_name = "sveppir" +def QPizzaStatus(node: Node, params: QueryStateDict, result: Result) -> None: + result.qtype = "QPizzaStatus" + dsm: DialogueStateManager = Query.get_dsm(result) + at = dsm.get_answer(_ANSWERING_FUNCTIONS, result) + pizza_string: str = "" + if "confirmed_pizzas" in dsm.extras: + number = dsm.extras["confirmed_pizzas"] + if dsm.extras["confirmed_pizzas"] > 0: + pizza_string = "Pöntunin þín inniheldur {}".format( + numbers_to_text( + sing_or_plur(number, "fullkláraða pítsu", "fullkláraðar pítsur"), + gender="kvk", + case="þf", + ) + ) + print("Pizza status before total") + if "total_pizzas" in dsm.extras: + total = dsm.extras["total_pizzas"] + confirmed = dsm.extras.get("confirmed_pizzas", 0) + if confirmed == 0: + pizza_string = "Pöntunin þín inniheldur" + elif total - confirmed > 0: + pizza_string += " og" + if total - confirmed > 0: + pizza_string += " {}".format( + numbers_to_text( + sing_or_plur( + total - confirmed, "ókláraða pítsu", "ókláraðar pítsur" + ), + gender="kvk", + case="þf", + ) + ) + if total > 0: + pizza_string += ". " + if len(pizza_string) == 0: + pizza_string = "Hingað til eru engar vörur í pöntuninni. " + if at: + (_, ans, voice) = at + ans = pizza_string + ans + voice = pizza_string + voice + dsm.set_answer((dict(answer=ans), ans, voice)) + + _ANSWERING_FUNCTIONS: AnsweringFunctionMap = { "PizzaOrder": _generate_order_answer, "Pizza": _generate_pizza_answer, From 35b8ce90b5d15765dddb04ed1be102e4ca3a9e47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Fri, 22 Jul 2022 14:18:52 +0000 Subject: [PATCH 281/371] You can now finish the pizza dialogue --- queries/dialogues/pizza.toml | 2 +- queries/pizza.py | 42 ++++++++++++++++++++++++------------ 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/queries/dialogues/pizza.toml b/queries/dialogues/pizza.toml index 1f08f856..8230b47c 100644 --- a/queries/dialogues/pizza.toml +++ b/queries/dialogues/pizza.toml @@ -15,7 +15,7 @@ name = "Final" type = "FinalResource" requires = ["PizzaOrder"] prompts.final = "Pítsupöntunin þín er móttekin." -prompts.cancelled = "Móttekið, hætti við pizzu pöntunina." +prompts.cancelled = "Móttekið, hætti við pítsu pöntunina." prompts.timed_out = "Pítsupöntunin þín rann út á tíma. Vinsamlegast byrjaðu aftur." # Dynamic resources that get created when a user orders a pizza diff --git a/queries/pizza.py b/queries/pizza.py index e618afd9..30e9250f 100644 --- a/queries/pizza.py +++ b/queries/pizza.py @@ -103,19 +103,15 @@ def _generate_order_answer( total: int = dsm.extras["confirmed_pizzas"] number: int = dsm.extras["added_pizzas"] print("Added pizzas", number) - ans = ( - resource.prompts["added_pizzas"] - .format( - pizzas=numbers_to_text( - sing_or_plur(number, "pítsu", "pítsum"), gender="kvk", case="þgf" - ), - total_pizzas=numbers_to_text( - sing_or_plur(total, "fullkláraða pítsu", "fullkláraðar pítsur"), - gender="kvk", - case="þf", - ), - ) - .capitalize() + ans = resource.prompts["added_pizzas"].format( + pizzas=numbers_to_text( + sing_or_plur(number, "pítsu", "pítsum"), gender="kvk", case="þgf" + ).capitalize(), + total_pizzas=numbers_to_text( + sing_or_plur(total, "fullkláraða pítsu", "fullkláraðar pítsur"), + gender="kvk", + case="þf", + ), ) dsm.extras["added_pizzas"] = 0 return (dict(answer=ans), ans, ans) @@ -124,7 +120,6 @@ def _generate_order_answer( print("Total pizzas: ", total) ans = resource.prompts["confirmed_pizzas"] return (dict(answer=ans), ans, ans) - print("!!!!!!!!!!!!!!!!!!!") return gen_answer(resource.prompts["initial"]) @@ -211,6 +206,15 @@ def _generate_pizza_answer( return (dict(answer=text_ans), text_ans, ans) +def _generate_final_answer( + resource: FinalResource, dsm: DialogueStateManager, result: Result +) -> Optional[AnswerTuple]: + if resource.is_cancelled: + return gen_answer(resource.prompts["cancelled"]) + + return gen_answer(resource.prompts["final"]) + + def QPizzaDialogue(node: Node, params: QueryStateDict, result: Result) -> None: if "qtype" not in result: result.qtype = _PIZZA_QTYPE @@ -353,6 +357,15 @@ def QPizzaMushroom(node: Node, params: QueryStateDict, result: Result) -> None: result.real_name = "sveppir" +def QPizzaNo(node: Node, params: QueryStateDict, result: Result) -> None: + dsm: DialogueStateManager = Query.get_dsm(result) + resource: WrapperResource = cast(WrapperResource, dsm.current_resource) + print("No resource: ", resource.name) + if resource.name == "PizzaOrder": + dsm.set_resource_state(resource.name, ResourceState.CONFIRMED) + dsm.set_resource_state("Final", ResourceState.CONFIRMED) + + def QPizzaStatus(node: Node, params: QueryStateDict, result: Result) -> None: result.qtype = "QPizzaStatus" dsm: DialogueStateManager = Query.get_dsm(result) @@ -400,6 +413,7 @@ def QPizzaStatus(node: Node, params: QueryStateDict, result: Result) -> None: _ANSWERING_FUNCTIONS: AnsweringFunctionMap = { "PizzaOrder": _generate_order_answer, "Pizza": _generate_pizza_answer, + "Final": _generate_final_answer, } From dcb26e40e33304f9d6ddaedb9ff486038677fdb9 Mon Sep 17 00:00:00 2001 From: Logi E Date: Fri, 22 Jul 2022 14:19:43 +0000 Subject: [PATCH 282/371] added duplicate dynamic resource function --- queries/dialogue.py | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index 294d97a7..9fc07186 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -1,3 +1,4 @@ +import copy from typing import ( Any, Callable, @@ -308,6 +309,34 @@ def _add_child_resource(resource: Resource) -> None: self._initialize_resource_graph() self._find_current_resource() + def duplicate_dynamic_resource(self, original: Resource) -> None: + suffix = ( + len( + [ + i + for i in self._resources + if self.get_resource(i).name.startswith( + original.name.split("_")[0] + "_" + ) + ] + ) + + 1 + ) + + def _recursive_deep_copy(resource: Resource) -> None: + nonlocal suffix, self + new_resource = copy.deepcopy(resource) + prefix = "_".join(new_resource.name.split("_")[:-1]) + new_resource.name = prefix + f"_{suffix}" + self._resources[new_resource.name] = new_resource + for child in self._resource_graph[resource]["children"]: + _recursive_deep_copy(child) + + _recursive_deep_copy(original) + # Initialize the resource graph again with the update resources + self._initialize_resource_graph() + self._find_current_resource() + def hotword_activated(self) -> None: self._in_this_dialogue = True print("In hotword activated") @@ -505,10 +534,10 @@ def _recurse_resources(resource: Resource) -> None: else: break - _recurse_resources(self._resources["Final"]) + _recurse_resources(self._resources[_FINAL_RESOURCE_NAME]) if curr_res is not None: print("CURRENT RESOURCE IN FIND CURRENT RESOURCE: ", curr_res.name) - self._current_resource = curr_res or self._resources["Final"] + self._current_resource = curr_res or self._resources[_FINAL_RESOURCE_NAME] # TODO: Can we move this function into set_resource_state? def skip_other_resources(self, or_resource: OrResource, resource: Resource) -> None: From 726d0af57a3b116878a9e9aa6dfee10c3104be52 Mon Sep 17 00:00:00 2001 From: Logi E Date: Fri, 22 Jul 2022 14:37:38 +0000 Subject: [PATCH 283/371] fixed bug --- queries/dialogue.py | 1 + 1 file changed, 1 insertion(+) diff --git a/queries/dialogue.py b/queries/dialogue.py index 9fc07186..de75ef3d 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -328,6 +328,7 @@ def _recursive_deep_copy(resource: Resource) -> None: new_resource = copy.deepcopy(resource) prefix = "_".join(new_resource.name.split("_")[:-1]) new_resource.name = prefix + f"_{suffix}" + new_resource.requires = [rn + f"_{suffix}" for rn in new_resource.requires] self._resources[new_resource.name] = new_resource for child in self._resource_graph[resource]["children"]: _recursive_deep_copy(child) From 78f26928537c50b1b5e927d5e42342562137a807 Mon Sep 17 00:00:00 2001 From: Logi E Date: Fri, 22 Jul 2022 14:38:39 +0000 Subject: [PATCH 284/371] fixed bug again --- queries/dialogue.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index de75ef3d..df5c2f48 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -328,7 +328,7 @@ def _recursive_deep_copy(resource: Resource) -> None: new_resource = copy.deepcopy(resource) prefix = "_".join(new_resource.name.split("_")[:-1]) new_resource.name = prefix + f"_{suffix}" - new_resource.requires = [rn + f"_{suffix}" for rn in new_resource.requires] + new_resource.requires = ["_".join(rn.split("_")[:-1]) + f"_{suffix}" for rn in new_resource.requires] self._resources[new_resource.name] = new_resource for child in self._resource_graph[resource]["children"]: _recursive_deep_copy(child) From 8f2c70c4125ae2f8733cd3bfd72b027674f55b83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Fri, 22 Jul 2022 15:44:10 +0000 Subject: [PATCH 285/371] Started working on duplication, broken --- queries/dialogue.py | 11 ++++++++- queries/grammars/pizza.grammar | 6 ++--- queries/pizza.py | 42 ++++++++++++++++++++++++++-------- query.py | 3 ++- 4 files changed, 47 insertions(+), 15 deletions(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index df5c2f48..cb5d7484 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -266,6 +266,7 @@ def add_dynamic_resource(self, resource_name: str, parent_name: str) -> None: ) + 1 ) + print("<<<<<<<< DYNAMIC INDEX: ", dynamic_resource_index) # TODO: Only update index for added dynamic resources (don't loop through all, just the added ones) # Adding all dynamic resources to a list for dynamic_resource in obj[_DYNAMIC_RESOURCES_KEY]: @@ -328,11 +329,19 @@ def _recursive_deep_copy(resource: Resource) -> None: new_resource = copy.deepcopy(resource) prefix = "_".join(new_resource.name.split("_")[:-1]) new_resource.name = prefix + f"_{suffix}" - new_resource.requires = ["_".join(rn.split("_")[:-1]) + f"_{suffix}" for rn in new_resource.requires] + new_resource.requires = [ + "_".join(rn.split("_")[:-1]) + f"_{suffix}" + for rn in new_resource.requires + ] self._resources[new_resource.name] = new_resource for child in self._resource_graph[resource]["children"]: _recursive_deep_copy(child) + for parent in self._resource_graph[original]["parents"]: + print("!!!Adding to parent !!!: ", parent.name, original.name) + parent.requires.append(f"Pizza_{suffix}") + print("Parent requirements: ", parent.requires) + _recursive_deep_copy(original) # Initialize the resource graph again with the update resources self._initialize_resource_graph() diff --git a/queries/grammars/pizza.grammar b/queries/grammars/pizza.grammar index 02a67004..087e6b3c 100644 --- a/queries/grammars/pizza.grammar +++ b/queries/grammars/pizza.grammar @@ -136,13 +136,13 @@ QPizzaOgMedToppingsPhrase/fall → # It is common to say "miðstærð af pítsu", which is handled separately here. QPizzaSizePhrase/fall → - QPizzaSize/fall QPizzaOrMenuWord/fall? + QPizzaSize/fall QPizzaWord/fall? | QPizzaMediumWord/fall QPizzaAfPitsuPhrase? # This duplicate is a result of difficulties with the composite logic. QPizzaSizeMenuPhrase/fall → - QPizzaSize/fall QPizzaMenuWords/fall? - | QPizzaMediumWord/fall QPizzaAfMenuPhrase? + QPizzaSize/fall QPizzaMenuWords/fall + | QPizzaMediumWord/fall QPizzaAfMenuPhrase QPizzaCrustPhrase/fall → QPizzaCrustType/fall QPizzaCrustWord/fall? diff --git a/queries/pizza.py b/queries/pizza.py index 30e9250f..e0702be1 100644 --- a/queries/pizza.py +++ b/queries/pizza.py @@ -41,6 +41,7 @@ ListResource, DictResource, OrResource, + Resource, ResourceState, StringResource, WrapperResource, @@ -102,6 +103,9 @@ def _generate_order_answer( if dsm.extras.get("added_pizzas", False): total: int = dsm.extras["confirmed_pizzas"] number: int = dsm.extras["added_pizzas"] + r1 = dsm.get_resource("Pizza_1") + r2 = dsm.get_resource("Pizza_2") + print("Is r1 the same as r2: ", id(r1.state) is id(r2.state)) print("Added pizzas", number) ans = resource.prompts["added_pizzas"].format( pizzas=numbers_to_text( @@ -126,6 +130,9 @@ def _generate_order_answer( def _generate_pizza_answer( resource: WrapperResource, dsm: DialogueStateManager, result: Result ) -> Optional[AnswerTuple]: + order_resource = dsm.get_resource("PizzaOrder") + for child in dsm._resource_graph[order_resource]["children"]: + print("!!!$$$$Pizza: ", child.name, child.state) print("Generating pizza answer") print("Generate pizza resource name: ", resource.name) type_resource: OrResource = cast(OrResource, dsm.get_children(resource)[0]) @@ -235,11 +242,18 @@ def QPizzaNumberAndSpecification( QPizzaSpecification. """ dsm: DialogueStateManager = Query.get_dsm(result) - # resource = dsm.get_resource("PizzaCount") - # number: int = result.get("number", 1) - # for _ in range(number): - # dsm.add_dynamic_resource("Pizza", "PizzaOrder") - # print("Pizza Count: ", number) + print("Getting new pizza") + resource: Resource = result.get("new_pizza", dsm.get_resource("Pizza_1")) + print("!!!! New pizza: ", resource.name) + number: int = result.get("number", 1) - 1 + print("!!!! Number: ", number) + for _ in range(number): + dsm.duplicate_dynamic_resource(resource) + print("Duplicating resource: ", resource.name) + dsm.extras["total_pizzas"] = dsm.extras.get("total_pizzas", 0) + 1 + if resource.is_confirmed: + dsm.extras["confirmed_pizzas"] = dsm.extras.get("confirmed_pizzas", 0) + 1 + dsm.extras["added_pizzas"] = dsm.extras.get("added_pizzas", 0) + 1 def QPizzaSpecification(node: Node, params: QueryStateDict, result: Result) -> None: @@ -248,20 +262,26 @@ def QPizzaSpecification(node: Node, params: QueryStateDict, result: Result) -> N otherwise adds ingredients to the current pizza. """ print("In QPizzaSpecification") + if ( + len(list(node.descendants(lambda x: "QPizzaSpecification" in x.string_self()))) + != 0 + ): + return dsm: DialogueStateManager = Query.get_dsm(result) resource: WrapperResource = cast(WrapperResource, dsm.current_resource) print("Current resource: ", resource.name) - if resource.name == "PizzaOrder": + if resource.name == "PizzaOrder" or (dsm.extras.pop("adding_pizzas", False)): # Create a new pizza print("Adding new pizza") dsm.add_dynamic_resource("Pizza", "PizzaOrder") + dsm.extras["adding_pizzas"] = True print("Done adding new pizza") dsm.extras["total_pizzas"] = dsm.extras.get("total_pizzas", 0) + 1 print("Done adding to total pizzas") # Add to the pizza pizza_resource: WrapperResource = cast(WrapperResource, dsm.current_resource) type_resource: OrResource = cast(OrResource, dsm.get_children(pizza_resource)[0]) - toppings: Optional[Dict[str, int]] = result.get("toppings", None) + toppings: Optional[Dict[str, int]] = result.dict.pop("toppings", None) if toppings: toppings_resource = cast(DictResource, dsm.get_children(type_resource)[0]) for (topping, amount) in toppings.items(): @@ -271,7 +291,7 @@ def QPizzaSpecification(node: Node, params: QueryStateDict, result: Result) -> N dsm.set_resource_state(toppings_resource.name, ResourceState.CONFIRMED) dsm.set_resource_state(type_resource.name, ResourceState.CONFIRMED) - menu: Optional[str] = result.get("menu", None) + menu: Optional[str] = result.dict.pop("menu", None) if menu: menu_resource: StringResource = cast( StringResource, dsm.get_children(type_resource)[1] @@ -280,7 +300,7 @@ def QPizzaSpecification(node: Node, params: QueryStateDict, result: Result) -> N dsm.skip_other_resources(type_resource, menu_resource) dsm.set_resource_state(menu_resource.name, ResourceState.CONFIRMED) dsm.set_resource_state(type_resource.name, ResourceState.CONFIRMED) - size: Optional[str] = result.get("pizza_size", None) + size: Optional[str] = result.dict.pop("pizza_size", None) print("Size: ", size) if size: size_resource: StringResource = cast( @@ -291,7 +311,7 @@ def QPizzaSpecification(node: Node, params: QueryStateDict, result: Result) -> N dsm.set_resource_state(size_resource.name, ResourceState.CONFIRMED) print("Size state: ", size_resource.state) - crust: Optional[str] = result.get("crust", None) + crust: Optional[str] = result.dict.pop("crust", None) print("Crust: ", crust) if crust: crust_resource: StringResource = cast( @@ -304,6 +324,7 @@ def QPizzaSpecification(node: Node, params: QueryStateDict, result: Result) -> N dsm.set_resource_state(pizza_resource.name, ResourceState.CONFIRMED) dsm.extras["confirmed_pizzas"] = dsm.extras.get("confirmed_pizzas", 0) + 1 dsm.extras["added_pizzas"] = dsm.extras.get("added_pizzas", 0) + 1 + result["new_pizza"] = pizza_resource def QPizzaToppingsWord(node: Node, params: QueryStateDict, result: Result) -> None: @@ -315,6 +336,7 @@ def QPizzaToppingsWord(node: Node, params: QueryStateDict, result: Result) -> No def QPizzaMenuWords(node: Node, params: QueryStateDict, result: Result) -> None: result.menu = result._nominative + # TODO: If multiple menu items added at the same time it will be in plural form def QPizzaNum(node: Node, params: QueryStateDict, result: Result) -> None: diff --git a/query.py b/query.py index fb41fb95..1e818a11 100755 --- a/query.py +++ b/query.py @@ -734,7 +734,8 @@ def execute_from_dialogue(self) -> bool: # Yes: hand the query tree over to the processor try: self._tree.process_sentence(state, query_tree) - except ResourceNotFoundError: + except ResourceNotFoundError as e: + print("Resource not found: ", e) pass print( "DO WE HAVE AN ANSWER?", self.has_answer(), self._error From eba1f1c295482d9ee3fc048a489b150d49cc9730 Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Sat, 23 Jul 2022 00:31:28 +0000 Subject: [PATCH 286/371] . --- queries/grammars/pizza.grammar | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/queries/grammars/pizza.grammar b/queries/grammars/pizza.grammar index 02a67004..087e6b3c 100644 --- a/queries/grammars/pizza.grammar +++ b/queries/grammars/pizza.grammar @@ -136,13 +136,13 @@ QPizzaOgMedToppingsPhrase/fall → # It is common to say "miðstærð af pítsu", which is handled separately here. QPizzaSizePhrase/fall → - QPizzaSize/fall QPizzaOrMenuWord/fall? + QPizzaSize/fall QPizzaWord/fall? | QPizzaMediumWord/fall QPizzaAfPitsuPhrase? # This duplicate is a result of difficulties with the composite logic. QPizzaSizeMenuPhrase/fall → - QPizzaSize/fall QPizzaMenuWords/fall? - | QPizzaMediumWord/fall QPizzaAfMenuPhrase? + QPizzaSize/fall QPizzaMenuWords/fall + | QPizzaMediumWord/fall QPizzaAfMenuPhrase QPizzaCrustPhrase/fall → QPizzaCrustType/fall QPizzaCrustWord/fall? From 81bd37dd24bd78d9c1e9548a7d5d7f48532c8423 Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Mon, 25 Jul 2022 15:20:12 +0000 Subject: [PATCH 287/371] fix attempt --- queries/grammars/pizza.grammar | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/queries/grammars/pizza.grammar b/queries/grammars/pizza.grammar index 087e6b3c..d9f133db 100644 --- a/queries/grammars/pizza.grammar +++ b/queries/grammars/pizza.grammar @@ -81,8 +81,9 @@ QPizzaNumberAndSpecificationWrapper/fall → # The number is outside of the specification as it specifies the number of pizzas with the given specifications. # This clarity makes the handling in the pizza module easier. QPizzaNumberAndSpecification/fall → - QPizzaNum/fall? QPizzaSpecification/fall + QPizzaNum/fall? QPizzaCompositeSpecification/fall | QPizzaNum/fall QPizzaWord/fall + | QPizzaNum/fall QPizzaPrimeSpecification/fall QPizzaSpecification/fall → QPizzaPrimeSpecification/fall # Specifies a single thing. From b705700099df5f8132df5d3aadbe5e2a4386d236 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Mon, 25 Jul 2022 15:21:32 +0000 Subject: [PATCH 288/371] Adding multiple pizzas works now --- queries/dialogue.py | 3 ++- queries/pizza.py | 20 ++++++++++++++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index cb5d7484..577c631c 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -326,7 +326,7 @@ def duplicate_dynamic_resource(self, original: Resource) -> None: def _recursive_deep_copy(resource: Resource) -> None: nonlocal suffix, self - new_resource = copy.deepcopy(resource) + new_resource = RESOURCE_MAP[resource.type](**resource.__dict__) prefix = "_".join(new_resource.name.split("_")[:-1]) new_resource.name = prefix + f"_{suffix}" new_resource.requires = [ @@ -334,6 +334,7 @@ def _recursive_deep_copy(resource: Resource) -> None: for rn in new_resource.requires ] self._resources[new_resource.name] = new_resource + print("!!!!!!New resource: ", new_resource.__dict__) for child in self._resource_graph[resource]["children"]: _recursive_deep_copy(child) diff --git a/queries/pizza.py b/queries/pizza.py index e0702be1..53e0910a 100644 --- a/queries/pizza.py +++ b/queries/pizza.py @@ -103,9 +103,9 @@ def _generate_order_answer( if dsm.extras.get("added_pizzas", False): total: int = dsm.extras["confirmed_pizzas"] number: int = dsm.extras["added_pizzas"] - r1 = dsm.get_resource("Pizza_1") - r2 = dsm.get_resource("Pizza_2") - print("Is r1 the same as r2: ", id(r1.state) is id(r2.state)) + # r1 = dsm.get_resource("Pizza_1") + # r2 = dsm.get_resource("Pizza_2") + # print("Is r1 the same as r2: ", id(r1.state) is id(r2.state)) print("Added pizzas", number) ans = resource.prompts["added_pizzas"].format( pizzas=numbers_to_text( @@ -233,6 +233,13 @@ def QPizzaHotWord(node: Node, params: QueryStateDict, result: Result) -> None: Query.get_dsm(result).hotword_activated() +def QPizzaNumberAndSpecificationWrapper( + node: Node, params: QueryStateDict, result: Result +) -> None: + dsm: DialogueStateManager = Query.get_dsm(result) + dsm.extras.pop("adding_pizzas", None) + + def QPizzaNumberAndSpecification( node: Node, params: QueryStateDict, result: Result ) -> None: @@ -270,6 +277,11 @@ def QPizzaSpecification(node: Node, params: QueryStateDict, result: Result) -> N dsm: DialogueStateManager = Query.get_dsm(result) resource: WrapperResource = cast(WrapperResource, dsm.current_resource) print("Current resource: ", resource.name) + print("Resource.name == PizzaOrder", resource.name == "PizzaOrder") + print( + "(dsm.extras.pop(adding_pizzas, False): ", + (dsm.extras.get("adding_pizzas", False)), + ) if resource.name == "PizzaOrder" or (dsm.extras.pop("adding_pizzas", False)): # Create a new pizza print("Adding new pizza") @@ -335,7 +347,7 @@ def QPizzaToppingsWord(node: Node, params: QueryStateDict, result: Result) -> No def QPizzaMenuWords(node: Node, params: QueryStateDict, result: Result) -> None: - result.menu = result._nominative + result.menu = result._root # TODO: If multiple menu items added at the same time it will be in plural form From 400721a7398b99c08595e91f88f8a70ed024847d Mon Sep 17 00:00:00 2001 From: hrolfurinn <106666932+hrolfurinn@users.noreply.github.com> Date: Mon, 25 Jul 2022 15:24:04 +0000 Subject: [PATCH 289/371] undo --- queries/grammars/pizza.grammar | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/queries/grammars/pizza.grammar b/queries/grammars/pizza.grammar index d9f133db..a6d4fe3b 100644 --- a/queries/grammars/pizza.grammar +++ b/queries/grammars/pizza.grammar @@ -5,6 +5,7 @@ # TODO: Add the words for margherita to BinPackage # TODO: Fix bug where "E_QUERY_NOT_UNDERSTOOD" is stored between trees in a single module. # TODO: Fix ugly inches hotfix. +# TODO: Fix the requirement of saying the number of pizzas to make a prime specification. /þgf = þgf /ef = ef @@ -81,9 +82,8 @@ QPizzaNumberAndSpecificationWrapper/fall → # The number is outside of the specification as it specifies the number of pizzas with the given specifications. # This clarity makes the handling in the pizza module easier. QPizzaNumberAndSpecification/fall → - QPizzaNum/fall? QPizzaCompositeSpecification/fall + QPizzaNum/fall? QPizzaSpecification/fall | QPizzaNum/fall QPizzaWord/fall - | QPizzaNum/fall QPizzaPrimeSpecification/fall QPizzaSpecification/fall → QPizzaPrimeSpecification/fall # Specifies a single thing. From 7abf12e244003e2ba401ba1ebc108ac0e3902e05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Mon, 25 Jul 2022 16:32:01 +0000 Subject: [PATCH 290/371] Adding multiple pizzas moved to wrapper and in correct order now --- queries/dialogue.py | 16 +--- queries/pizza.py | 200 +++++++++++++++++++++++++------------------- 2 files changed, 113 insertions(+), 103 deletions(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index 577c631c..17e32e64 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -156,7 +156,6 @@ def setup_resources(self) -> None: """ Load dialogue resources from TOML file and update their state from database data. """ - print("Setting up resources") # TODO: Only initialize if not hotword activated # Fetch empty resources from TOML self._initialize_resources(self._dialogue_name) @@ -178,9 +177,7 @@ def setup_resources(self) -> None: if self._saved_state and _EXTRAS_KEY in self._saved_state: self._extras = self._saved_state.get(_EXTRAS_KEY) or self._extras # Create resource dependency relationship graph - print("Initializing resource graph") self._initialize_resource_graph() - print("Finished setting up resources") def _initialize_resource_graph(self) -> None: """ @@ -189,19 +186,14 @@ def _initialize_resource_graph(self) -> None: to what each resource requires. """ for resource in self._resources.values(): - print("Initializing resource graph for", resource.name) if resource.order_index == 0 and self._initial_resource is None: self._initial_resource = resource self._resource_graph[resource] = {"children": [], "parents": []} - print("Children/parents set up, starting to fill:") for resource in self._resources.values(): - print("In outer for loop") for req in resource.requires: - print("Appending parents and children for resource", req) self._resource_graph[self._resources[req]]["parents"].append(resource) self._resource_graph[resource]["children"].append(self._resources[req]) - print("Done appending parents and children for resource", req) - print("Resource graph: ", self._resource_graph) + # print("Resource graph: ", self._resource_graph) def _initialize_resources(self, filename: str) -> None: """ @@ -209,10 +201,8 @@ def _initialize_resources(self, filename: str) -> None: fills self._resources with empty Resource instances. """ if self._saved_state: - print("IN IFFFFFF with resources: ", self._saved_state[_RESOURCES_KEY]) self._resources = {} for rname, resource in self._saved_state[_RESOURCES_KEY].items(): - print("Adding resource", rname) self._resources[rname] = resource self._expiration_time = self._saved_state.get( "expiration_time", _DEFAULT_EXPIRATION_TIME @@ -294,13 +284,11 @@ def add_dynamic_resource(self, resource_name: str, parent_name: str) -> None: resource: Resource = dynamic_resources[indexed_resource_name] # Appending resource to required list of parent resource parent_resource.requires.append(indexed_resource_name) - print("Parent resource requirements: ", parent_resource.requires) def _add_child_resource(resource: Resource) -> None: """ Recursively adds a child resource to the resources list """ - print("Start of add child resource") self._resources[resource.name] = resource for req in resource.requires: _add_child_resource(dynamic_resources[req]) @@ -339,9 +327,7 @@ def _recursive_deep_copy(resource: Resource) -> None: _recursive_deep_copy(child) for parent in self._resource_graph[original]["parents"]: - print("!!!Adding to parent !!!: ", parent.name, original.name) parent.requires.append(f"Pizza_{suffix}") - print("Parent requirements: ", parent.requires) _recursive_deep_copy(original) # Initialize the resource graph again with the update resources diff --git a/queries/pizza.py b/queries/pizza.py index 53e0910a..92ee4875 100644 --- a/queries/pizza.py +++ b/queries/pizza.py @@ -21,7 +21,7 @@ This query module handles dialogue related to ordering pizza. """ from lzma import is_check_supported -from typing import Dict, Optional, Set, cast +from typing import Any, Dict, List, Optional, Set, cast import logging import random @@ -136,13 +136,13 @@ def _generate_pizza_answer( print("Generating pizza answer") print("Generate pizza resource name: ", resource.name) type_resource: OrResource = cast(OrResource, dsm.get_children(resource)[0]) - print("Type state: {}".format(type_resource.state)) + print("Type state: {}".format(type_resource.data)) size_resource: StringResource = cast(StringResource, dsm.get_children(resource)[1]) - print("Size state: {}".format(size_resource.state)) + print("Size state: {}".format(size_resource.data)) crust_resource: StringResource = cast(StringResource, dsm.get_children(resource)[2]) - print("Crust state: {}".format(crust_resource.state)) - index = dsm.current_resource.name.split("_")[-1] - number: int = cast(int, index) + print("Crust state: {}".format(crust_resource.data)) + index: str = resource.name.split("_")[-1] + number: int = int(index) # Pizza text formatting pizza_text: str = f"\n" if any( @@ -236,8 +236,94 @@ def QPizzaHotWord(node: Node, params: QueryStateDict, result: Result) -> None: def QPizzaNumberAndSpecificationWrapper( node: Node, params: QueryStateDict, result: Result ) -> None: + print("In number and specification wrapper") dsm: DialogueStateManager = Query.get_dsm(result) - dsm.extras.pop("adding_pizzas", None) + resource: WrapperResource = cast(WrapperResource, dsm.current_resource) + pizzas: List[Dict[str, Any]] = result.get("pizzas", []) + print("Current resource: ", resource.name) + print("Resource.name == PizzaOrder", resource.name == "PizzaOrder") + print( + "(dsm.extras.pop(adding_pizzas, False): ", + (dsm.extras.get("adding_pizzas", False)), + ) + print("Pizzas: ", pizzas) + for pizza in pizzas: + print("Pizza: ", pizza) + if resource.name == "PizzaOrder": + # Create a new pizza + print("Adding new pizza") + dsm.add_dynamic_resource("Pizza", "PizzaOrder") + # dsm.extras["adding_pizzas"] = True + print("Done adding new pizza", dsm.get_children(resource)[-1]) + dsm.extras["total_pizzas"] = dsm.extras.get("total_pizzas", 0) + 1 + print("Done adding to total pizzas") + pizza_resource: WrapperResource = cast( + WrapperResource, dsm.get_children(resource)[-1] + ) + else: + resource = cast(WrapperResource, dsm.get_resource("PizzaOrder")) + pizza_resource = cast(WrapperResource, dsm.current_resource) + # Add to the pizza + print(">>> Pizza resource: , ", pizza_resource.name) + type_resource: OrResource = cast( + OrResource, dsm.get_children(pizza_resource)[0] + ) + toppings: Optional[Dict[str, int]] = pizza.get("toppings", None) + if toppings: + toppings_resource = cast(DictResource, dsm.get_children(type_resource)[0]) + for (topping, amount) in toppings.items(): + print("Topping in for loop: ", topping) + toppings_resource.data[topping] = amount + dsm.skip_other_resources(type_resource, toppings_resource) + dsm.set_resource_state(toppings_resource.name, ResourceState.CONFIRMED) + dsm.set_resource_state(type_resource.name, ResourceState.CONFIRMED) + + menu: Optional[str] = pizza.get("menu", None) + if menu: + menu_resource: StringResource = cast( + StringResource, dsm.get_children(type_resource)[1] + ) + menu_resource.data = menu + dsm.skip_other_resources(type_resource, menu_resource) + dsm.set_resource_state(menu_resource.name, ResourceState.CONFIRMED) + dsm.set_resource_state(type_resource.name, ResourceState.CONFIRMED) + size: Optional[str] = pizza.get("size", None) + print("Size: ", size) + if size: + size_resource: StringResource = cast( + StringResource, dsm.get_children(pizza_resource)[1] + ) + print("Size resource name: ", size_resource.name) + size_resource.data = size + dsm.set_resource_state(size_resource.name, ResourceState.CONFIRMED) + print("Size state: ", size_resource.state) + + crust: Optional[str] = pizza.get("crust", None) + print("Crust: ", crust) + if crust: + crust_resource: StringResource = cast( + StringResource, dsm.get_children(pizza_resource)[2] + ) + crust_resource.data = crust + dsm.set_resource_state(crust_resource.name, ResourceState.CONFIRMED) + dsm.update_wrapper_state(pizza_resource) + if pizza_resource.state == ResourceState.FULFILLED: + dsm.set_resource_state(pizza_resource.name, ResourceState.CONFIRMED) + dsm.extras["confirmed_pizzas"] = dsm.extras.get("confirmed_pizzas", 0) + 1 + dsm.extras["added_pizzas"] = dsm.extras.get("added_pizzas", 0) + 1 + result["new_pizza"] = pizza_resource + + number: int = pizza.get("count", 1) - 1 + print("Getting new pizza") + for _ in range(number): + dsm.duplicate_dynamic_resource(pizza_resource) + print("Duplicating resource: ", pizza_resource.name) + dsm.extras["total_pizzas"] = dsm.extras.get("total_pizzas", 0) + 1 + if pizza_resource.is_confirmed: + dsm.extras["confirmed_pizzas"] = ( + dsm.extras.get("confirmed_pizzas", 0) + 1 + ) + dsm.extras["added_pizzas"] = dsm.extras.get("added_pizzas", 0) + 1 def QPizzaNumberAndSpecification( @@ -248,19 +334,25 @@ def QPizzaNumberAndSpecification( according to the specification that was added in QPizzaSpecification. """ - dsm: DialogueStateManager = Query.get_dsm(result) - print("Getting new pizza") - resource: Resource = result.get("new_pizza", dsm.get_resource("Pizza_1")) - print("!!!! New pizza: ", resource.name) - number: int = result.get("number", 1) - 1 - print("!!!! Number: ", number) - for _ in range(number): - dsm.duplicate_dynamic_resource(resource) - print("Duplicating resource: ", resource.name) - dsm.extras["total_pizzas"] = dsm.extras.get("total_pizzas", 0) + 1 - if resource.is_confirmed: - dsm.extras["confirmed_pizzas"] = dsm.extras.get("confirmed_pizzas", 0) + 1 - dsm.extras["added_pizzas"] = dsm.extras.get("added_pizzas", 0) + 1 + print("QPizzaNumberAndSpecification") + toppings: Optional[Dict[str, int]] = result.dict.pop("toppings", None) + print("Toppings: ", toppings) + menu: Optional[str] = result.dict.pop("menu", None) + print("Menu: ", menu) + size: Optional[str] = result.dict.pop("pizza_size", None) + print("Size: ", size) + crust: Optional[str] = result.dict.pop("crust", None) + print("Crust: ", crust) + number: int = result.get("number", 1) + pizza: Dict[str, Any] = { + "count": number, + "toppings": toppings, + "menu": menu, + "size": size, + "crust": crust, + } + print("Pizza in QPizzaNumberAndSpecification: ", pizza) + result.pizzas = [pizza] def QPizzaSpecification(node: Node, params: QueryStateDict, result: Result) -> None: @@ -269,74 +361,6 @@ def QPizzaSpecification(node: Node, params: QueryStateDict, result: Result) -> N otherwise adds ingredients to the current pizza. """ print("In QPizzaSpecification") - if ( - len(list(node.descendants(lambda x: "QPizzaSpecification" in x.string_self()))) - != 0 - ): - return - dsm: DialogueStateManager = Query.get_dsm(result) - resource: WrapperResource = cast(WrapperResource, dsm.current_resource) - print("Current resource: ", resource.name) - print("Resource.name == PizzaOrder", resource.name == "PizzaOrder") - print( - "(dsm.extras.pop(adding_pizzas, False): ", - (dsm.extras.get("adding_pizzas", False)), - ) - if resource.name == "PizzaOrder" or (dsm.extras.pop("adding_pizzas", False)): - # Create a new pizza - print("Adding new pizza") - dsm.add_dynamic_resource("Pizza", "PizzaOrder") - dsm.extras["adding_pizzas"] = True - print("Done adding new pizza") - dsm.extras["total_pizzas"] = dsm.extras.get("total_pizzas", 0) + 1 - print("Done adding to total pizzas") - # Add to the pizza - pizza_resource: WrapperResource = cast(WrapperResource, dsm.current_resource) - type_resource: OrResource = cast(OrResource, dsm.get_children(pizza_resource)[0]) - toppings: Optional[Dict[str, int]] = result.dict.pop("toppings", None) - if toppings: - toppings_resource = cast(DictResource, dsm.get_children(type_resource)[0]) - for (topping, amount) in toppings.items(): - print("Topping in for loop: ", topping) - toppings_resource.data[topping] = amount - dsm.skip_other_resources(type_resource, toppings_resource) - dsm.set_resource_state(toppings_resource.name, ResourceState.CONFIRMED) - dsm.set_resource_state(type_resource.name, ResourceState.CONFIRMED) - - menu: Optional[str] = result.dict.pop("menu", None) - if menu: - menu_resource: StringResource = cast( - StringResource, dsm.get_children(type_resource)[1] - ) - menu_resource.data = menu - dsm.skip_other_resources(type_resource, menu_resource) - dsm.set_resource_state(menu_resource.name, ResourceState.CONFIRMED) - dsm.set_resource_state(type_resource.name, ResourceState.CONFIRMED) - size: Optional[str] = result.dict.pop("pizza_size", None) - print("Size: ", size) - if size: - size_resource: StringResource = cast( - StringResource, dsm.get_children(dsm.current_resource)[1] - ) - print("Size resource name: ", size_resource.name) - size_resource.data = size - dsm.set_resource_state(size_resource.name, ResourceState.CONFIRMED) - print("Size state: ", size_resource.state) - - crust: Optional[str] = result.dict.pop("crust", None) - print("Crust: ", crust) - if crust: - crust_resource: StringResource = cast( - StringResource, dsm.get_children(dsm.current_resource)[2] - ) - crust_resource.data = crust - dsm.set_resource_state(crust_resource.name, ResourceState.CONFIRMED) - dsm.update_wrapper_state(pizza_resource) - if pizza_resource.state == ResourceState.FULFILLED: - dsm.set_resource_state(pizza_resource.name, ResourceState.CONFIRMED) - dsm.extras["confirmed_pizzas"] = dsm.extras.get("confirmed_pizzas", 0) + 1 - dsm.extras["added_pizzas"] = dsm.extras.get("added_pizzas", 0) + 1 - result["new_pizza"] = pizza_resource def QPizzaToppingsWord(node: Node, params: QueryStateDict, result: Result) -> None: From 67ad184120b154d9bb86a4b33cd3ecbd5ab326a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Mon, 25 Jul 2022 16:59:27 +0000 Subject: [PATCH 291/371] =?UTF-8?q?Handling=20multiple=20pizzas=20with=20e?= =?UTF-8?q?ina=20p=C3=ADtsu=20me=C3=B0=20...=20now=20works.=20Still=20some?= =?UTF-8?q?=20problems=20with=20it=20splitting=20it=20into=20different=20p?= =?UTF-8?q?izzas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- queries/grammars/pizza.grammar | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/queries/grammars/pizza.grammar b/queries/grammars/pizza.grammar index a6d4fe3b..cd06cc77 100644 --- a/queries/grammars/pizza.grammar +++ b/queries/grammars/pizza.grammar @@ -83,7 +83,7 @@ QPizzaNumberAndSpecificationWrapper/fall → # This clarity makes the handling in the pizza module easier. QPizzaNumberAndSpecification/fall → QPizzaNum/fall? QPizzaSpecification/fall - | QPizzaNum/fall QPizzaWord/fall + #| QPizzaNum/fall QPizzaWord/fall QPizzaSpecification/fall → QPizzaPrimeSpecification/fall # Specifies a single thing. @@ -103,7 +103,7 @@ QPizzaMenuSpecification/fall → QPizzaToppingsSpecification/fall → QPizzaToppingsList/fall QPizzaAPitsunaPhrase? # e.g. "Skinku, ólífur og pepperóní á pítsuna." - | QPizzaWord/fall? QPizzaMedToppingsPhrase # e.g. "Með ólífum og ananas." + | QPizzaWord/fall QPizzaMedToppingsPhrase # e.g. "Með ólífum og ananas." QPizzaSizeSpecification/fall → QPizzaSizePhrase/fall # e.g. "Tólf tommu pítsa." @@ -134,10 +134,11 @@ QPizzaOgNumberAndSpecification/fall → QPizzaOgMedToppingsPhrase/fall → "og"? 'með:fs'? QPizzaToppingsWord/fall +$score(+100) QPizzaOgMedToppingsPhrase/fall # It is common to say "miðstærð af pítsu", which is handled separately here. QPizzaSizePhrase/fall → - QPizzaSize/fall QPizzaWord/fall? + QPizzaSize/fall QPizzaWord/fall | QPizzaMediumWord/fall QPizzaAfPitsuPhrase? # This duplicate is a result of difficulties with the composite logic. From 875bd8e916227924ed8c58a51c0d4ae8c4d10a1e Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Tue, 26 Jul 2022 13:25:12 +0000 Subject: [PATCH 292/371] Spotify new plaintext module --- queries/iot_spotify.py | 207 ++++++++++++------------------------- queries/iot_spotify_old.py | 204 ++++++++++++++++++++++++++++++++++++ 2 files changed, 269 insertions(+), 142 deletions(-) create mode 100644 queries/iot_spotify_old.py diff --git a/queries/iot_spotify.py b/queries/iot_spotify.py index eae16869..c765f48a 100644 --- a/queries/iot_spotify.py +++ b/queries/iot_spotify.py @@ -2,7 +2,7 @@ Greynir: Natural language processing for Icelandic - Randomness query response module + Example of a plain text query processor module. Copyright (C) 2022 Miðeind ehf. @@ -18,120 +18,43 @@ You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/. - This query module handles queries related to the generation - of random numbers, e.g. "Kastaðu tengingi", "Nefndu tölu milli 5 og 10", etc. - -""" + This module is an example of a plug-in query response module + for the Greynir query subsystem. It handles plain text queries, i.e. + ones that do not require parsing the query text. For this purpose + it only needs to implement the handle_plain_text() function, as + shown below. -# TODO: add "láttu", "hafðu", "litaðu", "kveiktu" functionality. -# TODO: make the objects of sentences more modular, so that the same structure doesn't need to be written for each action -# TODO: ditto the previous comment. make the initial non-terminals general and go into specifics at the terminal level instead. -# TODO: substituion klósett, baðherbergi hugmyndÆ senda lista i javascript og profa i röð -# TODO: Embla stores old javascript code cached which has caused errors -# TODO: Cut down javascript sent to Embla -# TODO: Two specified groups or lights. -# TODO: No specified location -# TODO: Fix scene issues -from os import access -from typing import Dict, Mapping, Optional, cast -from typing_extensions import TypedDict +""" -import logging -import random -import json -import flask +from query import Query from datetime import datetime, timedelta +import random import re - -from reynir.lemmatize import simple_lemmatize - -from query import Query, QueryStateDict, AnswerTuple -from queries import gen_answer, read_jsfile, read_grammar_file from queries.spotify import SpotifyClient -from tree import Result, Node, TerminalNode -from util import read_api_key - - -_IoT_QTYPE = "IoTSpotify" - -# TOPIC_LEMMAS = [ -# "tónlist", -# "spila", -# ] +from queries import gen_answer def help_text(lemma: str) -> str: """Help text to return when query.py is unable to parse a query but one of the above lemmas is found in it""" return "Ég skil þig ef þú segir til dæmis: {0}.".format( - random.choice( - ("Hækkaðu í tónlistinni", "Kveiktu á tónlist", "Láttu vera tónlist") - ) + random.choice(("Spilaðu Þorparinn með Pálma Gunnarssyni")) ) -# This module wants to handle parse trees for queries -HANDLE_TREE = True - -# The grammar nonterminals this module wants to handle -QUERY_NONTERMINALS = {"QIoTSpotify"} - # The context-free grammar for the queries recognized by this plug-in module _SPOTIFY_REGEXES = [ - r"^(spilaðu )([\w|\s]+)(með )([\w|\s]+)$", + r"^spilaðu ([\w|\s]+) með ([\w|\s]+)$", + # r"^spilaðu ([\w|\s]+) með ([\w|\s]+) á spotify?$", + r"^spilaðu ([\w|\s]+) á spotify$", + r"^spilaðu ([\w|\s]+) á spotify", ] -GRAMMAR = f""" - -/þgf = þgf -/ef = ef - -Query → - QIoTSpotify '?'? - -QIoTSpotify → - QIoTSpotifyPlaySongByArtist - -QIoTSpotifyPlaySongByArtist → - QIoTSpotifyPlayVerb QIoTSpotifySongName QIoTSpotifyWithPreposition QIoTSpotifyArtistName - -QIoTSpotifyPlayVerb → - 'spila:so'_bh - -QIoTSpotifySongName → - Nl - -QIoTSpotifyWithPreposition → - 'með' - | 'eftir' -QIoTSpotifyArtistName → - Nl - | sérnafn -""" - - -def QIoTSpotify(node: Node, params: QueryStateDict, result: Result) -> None: - result.qtype = _IoT_QTYPE - - -def QIoTSpotifyPlayVerb(node: Node, params: QueryStateDict, result: Result) -> None: - "spotify play function" - result.action = "play" - - -def QIoTSpotifySongName(node: Node, params: QueryStateDict, result: Result) -> None: - result.song_name = result._text - - -def QIoTSpotifyArtistName(node: Node, params: QueryStateDict, result: Result) -> None: - result.artist_name = result._indefinite - - -def get_song_and_artist(q: Query) -> tuple: +def handle_plain_text(q) -> bool: """Handle a plain text query requesting Spotify to play a specific song by a specific artist.""" # ql = q.query_lower.strip().rstrip("?") print("handle_plain_text") @@ -147,58 +70,58 @@ def get_song_and_artist(q: Query) -> tuple: print(m) if m: (print("MATCH!")) - song_name = m.group(2) - artist_name = m.group(4).strip() - return (song_name, artist_name) + song_name = m.group(1) + artist_name = m.group(2).strip() + print("SONG NAME :", song_name) + print("ARTIST NAME :", artist_name) + device_data = q.client_data("spotify") + if device_data is not None: + client_id = str(q.client_id) + spotify_client = SpotifyClient( + device_data, + client_id, + song_name=song_name, + artist_name=artist_name, + ) + song_url = spotify_client.get_song_by_artist() + response = spotify_client.play_song_on_device() + # response = None + print("RESPONSE FROM SPOTIFY:", response) + answer = "Ég spilaði lagið" + if response is None: + q.set_url(song_url) + q.set_answer(*gen_answer(answer)) + + return True + + else: + answer = "Það vantar að tengja Spotify aðgang." + q.set_answer(*gen_answer(answer)) + return True else: return False + # Caching (optional) + q.set_expires(datetime.utcnow() + timedelta(hours=24)) -def sentence(state: QueryStateDict, result: Result) -> None: - """Called when sentence processing is complete""" - print("sentence") - q: Query = state["query"] - if result.action == "play": - print("SPOTIFY PLAY") - song_artist_tuple = get_song_and_artist(q) - print("exited plain text") - song_name = song_artist_tuple[0] - artist_name = song_artist_tuple[1] - print("SONG NAME :", song_name) - print("ARTIST NAME :", artist_name) - - print("RESTULT SONG NAME:", result.song_name) - print("RESULT ARTIST NAME:", result.artist_name) - device_data = q.client_data("spotify") - if device_data is not None: - client_id = str(q.client_id) - spotify_client = SpotifyClient( - device_data, - client_id, - song_name=result.song_name, - artist_name=result.artist_name, - ) - song_url = spotify_client.get_song_by_artist() - response = spotify_client.play_song_on_device() - # response = None - print("RESPONSE FROM SPOTIFY:", response) - if response is None: - q.set_url(song_url) - - answer = "Ég spilaði lagið" - else: - answer = "Það vantar að tengja Spotify aðgang." - q.set_answer(*gen_answer(answer)) - return - # q.set_url( - # "https://spotify.app.link/?product=open&%24full_url=https%3A%2F%2Fopen.spotify.com%2Ftrack%2F2BSyX4weGuITcvl5r2lLCC%3Fgo%3D1%26sp_cid%3D2a74d03dedb9fa4450d122ddebebcf9b%26fallback%3Dgetapp&feature=organic&_p=c31529c0980b7af1e11b90f9" - # ) - voice_answer, response = answer, dict(answer=answer) - q.set_answer(response, answer, voice_answer) + # Context (optional) + # q.set_context(dict(subject="Prufuviðfangsefni")) + + # Source (optional) + # q.set_source("Prufumódúll") + + # Beautify query for end user display (optional) + # q.set_beautified_query(ql.upper()) + + # Javascript command to execute client-side (optional) + # q.set_command("2 + 2") + + # URL to be opened by client (optional) + # q.set_url("https://miðeind.is") + + return True + + return False - else: - print("ELSE") - q.set_error("E_QUERY_NOT_UNDERSTOOD") - return - # # TODO: Need to add check for if there are no registered devices to an account, probably when initilazing the querydata +# def get_song_and_artist(q: Query) -> tuple: diff --git a/queries/iot_spotify_old.py b/queries/iot_spotify_old.py new file mode 100644 index 00000000..bd9cff3d --- /dev/null +++ b/queries/iot_spotify_old.py @@ -0,0 +1,204 @@ +# """ + +# Greynir: Natural language processing for Icelandic + +# Randomness query response module + +# Copyright (C) 2022 Miðeind ehf. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. + +# This query module handles queries related to the generation +# of random numbers, e.g. "Kastaðu tengingi", "Nefndu tölu milli 5 og 10", etc. + +# """ + + +# # TODO: add "láttu", "hafðu", "litaðu", "kveiktu" functionality. +# # TODO: make the objects of sentences more modular, so that the same structure doesn't need to be written for each action +# # TODO: ditto the previous comment. make the initial non-terminals general and go into specifics at the terminal level instead. +# # TODO: substituion klósett, baðherbergi hugmyndÆ senda lista i javascript og profa i röð +# # TODO: Embla stores old javascript code cached which has caused errors +# # TODO: Cut down javascript sent to Embla +# # TODO: Two specified groups or lights. +# # TODO: No specified location +# # TODO: Fix scene issues + +# from os import access +# from typing import Dict, Mapping, Optional, cast +# from typing_extensions import TypedDict + +# import logging +# import random +# import json +# import flask +# from datetime import datetime, timedelta +# import re + +# from reynir.lemmatize import simple_lemmatize + +# from query import Query, QueryStateDict, AnswerTuple +# from queries import gen_answer, read_jsfile, read_grammar_file +# from queries.spotify import SpotifyClient +# from tree import Result, Node, TerminalNode +# from util import read_api_key + + +# _IoT_QTYPE = "IoTSpotify" + +# # TOPIC_LEMMAS = [ +# # "tónlist", +# # "spila", +# # ] + + +# def help_text(lemma: str) -> str: +# """Help text to return when query.py is unable to parse a query but +# one of the above lemmas is found in it""" +# return "Ég skil þig ef þú segir til dæmis: {0}.".format( +# random.choice( +# ("Hækkaðu í tónlistinni", "Kveiktu á tónlist", "Láttu vera tónlist") +# ) +# ) + + +# # This module wants to handle parse trees for queries +# HANDLE_TREE = True + +# # The grammar nonterminals this module wants to handle +# QUERY_NONTERMINALS = {"QIoTSpotify"} + +# # The context-free grammar for the queries recognized by this plug-in module + +# _SPOTIFY_REGEXES = [ +# r"^(spilaðu )([\w|\s]+)(með )([\w|\s]+)$", +# ] + +# GRAMMAR = f""" + +# /þgf = þgf +# /ef = ef + +# Query → +# QIoTSpotify '?'? + +# QIoTSpotify → +# QIoTSpotifyPlaySongByArtist + +# QIoTSpotifyPlaySongByArtist → +# QIoTSpotifyPlayVerb QIoTSpotifySongName QIoTSpotifyWithPreposition QIoTSpotifyArtistName + +# QIoTSpotifyPlayVerb → +# 'spila:so'_bh + +# QIoTSpotifySongName → +# Nl + +# QIoTSpotifyWithPreposition → +# 'með' +# | 'eftir' + +# QIoTSpotifyArtistName → +# Nl +# | sérnafn +# """ + + +# def QIoTSpotify(node: Node, params: QueryStateDict, result: Result) -> None: +# result.qtype = _IoT_QTYPE + + +# def QIoTSpotifyPlayVerb(node: Node, params: QueryStateDict, result: Result) -> None: +# "spotify play function" +# result.action = "play" + + +# def QIoTSpotifySongName(node: Node, params: QueryStateDict, result: Result) -> None: +# result.song_name = result._text + + +# def QIoTSpotifyArtistName(node: Node, params: QueryStateDict, result: Result) -> None: +# result.artist_name = result._indefinite + + +# def get_song_and_artist(q: Query) -> tuple: +# """Handle a plain text query requesting Spotify to play a specific song by a specific artist.""" +# # ql = q.query_lower.strip().rstrip("?") +# print("handle_plain_text") +# ql = q.query_lower.strip().rstrip("?") +# print("QL:", ql) + +# pfx = None + +# for rx in _SPOTIFY_REGEXES: +# print(rx) +# print("") +# m = re.search(rx, ql) +# print(m) +# if m: +# (print("MATCH!")) +# song_name = m.group(2) +# artist_name = m.group(4).strip() +# return (song_name, artist_name) +# else: +# return False + + +# def sentence(state: QueryStateDict, result: Result) -> None: +# """Called when sentence processing is complete""" +# print("sentence") +# q: Query = state["query"] +# if result.action == "play": +# print("SPOTIFY PLAY") +# song_artist_tuple = get_song_and_artist(q) +# print("exited plain text") +# song_name = song_artist_tuple[0] +# artist_name = song_artist_tuple[1] +# print("SONG NAME :", song_name) +# print("ARTIST NAME :", artist_name) + +# print("RESTULT SONG NAME:", result.song_name) +# print("RESULT ARTIST NAME:", result.artist_name) +# device_data = q.client_data("spotify") +# if device_data is not None: +# client_id = str(q.client_id) +# spotify_client = SpotifyClient( +# device_data, +# client_id, +# song_name=result.song_name, +# artist_name=result.artist_name, +# ) +# song_url = spotify_client.get_song_by_artist() +# response = spotify_client.play_song_on_device() +# # response = None +# print("RESPONSE FROM SPOTIFY:", response) +# if response is None: +# q.set_url(song_url) + +# answer = "Ég spilaði lagið" +# else: +# answer = "Það vantar að tengja Spotify aðgang." +# q.set_answer(*gen_answer(answer)) +# return +# # q.set_url( +# # "https://spotify.app.link/?product=open&%24full_url=https%3A%2F%2Fopen.spotify.com%2Ftrack%2F2BSyX4weGuITcvl5r2lLCC%3Fgo%3D1%26sp_cid%3D2a74d03dedb9fa4450d122ddebebcf9b%26fallback%3Dgetapp&feature=organic&_p=c31529c0980b7af1e11b90f9" +# # ) +# voice_answer, response = answer, dict(answer=answer) +# q.set_answer(response, answer, voice_answer) + +# else: +# print("ELSE") +# q.set_error("E_QUERY_NOT_UNDERSTOOD") +# return + +# # # TODO: Need to add check for if there are no registered devices to an account, probably when initilazing the querydata From a72d8e4dd1dd2b719619c0590473135163f26cba Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Wed, 27 Jul 2022 13:18:24 +0000 Subject: [PATCH 293/371] Spotify devices bug fix --- queries/__init__.py | 8 +++++--- queries/iot_spotify.py | 3 +-- queries/spotify.py | 10 ++++++---- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/queries/__init__.py b/queries/__init__.py index c2dd018a..3e7d1dc2 100755 --- a/queries/__init__.py +++ b/queries/__init__.py @@ -485,7 +485,7 @@ def put_to_json_api( ) -> Union[None, List[Any], Dict[str, Any]]: """Send a POST request to the URL, expecting a JSON response which is parsed and returned as a Python data structure.""" - + print("put to json api") # Send request try: r = requests.put(url, data=json_data, headers=headers) @@ -500,8 +500,10 @@ def put_to_json_api( # Parse json API response try: - res = json.loads(r.text) - return res + if r.text: + res = json.loads(r.text) + return res + return {} except Exception as e: logging.warning("Error parsing JSON API response: {0}".format(e)) return None diff --git a/queries/iot_spotify.py b/queries/iot_spotify.py index c765f48a..d6e0d4fa 100644 --- a/queries/iot_spotify.py +++ b/queries/iot_spotify.py @@ -90,8 +90,7 @@ def handle_plain_text(q) -> bool: answer = "Ég spilaði lagið" if response is None: q.set_url(song_url) - q.set_answer(*gen_answer(answer)) - + q.set_answer({"answer": answer}, answer, "") return True else: diff --git a/queries/spotify.py b/queries/spotify.py index 142dbda6..cf0dd1b0 100644 --- a/queries/spotify.py +++ b/queries/spotify.py @@ -36,7 +36,6 @@ def __init__( except (KeyError, TypeError): self._create_token() self._check_token_expiration() - self._devices = self._get_devices() self._store_credentials() def _create_token(self): @@ -100,7 +99,7 @@ def _refresh_expired_token(self): headers = { "Content-Type": "application/x-www-form-urlencoded", "Authorization": f"Basic {self._encoded_credentials}", - # 'Cookie': '__Host-device_id=AQBxVApczxoXIW_roLoJ5nY1ND2wR8StM3lgCAP1SzmApFbSWeNGRpxDLjOtLaGOHTM-CpdxKbWCvXcc77StrhE1N4L5q21o2l0; __Secure-TPASESSION=AQB0Nywu3HtM0ccHT76ksjXMzzeDpIEIbYzytEhvu05ELAEfMRTsc0qyaxUphsBxE8qCN2Vsruz6Mo897xYLznaxfa0ZGdh5Jpw=; sp_sso_csrf_token=013acda7191871a43462f6a67f78e88cb74e9b5bc031363537353339303539343131; sp_tr=false' + # "Cookie": "__Host-device_id=AQBxVApczxoXIW_roLoJ5nY1ND2wR8StM3lgCAP1SzmApFbSWeNGRpxDLjOtLaGOHTM-CpdxKbWCvXcc77StrhE1N4L5q21o2l0; __Secure-TPASESSION=AQB0Nywu3HtM0ccHT76ksjXMzzeDpIEIbYzytEhvu05ELAEfMRTsc0qyaxUphsBxE8qCN2Vsruz6Mo897xYLznaxfa0ZGdh5Jpw=; sp_sso_csrf_token=013acda7191871a43462f6a67f78e88cb74e9b5bc031363537353339303539343131; sp_tr=false", } response = post_to_json_api(url, payload, headers) @@ -162,6 +161,7 @@ def get_song_by_artist(self): } response = query_json_api(url, headers) + # print(response) self._song_url = response["tracks"]["items"][0]["external_urls"]["spotify"] self._song_uri = response["tracks"]["items"][0]["uri"] print("SONG URI: ", self._song_uri) @@ -171,7 +171,8 @@ def get_song_by_artist(self): def play_song_on_device(self): print("play song from device") print("accesss token play song; ", self._access_token) - self._device_data = self._get_devices() + self._devices = self._get_devices() + print("exited get devices") url = "https://api.spotify.com/v1/me/player/play" payload = json.dumps( @@ -202,7 +203,8 @@ def _get_devices(self): } response = query_json_api(url, headers) - return response["devices"] + print("devices: ", response) + return response.get("devices") def filter_devices(self): print("filter devices") From a5e3606284a107ab4d6ddec9e53e3f49ce4a1f65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Fri, 29 Jul 2022 16:52:22 +0000 Subject: [PATCH 294/371] Added documentation to pizza.py --- queries/pizza.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/queries/pizza.py b/queries/pizza.py index 92ee4875..4b70384b 100644 --- a/queries/pizza.py +++ b/queries/pizza.py @@ -236,6 +236,11 @@ def QPizzaHotWord(node: Node, params: QueryStateDict, result: Result) -> None: def QPizzaNumberAndSpecificationWrapper( node: Node, params: QueryStateDict, result: Result ) -> None: + """ + Dynamically adds a number of pizzas if there is no + current pizza, otherwise adds ingredients to the current pizza. + The specification of the pizzas is gotten from the result. + """ print("In number and specification wrapper") dsm: DialogueStateManager = Query.get_dsm(result) resource: WrapperResource = cast(WrapperResource, dsm.current_resource) @@ -330,9 +335,7 @@ def QPizzaNumberAndSpecification( node: Node, params: QueryStateDict, result: Result ) -> None: """ - Dynamically adds a number of pizzas to the query - according to the specification that was added in - QPizzaSpecification. + Adds pizza information to the result. """ print("QPizzaNumberAndSpecification") toppings: Optional[Dict[str, int]] = result.dict.pop("toppings", None) @@ -356,10 +359,6 @@ def QPizzaNumberAndSpecification( def QPizzaSpecification(node: Node, params: QueryStateDict, result: Result) -> None: - """ - Creates a pizza if there is no current pizza, - otherwise adds ingredients to the current pizza. - """ print("In QPizzaSpecification") From 451fb1bf7559d97b2c599e90b23c395ce93cfd6c Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Fri, 5 Aug 2022 14:40:46 +0000 Subject: [PATCH 295/371] Sonos session fix --- queries/sonos.py | 17 +++++++++++++++-- queries/spotify.py | 16 ++++++++-------- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/queries/sonos.py b/queries/sonos.py index 42c015e7..d4c96b04 100644 --- a/queries/sonos.py +++ b/queries/sonos.py @@ -361,7 +361,7 @@ def _create_playerdict_for_db(self, players: list): players_dict[players[i]["name"]] = players[i]["id"] return players_dict - def _create_or_join_session(self): + def _create_or_join_session(self, recursion=None): print("_create_or_join_session") # group_id = self._get_group_id() url = f"https://api.ws.sonos.com/control/api/v1/groups/{self._group_id}/playbackSession/joinOrCreate" @@ -374,7 +374,19 @@ def _create_or_join_session(self): response = post_to_json_api(url, payload, headers) print(response) - session_id = response["sessionId"] + if response is None: + self.toggle_pause() + if recursion is None: + response = self._create_or_join_session(recursion=True) + else: + return None + print("response was none , so we created a new session") + session_id = response + + else: + session_id = response["sessionId"] + print("response after loop:", response) + print("session_id :", session_id) return session_id """ @@ -384,6 +396,7 @@ def _create_or_join_session(self): def play_radio_stream(self, radio_url): #: Optional[str] = self._device_data.get] print("play radio stream") session_id = self._create_or_join_session() + print("exited create or join session") if radio_url is None: try: radio_url = self._device_data["sonos"]["data"]["last_radio_url"] diff --git a/queries/spotify.py b/queries/spotify.py index cf0dd1b0..db71dc79 100644 --- a/queries/spotify.py +++ b/queries/spotify.py @@ -171,7 +171,7 @@ def get_song_by_artist(self): def play_song_on_device(self): print("play song from device") print("accesss token play song; ", self._access_token) - self._devices = self._get_devices() + # self._devices = self._get_devices() print("exited get devices") url = "https://api.spotify.com/v1/me/player/play" @@ -206,10 +206,10 @@ def _get_devices(self): print("devices: ", response) return response.get("devices") - def filter_devices(self): - print("filter devices") - filtered_devices = [] - for device in self._devices: - if device["type"] == "Smartphone": - filtered_devices.append(device) - return filtered_devices + # def filter_devices(self): + # print("filter devices") + # filtered_devices = [] + # for device in self._devices: + # if device["type"] == "Smartphone": + # filtered_devices.append(device) + # return filtered_devices From 0fc2f0f4bf5c1ec7701c9000c613f72aa3d94da2 Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Fri, 5 Aug 2022 15:31:07 +0000 Subject: [PATCH 296/371] New spotify search fix --- queries/spotify.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/queries/spotify.py b/queries/spotify.py index db71dc79..e2bbfde2 100644 --- a/queries/spotify.py +++ b/queries/spotify.py @@ -151,8 +151,12 @@ def get_song_by_artist(self): print("accesss token get song; ", self._access_token) song_name = self._song_name.replace(" ", "%20") artist_name = self._artist_name.replace(" ", "%20") - - url = f"https://api.spotify.com/v1/search?q=track:{song_name}+artist:{artist_name}&type=track" + print("song name: ", song_name) + print("artist name: ", artist_name) + url = ( + f"https://api.spotify.com/v1/search?type=track&q={song_name}+{artist_name}" + ) + print("url: ", url) payload = "" headers = { @@ -161,9 +165,13 @@ def get_song_by_artist(self): } response = query_json_api(url, headers) - # print(response) - self._song_url = response["tracks"]["items"][0]["external_urls"]["spotify"] - self._song_uri = response["tracks"]["items"][0]["uri"] + print(response) + try: + self._song_url = response["tracks"]["items"][0]["external_urls"]["spotify"] + self._song_uri = response["tracks"]["items"][0]["uri"] + except IndexError: + print("No song found.") + return print("SONG URI: ", self._song_uri) return self._song_url From 8b568c065eff35d17630ffd25093c73a6865a501 Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Fri, 5 Aug 2022 15:50:26 +0000 Subject: [PATCH 297/371] spotify print exclude --- queries/spotify.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/queries/spotify.py b/queries/spotify.py index e2bbfde2..2b570b79 100644 --- a/queries/spotify.py +++ b/queries/spotify.py @@ -165,7 +165,7 @@ def get_song_by_artist(self): } response = query_json_api(url, headers) - print(response) + # print(response) try: self._song_url = response["tracks"]["items"][0]["external_urls"]["spotify"] self._song_uri = response["tracks"]["items"][0]["uri"] From b654aae6089261cf72e3d34897fd3dce6d802b49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Fri, 5 Aug 2022 18:00:31 +0000 Subject: [PATCH 298/371] =?UTF-8?q?Changed=20Hver=20er=20sta=C3=B0an.=20to?= =?UTF-8?q?=20Hver=20er=20sta=C3=B0an=3F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- queries/dialogue.py | 1 + queries/pizza.py | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/queries/dialogue.py b/queries/dialogue.py index 17e32e64..5ee65ca5 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -38,6 +38,7 @@ # TODO: FIX TYPE HINTS (esp. 'Any') # TODO: Add specific prompt handling to DSM to remove result from DSM. # TODO: Add try-except blocks where appropriate +# TODO: Add "needs_confirmation" to TOML files (skip fulfilled, go straight to confirmed) _TOML_FOLDER_NAME = "dialogues" _DEFAULT_EXPIRATION_TIME = 30 * 60 # a dialogue expires after 30 minutes diff --git a/queries/pizza.py b/queries/pizza.py index 4b70384b..7877abe7 100644 --- a/queries/pizza.py +++ b/queries/pizza.py @@ -499,6 +499,11 @@ def sentence(state: QueryStateDict, result: Result) -> None: print("F", ans) q.set_answer(*ans) print("G") + q.set_beautified_query( + q.beautified_query.replace("Panta", "panta").replace( + "Hver er staðan.", "Hver er staðan?" + ) + ) except Exception as e: print("Exception: ", e) logging.warning("Exception while processing random query: {0}".format(e)) From cb5560b8175af04facfeca0046c50f3cc220455e Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Tue, 9 Aug 2022 12:28:50 +0000 Subject: [PATCH 299/371] New data structure for iot devices --- queries/iot_hue.py | 6 ++-- queries/iot_speakers.py | 2 +- queries/iot_spotify.py | 2 +- queries/js/IoT_Embla/Philips_Hue/hub.js | 12 ++++--- queries/sonos.py | 10 +++--- queries/spotify.py | 5 +-- routes/api.py | 44 ++++++++++++++----------- 7 files changed, 45 insertions(+), 36 deletions(-) diff --git a/queries/iot_hue.py b/queries/iot_hue.py index c5ffc15a..bd2909e8 100755 --- a/queries/iot_hue.py +++ b/queries/iot_hue.py @@ -361,12 +361,14 @@ def sentence(state: QueryStateDict, result: Result) -> None: q.set_qtype(result.qtype) - smartdevice_type = "iot_lights" + smartdevice_type = "iot" client_id = str(q.client_id) print("client_id:", client_id) # Fetch relevant data from the device_data table to perform an action on the lights - device_data = cast(Optional[DeviceData], q.client_data(smartdevice_type)) + device_data = cast( + Optional[DeviceData], q.client_data(smartdevice_type).get("iot_lights") + ) print("location :", q.location) print("device data :", device_data) diff --git a/queries/iot_speakers.py b/queries/iot_speakers.py index 7f965388..cc123906 100644 --- a/queries/iot_speakers.py +++ b/queries/iot_speakers.py @@ -307,7 +307,7 @@ def sentence(state: QueryStateDict, result: Result) -> None: print("IF QTYPE AND QKEY") try: q.set_qtype(result.qtype) - device_data = q.client_data("iot_speakers") + device_data = q.client_data("iot").get("iot_speakers") if device_data is not None: print("JUST BEFORE SONOS CLIENT") sonos_client = SonosClient( diff --git a/queries/iot_spotify.py b/queries/iot_spotify.py index d6e0d4fa..34a8ffaf 100644 --- a/queries/iot_spotify.py +++ b/queries/iot_spotify.py @@ -74,7 +74,7 @@ def handle_plain_text(q) -> bool: artist_name = m.group(2).strip() print("SONG NAME :", song_name) print("ARTIST NAME :", artist_name) - device_data = q.client_data("spotify") + device_data = q.client_data("iot").get("iot_streaming").get("spotify") if device_data is not None: client_id = str(q.client_id) spotify_client = SpotifyClient( diff --git a/queries/js/IoT_Embla/Philips_Hue/hub.js b/queries/js/IoT_Embla/Philips_Hue/hub.js index d56e7e35..2904845c 100644 --- a/queries/js/IoT_Embla/Philips_Hue/hub.js +++ b/queries/js/IoT_Embla/Philips_Hue/hub.js @@ -72,12 +72,14 @@ async function connectHub(clientID, requestURL) { const data = { client_id: clientID, - key: "iot_lights", + key: "iot", data: { - philips_hue: { - credentials: { - username: username.success.username, - ip_address: deviceInfo.internalipaddress, + iot_lights: { + philips_hue: { + credentials: { + username: username.success.username, + ip_address: deviceInfo.internalipaddress, + }, }, }, }, diff --git a/queries/sonos.py b/queries/sonos.py index d4c96b04..f8d67b19 100644 --- a/queries/sonos.py +++ b/queries/sonos.py @@ -98,7 +98,9 @@ def __init__( self._encoded_credentials = read_api_key("SonosEncodedCredentials") self._code = self._device_data["sonos"]["credentials"]["code"] print("code :", self._code) - self._timestamp = device_data.get("sonos").get("credentials").get("timestamp") + self._timestamp = ( + self._device_data.get("sonos").get("credentials").get("timestamp") + ) print("device data :", self._device_data) try: self._access_token = self._device_data["sonos"]["credentials"][ @@ -176,7 +178,6 @@ def _create_token(self): url = f"https://api.sonos.com/login/v3/oauth/access?grant_type=authorization_code&code={self._code}&redirect_uri=http://{host}/connect_sonos.api" headers = { "Authorization": f"Basic {self._encoded_credentials}", - "Cookie": "JSESSIONID=F710019AF0A3B7126A8702577C883B5F; AWSELB=69BFEFC914A689BF6DC8E4652748D7B501ED60290D5EA56F2E543ABD7CF357A5F65186AEBCFB059E28075D83A700FD504C030A53CC28683B515BE3DCA3CC587AFAF606E171; AWSELBCORS=69BFEFC914A689BF6DC8E4652748D7B501ED60290D5EA56F2E543ABD7CF357A5F65186AEBCFB059E28075D83A700FD504C030A53CC28683B515BE3DCA3CC587AFAF606E171", } response = post_to_json_api(url, headers=headers) @@ -343,9 +344,8 @@ def _store_data_and_credentials(self): self._store_data(sonos_dict) def _store_data(self, data): - Query.store_query_data( - self._client_id, "iot_speakers", data, update_in_place=True - ) + new_dict = {"iot_speakers": data} + Query.store_query_data(self._client_id, "iot", new_dict, update_in_place=True) def _create_groupdict_for_db(self, groups: list): print("create_groupdict_for_db") diff --git a/queries/spotify.py b/queries/spotify.py index 2b570b79..fef24c7f 100644 --- a/queries/spotify.py +++ b/queries/spotify.py @@ -107,7 +107,7 @@ def _refresh_expired_token(self): self._timestamp = str(datetime.now()) def _store_credentials(self): - print("_store_smartthings_cred") + print("_store_spotify_cred") # data_dict = self._create_sonos_data_dict() cred_dict = self._create_cred_dict() self._store_data(cred_dict) @@ -124,7 +124,8 @@ def _create_cred_dict(self): return cred_dict def _store_data(self, data): - Query.store_query_data(self._client_id, "spotify", data, update_in_place=True) + new_dict = {"iot_streaming": {"spotify": data}} + Query.store_query_data(self._client_id, "iot", new_dict, update_in_place=True) def _store_credentials(self): print("_store_spotify credentials") diff --git a/routes/api.py b/routes/api.py index 71f9ecd0..9e541b69 100755 --- a/routes/api.py +++ b/routes/api.py @@ -644,15 +644,17 @@ def register_query_data_api(version: int = 1) -> Response: Data format example for IoT device from js code: 'client_id': clientID, - 'key': "iot_lights", + 'key': "iot", 'data': { - 'philips_hue': { - 'credentials': { - 'username': username, - 'ip_address': IP address, - }, - 'data': { - 'groups': {name, id}, {name, id}, ... + 'iot_lights: { + 'philips_hue': { + 'credentials': { + 'username': username, + 'ip_address': IP address, + }, + 'data': { + 'groups': {name, id}, {name, id}, ... + } }, }; @@ -680,7 +682,7 @@ def register_query_data_api(version: int = 1) -> Response: return better_jsonify(valid=False, errmsg="Missing parameters.") success = QueryObject.store_query_data( - qdata["client_id"], qdata["key"], qdata["data"] + qdata["client_id"], qdata["key"], qdata["data"], update_in_place=True ) if success: return better_jsonify(valid=True, msg="Query data registered") @@ -733,24 +735,24 @@ def sonos_code(version: int = 1) -> Response: client_id = args.get("state") code = args.get("code") code_dict = { - "sonos": {"credentials": {"code": code}} + "iot_speakers": {"sonos": {"credentials": {"code": code}}} } # create a dictonary with the code if client_id and code: success = QueryObject.store_query_data( - client_id, "iot_speakers", code_dict, update_in_place=True + client_id, "iot", code_dict, update_in_place=True ) if success: - device_data = code_dict + device_data = code_dict["iot_speakers"] # Create an instance of the SonosClient class. This will automatically create the rest of the credentials needed. sonos_client = SonosClient(device_data, client_id) sonos_voice_clip = ( f"Hæ! Embla hérna! Ég er búin að tengja þennan Sónos hátalara." ) - sonos_client.play_chime() - time.sleep(1.3) - sonos_client.play_audio_clip( - text_to_audio_url(sonos_voice_clip) - ) # Send the above message to the Sonos speaker + # sonos_client.play_chime() + # time.sleep(1.3) + # sonos_client.play_audio_clip( + # text_to_audio_url(sonos_voice_clip) + # ) # Send the above message to the Sonos speaker return better_jsonify(valid=True, msg="Registered sonos code") return better_jsonify(valid=False, errmsg="Error registering sonos code.") @@ -791,13 +793,15 @@ def spotify_code(version: int = 1) -> Response: args = request.args client_id = args.get("state") code = args.get("code") - code_dict = {"credentials": {"code": code}} # create a dictonary with the code + code_dict = { + "iot_streaming": {"spotify": {"credentials": {"code": code}}} + } # create a dictonary with the code if client_id and code: success = QueryObject.store_query_data( - client_id, "spotify", code_dict, update_in_place=True + client_id, "iot", code_dict, update_in_place=True ) if success: - device_data = code_dict + device_data = code_dict.get("iot_streaming").get("spotify") spotify_client = SpotifyClient(device_data, client_id) # Create an instance of the SonosClient class. This will automatically create the rest of the credentials needed. return better_jsonify(valid=True, msg="Registered spotify code") From 62922899286c7eea02f77bcd223c7d10f3f9bc82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Tue, 9 Aug 2022 12:34:49 +0000 Subject: [PATCH 300/371] Start of HTML views for iot --- .gitignore | 1 + queries/sonos.py | 13 +++-- query.py | 31 +++++++++++- routes/api.py | 34 +++++++++++++ routes/main.py | 18 +++++++ static/css/style.css | 72 ++++++++++++++++++++++++++++ templates/hue-instructions.html | 28 +++++++++++ templates/iot-info.html | 80 +++++++++++++++++++++++++++++++ templates/main.html | 46 +++++++++--------- templates/sonos-instructions.html | 11 +++++ 10 files changed, 308 insertions(+), 26 deletions(-) create mode 100644 static/css/style.css create mode 100644 templates/hue-instructions.html create mode 100644 templates/iot-info.html create mode 100644 templates/sonos-instructions.html diff --git a/.gitignore b/.gitignore index c4424052..265fc00e 100644 --- a/.gitignore +++ b/.gitignore @@ -45,6 +45,7 @@ nnmain.sh # Various resource files resources/*.txt +resources/*.txt:Zone.Identifier !resources/formers.txt !resources/last.txt !resources/ordalisti.sorted.txt diff --git a/queries/sonos.py b/queries/sonos.py index 42c015e7..7e6ec49b 100644 --- a/queries/sonos.py +++ b/queries/sonos.py @@ -98,16 +98,22 @@ def __init__( self._encoded_credentials = read_api_key("SonosEncodedCredentials") self._code = self._device_data["sonos"]["credentials"]["code"] print("code :", self._code) - self._timestamp = device_data.get("sonos").get("credentials").get("timestamp") + self._timestamp = ( + self._device_data.get("sonos").get("credentials").get("timestamp") + ) print("device data :", self._device_data) try: + print("Trying to get access token") self._access_token = self._device_data["sonos"]["credentials"][ "access_token" ] + print("access token :", self._access_token) self._refresh_token = self._device_data["sonos"]["credentials"][ "refresh_token" ] + print("refresh token :", self._refresh_token) except (KeyError, TypeError): + print("No access token found for Sonos.") self._create_token() self._check_token_expiration() self._households = self._get_households() @@ -176,12 +182,13 @@ def _create_token(self): url = f"https://api.sonos.com/login/v3/oauth/access?grant_type=authorization_code&code={self._code}&redirect_uri=http://{host}/connect_sonos.api" headers = { "Authorization": f"Basic {self._encoded_credentials}", - "Cookie": "JSESSIONID=F710019AF0A3B7126A8702577C883B5F; AWSELB=69BFEFC914A689BF6DC8E4652748D7B501ED60290D5EA56F2E543ABD7CF357A5F65186AEBCFB059E28075D83A700FD504C030A53CC28683B515BE3DCA3CC587AFAF606E171; AWSELBCORS=69BFEFC914A689BF6DC8E4652748D7B501ED60290D5EA56F2E543ABD7CF357A5F65186AEBCFB059E28075D83A700FD504C030A53CC28683B515BE3DCA3CC587AFAF606E171", + # "Cookie": "JSESSIONID=F710019AF0A3B7126A8702577C883B5F; AWSELB=69BFEFC914A689BF6DC8E4652748D7B501ED60290D5EA56F2E543ABD7CF357A5F65186AEBCFB059E28075D83A700FD504C030A53CC28683B515BE3DCA3CC587AFAF606E171; AWSELBCORS=69BFEFC914A689BF6DC8E4652748D7B501ED60290D5EA56F2E543ABD7CF357A5F65186AEBCFB059E28075D83A700FD504C030A53CC28683B515BE3DCA3CC587AFAF606E171", } response = post_to_json_api(url, headers=headers) - + print("Reponse :", response) self._access_token = response.get("access_token") + print("access token :", self._access_token) self._refresh_token = response.get("refresh_token") self._timestamp = str(datetime.now()) return response diff --git a/query.py b/query.py index a6ebc91d..53643fcc 100755 --- a/query.py +++ b/query.py @@ -301,7 +301,12 @@ def process_queries( return False with self.context(session, processor, query=query) as state: for query_tree in self._query_trees: - print("Processing query tree", query_tree.string_self(), "in module", processor.__name__) + print( + "Processing query tree", + query_tree.string_self(), + "in module", + processor.__name__, + ) # Is the processor interested in the root nonterminal # of this query tree? if query_tree.string_self() in processor_query_types: @@ -889,6 +894,30 @@ def client_data(self, key: str) -> Optional[ClientDataDict]: ) return None + @staticmethod + # TODO: This is a hack to get the query data for a specific device to render connected iot devices + def get_client_data(client_id: str, key: str) -> Optional[ClientDataDict]: + """Fetch client_id-associated data stored in the querydata table""" + with SessionContext(read_only=True) as session: + try: + client_data = ( + session.query(QueryData) + .filter(QueryData.key == key) + .filter(QueryData.client_id == client_id) + ).one_or_none() + return ( + None + if client_data is None + else cast(ClientDataDict, client_data.data) + ) + except Exception as e: + logging.error( + "Error fetching client '{0}' query data for key '{1}' from db: {2}".format( + client_id, key, e + ) + ) + return None + def set_client_data( self, key: str, data: ClientDataDict, update_in_place=False ) -> None: diff --git a/routes/api.py b/routes/api.py index 71f9ecd0..1dc8510c 100755 --- a/routes/api.py +++ b/routes/api.py @@ -818,3 +818,37 @@ def spotify_code(version: int = 1) -> Response: # return better_jsonify(valid=True, msg="Registered sonos code") # return better_jsonify(valid=False, errmsg="Error registering sonos code.") + +# TODO: Finish functionality to delete iot data from database +@routes.route("/delete_iot_data.api", methods=["DELETE"]) +@routes.route("/delete_iot_data.api/v", methods=["DELETE"]) +def delete_iot_data(version: int = 1) -> Response: + """ + API endpoint to delete IoT data + """ + args = request.args + client_id = args.get("client_id") + + if client_id: + success = QueryObject.delete_query_data(client_id) + if success: + return better_jsonify(valid=True, msg="Deleted IoT data") + return better_jsonify(valid=False, errmsg="Error deleting IoT data.") + + +@routes.route("/get_iot_devices.api", methods=["GET"]) +@routes.route("/get_iot_devices.api/v", methods=["GET"]) +def get_iot_devices(version: int = 1) -> Response: + """ + API endpoint to get IoT devices + """ + args = request.args + client_id = args.get("client_id") + + if client_id: + data = QueryObject.get_client_data(client_id, "iot_speakers") + if data: + print("Data: ", data["sonos"]) + json = better_jsonify(valid=True, data=data) + return json + return better_jsonify(valid=False, errmsg="Error getting IoT data.") diff --git a/routes/main.py b/routes/main.py index 1af6fefc..c4c894b8 100755 --- a/routes/main.py +++ b/routes/main.py @@ -96,6 +96,24 @@ def analysis(): return render_template("analysis.html", title="Málgreining", default_text=txt) +@routes.route("/iot/") +@max_age(seconds=60) +def iot(device: str): + """Handler for device connection views.""" + device_variables: Dict[str, Dict[str, Any]] = { + "hue-instructions": { + "iot_name": "Philips Hue", + "iot_description": "Hue er ein útlægir ljósæki sem er hægt að nota til að styrka ljósæki á Íslandi.", + }, + "sonos-instructions": { + "iot_name": "Sonos", + "iot_description": "Sonos er hátalari.", + }, + } + print("iot", device) + return render_template(f"{str(device)}.html", **device_variables.get(device)) + + @routes.route("/correct", methods=["GET", "POST"]) def correct(): """Handler for a page for spelling and grammar correction diff --git a/static/css/style.css b/static/css/style.css new file mode 100644 index 00000000..0128660d --- /dev/null +++ b/static/css/style.css @@ -0,0 +1,72 @@ +/* latin-ext */ +@font-face { + font-family: 'Lato'; + font-style: normal; + font-weight: 400; + font-display: block; + src: local('Lato'), + url("Lato-Regular.woff2") format('woff2'), + url("Lato-Regular.ttf") format('truetype'); +} + +@font-face { + font-family: 'Lato'; + font-style: normal; + font-weight: 700; + font-display: block; + src: local('Lato Bold'), + url("Lato-Bold.woff2") format('woff2'); +} + +@font-face { + font-family: 'Lato'; + font-style: italic; + font-weight: 400; + font-display: block; + src: local('Lato Italic'), + url("Lato-Italic.woff2") format('woff2'); +} + +body { + background-color: rgba(249, 249, 249, 1.0); + font-family: "Lato"; + font-size: 21px; + /* text-align: center;*/ + color: rgba(51, 51, 51, 1.0); +} + +a { + color: #E8302C; +} + +h1 { + font-size: 24px; + margin-top: 4px; +} + +h2 { + font-size: 18px; + margin-top: 4px; +} + +h4 { + text-align: center; +} + +.content { + padding-left: 15px; + padding-right: 15px; + max-width: 800px; + margin: auto; + display: inline-block; +} + +.content ul { + list-style-type: none; + padding-left: 0; +} + +.content ul li { + text-align: center; + font-style: italic; +} \ No newline at end of file diff --git a/templates/hue-instructions.html b/templates/hue-instructions.html new file mode 100644 index 00000000..f1144ec5 --- /dev/null +++ b/templates/hue-instructions.html @@ -0,0 +1,28 @@ +{% extends "iot-info.html" %} + + +{% block content %} +
+

Fyrirspurnaleiðbeiningar

+ +

Embla ræður eins og stendur við eftirfarandi tegundir fyrirspurna fyrir {{ iot_name }}. + Þetta er ekki tæmandi listi, þar sem hún skilur einnig ýmis tilbrigði við þessar spurningar.

+ +

Kveikja og slökkva

+ +
    +
  • Kveiktu ljósin í eldhúsinu.
  • +
  • Slökktu á ljósunum í svefnherberginnu.
  • +
  • Kveiktu ljósin frammi í eldhúsi.
  • +
+ +

Hækka og lækka birtu

+ +
    +
  • Hækkaðu birtuna í stofunni.
  • +
  • Gerðu birtu ljóssins í eldhúsinu meiri.
  • +
  • Lækkaðu birtustigið í svefnherberginu.
  • +
  • Láttu birtuna í eldhúsinu vera minni.
  • +
+
+{% endblock %} \ No newline at end of file diff --git a/templates/iot-info.html b/templates/iot-info.html new file mode 100644 index 00000000..9924c488 --- /dev/null +++ b/templates/iot-info.html @@ -0,0 +1,80 @@ + + + + + + + + + + {{ iot_name }} upplýsingar + + + + + + +
+ +

{{ iot_name }}

+ +

Upplýsingar

+ +

{{ iot_description }}

+ + + +
+ +
+ +
+ + {% block content %} + {% endblock %} + + + + + \ No newline at end of file diff --git a/templates/main.html b/templates/main.html index a0082f7a..6fd4eeb2 100644 --- a/templates/main.html +++ b/templates/main.html @@ -1,4 +1,3 @@ - {% extends "container-normal.html" %} {% block styles %} @@ -15,17 +14,18 @@
- +
- +
@@ -41,24 +41,25 @@
- Sláðu inn leitarstreng eða spurningu um mannanöfn, titla, starfsheiti eða sérnöfn hér að ofan. + Sláðu inn leitarstreng eða spurningu um mannanöfn, titla, starfsheiti eða sérnöfn hér að + ofan.

Leitarvél Greynis byggir á umfjöllunarefnum (þemum) frekar en bókstaflegum - leitarorðum. Bestu niðurstöður fást með því að skrifa hnitmiðaðar setningar sem líkjast - þeim niðurstöðum sem óskað er eftir. Mannanöfn og sérnöfn hafa þó sérstaka vigt.

+ leitarorðum. Bestu niðurstöður fást með því að skrifa hnitmiðaðar setningar sem líkjast + þeim niðurstöðum sem óskað er eftir. Mannanöfn og sérnöfn hafa þó sérstaka vigt.

    -
  • Dæmi um leitarstrengi: - Portúgal sigraði Eurovision með hugljúfu lagi, - Einkaneysla fer hraðvaxandi, - Mig langar að elda fisk í kvöldmatinn, - Verið er að byggja fjölda hótela á landinu -
  • -
  • Dæmi um spurningar: - Hver er forseti Finnlands?, - Hver er Þórunn Ólafsdóttir?, - Hvað er UNESCO? -
  • +
  • Dæmi um leitarstrengi: + Portúgal sigraði Eurovision með hugljúfu lagi, + Einkaneysla fer hraðvaxandi, + Mig langar að elda fisk í kvöldmatinn, + Verið er að byggja fjölda hótela á landinu +
  • +
  • Dæmi um spurningar: + Hver er forseti Finnlands?, + Hver er Þórunn Ólafsdóttir?, + Hvað er UNESCO? +

Einnig má slá inn vefslóð til að málgreina texta af vefnum.

@@ -116,7 +117,8 @@ {% block endscripts %} - + @@ -132,4 +134,4 @@ -{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/templates/sonos-instructions.html b/templates/sonos-instructions.html new file mode 100644 index 00000000..bb83b7a3 --- /dev/null +++ b/templates/sonos-instructions.html @@ -0,0 +1,11 @@ +{% extends "iot-info.html" %} + + +{% block content %} +
+

Fyrirspurnaleiðbeiningar

+ +

Embla ræður eins og stendur við eftirfarandi tegundir fyrirspurna fyrir {{ iot_name }}. + Þetta er ekki tæmandi listi, þar sem hún skilur einnig ýmis tilbrigði við þessar spurningar.

+
+{% endblock %} \ No newline at end of file From beb37f74fa0eddc8bd6ea296345ae3ee482e2fc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Wed, 10 Aug 2022 10:29:33 +0000 Subject: [PATCH 301/371] added delete_iot_data function that deletes a connection from the iot part of client_data --- query.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/query.py b/query.py index 53643fcc..ca355915 100755 --- a/query.py +++ b/query.py @@ -971,6 +971,35 @@ def store_query_data( logging.error("Error storing query data in db: {0}".format(e)) return False + @staticmethod + def delete_iot_data(client_id: str, iot_group: str, iot_name: str) -> bool: + """Delete iot data for specific iot_group and iot_name for a client""" + if not client_id or not iot_group or not iot_name: + return False + try: + with SessionContext(commit=True) as session: + rows = ( + session.query(QueryData) + .filter(QueryData.client_id == client_id) + .filter(QueryData.key == "iot") + # .filter(QueryData.data.contains(iot_group)) + # .filter(QueryData.data.contains(iot_name)) + ).all() + for row in rows: + iot_dict = row.data + if iot_group in iot_dict: + if iot_name in iot_dict[iot_group]: + del iot_dict[iot_group][iot_name] + if not iot_dict[iot_group]: + del iot_dict[iot_group] + if not iot_dict: + session.delete(row) + Query.store_query_data(client_id, "iot", iot_dict) + return True + except Exception as e: + logging.error("Error deleting iot data from db: {0}".format(e)) + return False + @classmethod def try_to_help(cls, query: str, result: ResponseDict) -> None: """Attempt to help the user in the case of a failed query, From 95777660a2b8f209c4f8984270b3f543ab559397 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Wed, 10 Aug 2022 10:30:23 +0000 Subject: [PATCH 302/371] added routes to delete iot data and get iot data --- routes/api.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/routes/api.py b/routes/api.py index 0ec02103..8cc6c7c3 100755 --- a/routes/api.py +++ b/routes/api.py @@ -832,9 +832,12 @@ def delete_iot_data(version: int = 1) -> Response: """ args = request.args client_id = args.get("client_id") + iot_group = args.get("iot_group") + iot_name = args.get("iot_name") + print("In delete_iot_data") - if client_id: - success = QueryObject.delete_query_data(client_id) + if client_id and iot_group and iot_name: + success = QueryObject.delete_iot_data(client_id, iot_group, iot_name) if success: return better_jsonify(valid=True, msg="Deleted IoT data") return better_jsonify(valid=False, errmsg="Error deleting IoT data.") @@ -850,9 +853,10 @@ def get_iot_devices(version: int = 1) -> Response: client_id = args.get("client_id") if client_id: - data = QueryObject.get_client_data(client_id, "iot_speakers") + data = QueryObject.get_client_data(client_id, "iot") + print("Data: ", data) if data: - print("Data: ", data["sonos"]) json = better_jsonify(valid=True, data=data) return json + print("Error getting IoT devices") return better_jsonify(valid=False, errmsg="Error getting IoT data.") From 8c243560a2614202a8b748d5137104479392d7af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Wed, 10 Aug 2022 10:31:08 +0000 Subject: [PATCH 303/371] Fixed bug where empty client_data would cause errors when rendering template for iot web view --- routes/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routes/main.py b/routes/main.py index c4c894b8..58d03bc2 100755 --- a/routes/main.py +++ b/routes/main.py @@ -111,7 +111,7 @@ def iot(device: str): }, } print("iot", device) - return render_template(f"{str(device)}.html", **device_variables.get(device)) + return render_template(f"{str(device)}.html", **device_variables.get(device, {})) @routes.route("/correct", methods=["GET", "POST"]) From a4471efe45d76f2e27302bf925ef5a8b1db98882 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Wed, 10 Aug 2022 10:33:20 +0000 Subject: [PATCH 304/371] Added javascript functionality to the disconnect device button --- templates/iot-info.html | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/templates/iot-info.html b/templates/iot-info.html index 9924c488..c8d1676e 100644 --- a/templates/iot-info.html +++ b/templates/iot-info.html @@ -36,13 +36,15 @@

Upplýsingar

display: flex; justify-content: center; "> -
@@ -51,16 +53,20 @@

Upplýsingar

\ No newline at end of file From 96089930ffdafdd52b91688149dc26396e2517e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Wed, 10 Aug 2022 10:34:22 +0000 Subject: [PATCH 305/371] Added html web view for hue connection process --- templates/hue-connection.html | 164 ++++++++++++++++++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 templates/hue-connection.html diff --git a/templates/hue-connection.html b/templates/hue-connection.html new file mode 100644 index 00000000..e640dcbd --- /dev/null +++ b/templates/hue-connection.html @@ -0,0 +1,164 @@ + + + + + + + + + + Philips Hue + + + + + + + +
+

Leiðbeiningar

+
    +
  1. Tengdu Philips Hue miðstöðina með netsnúru.
  2. +
  3. Gættu þess að miðstöðin sé tengd sama neti og síminn þinn.
  4. +
  5. Ýttu á takkann á Hue miðstöðinni þinni.
  6. +
  7. Veldu „Tengja“ innan 30 sekúndna frá því þú ýttir á takkann.
  8. +
+ + + +
+ + + + + + \ No newline at end of file From 43a1f56c3cc16ef2c497a31812a4b113fb908e87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Wed, 10 Aug 2022 17:01:20 +0000 Subject: [PATCH 306/371] added get_supported_iot_connections api point. It reads a toml file and returns it to the user, with correctly formatted webview links --- requirements.txt | 1 + resources/iot_supported.toml | 40 +++++++++++++++++++++++++ routes/api.py | 58 +++++++++++++++++++++++++++++++++++- 3 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 resources/iot_supported.toml diff --git a/requirements.txt b/requirements.txt index df47de7d..a79197ef 100644 --- a/requirements.txt +++ b/requirements.txt @@ -26,4 +26,5 @@ rjsmin>=1.1.0 odfpy>=1.4.1 pdfminer.six>=20201018 python-youtube>=0.8.1 +tomli >= 1.1.0 ; python_version < "3.11" azure-cognitiveservices-speech>=1.19.0 diff --git a/resources/iot_supported.toml b/resources/iot_supported.toml new file mode 100644 index 00000000..d1b0d1f2 --- /dev/null +++ b/resources/iot_supported.toml @@ -0,0 +1,40 @@ + +[connections.philips_hue] +display_name = "Philips Hue Hub" +mdns_name = "_hue._tcp.local" +db_name = "philips_hue" +name = "Hue Hub" +brand = "Philips" +group = "iot_lights" +logo = "philips.png" +# lightbulb_outline_rounded +icon = 0xf854 +webview_home = '{host}/iot/hue-instructions?client_id={client_id}&iot_group=iot_lights&iot_name=philips_hue' +webview_connect = '{host}/iot/hue-connection?client_id={client_id}&request_url={host}' + +[connections.sonos] +display_name = "Sonos" +mdns_name = "_sonos._tcp.local" +db_name = "sonos" +name = "Sonos" +brand = "Sonos Inc." +group = "iot_speakers" +logp = "sonos.png" +# speaker_outlined +icon = 0xf3c1 +webview_home = '{host}/iot/sonos-instructions?client_id={client_id}&iot_group=iot_speakers&iot_name=sonos' +webview_connect = 'https://api.sonos.com/login/v3/oauth?client_id={api_key}&response_type=code&state={client_id}&scope=playback-control-all&redirect_uri={host}/connect_sonos.api' +api_key_filename = "SonosKey" + + +[connections.spotify] +display_name = "Spotify" +db_name = "spotify" +name = "Spotify" +brand = "Spotify" +group = "iot_streaming" +logo = "spotify.png" +#music_note_outlined +icon = 0xf1fb +webview_home = '{host}/iot/hue-instructions?client_id={client_id}&iot_group=iot_lights&iot_name=philips_hue' +webview_connect = '{host}/iot/hue-connection?client_id={client_id}&request_url={host}' diff --git a/routes/api.py b/routes/api.py index 8cc6c7c3..036ac37d 100755 --- a/routes/api.py +++ b/routes/api.py @@ -22,12 +22,18 @@ """ -from typing import Dict, Any, List, Optional, cast +from typing import Dict, Any, List, Optional, TypedDict, cast from datetime import datetime import logging +import os.path import time +try: + import tomllib # type: ignore (module not available in Python <3.11) +except ModuleNotFoundError: + import tomli as tomllib # Used for Python <3.11 + from flask import request, abort from flask.wrappers import Response @@ -44,6 +50,7 @@ from query import process_query from query import Query as QueryObject from doc import SUPPORTED_DOC_MIMETYPES, Document +from util import read_api_key from speech import ( text_to_audio_url, DEFAULT_VOICE, @@ -860,3 +867,52 @@ def get_iot_devices(version: int = 1) -> Response: return json print("Error getting IoT devices") return better_jsonify(valid=False, errmsg="Error getting IoT data.") + + +class IotSupportedTOMLStructure(TypedDict): + """Structure of the iot_supported TOML file.""" + + connections: Dict[str, Dict[str, str]] + + +@routes.route("/get_supported_iot_connections.api", methods=["GET"]) +@routes.route("/get_supported_iot_connections.api/v", methods=["GET"]) +def get_supported_iot_connections(version: int = 1) -> Response: + """ + API endpoint to get supported IOT devices from iot_supported.toml. + Converts it to json and puts it in the repsonse body. + """ + args = request.args + client_id: str = args.get("client_id") + host: str = args.get("host") + print("Host: ", host) + basepath, _ = os.path.split(os.path.realpath(__file__)) + fpath = os.path.join(basepath, "../resources/iot_supported.toml") + print("fpath: ", fpath) + with open(fpath, mode="r") as file: + f = file.read() + # Read TOML file containing a list of resources for the dialogue + obj: IotSupportedTOMLStructure = tomllib.loads(f) # type: ignore + print("TOML: ", obj) + print("Connections: ", obj["connections"]) + if obj: + for (_, connection) in obj["connections"].items(): + print("Connection: ", connection) + webview_home = connection["webview_home"] + print("Webview home: ", webview_home) + webview_home = webview_home.format(host=host, client_id=client_id) + connection.update({"webview_home": webview_home}) + webview_connect = connection["webview_connect"] + if "api_key_filename" in connection: + api_key_filename: str = connection["api_key_filename"] + api_key = read_api_key(api_key_filename) + webview_connect = webview_connect.format( + host=host, client_id=client_id, api_key=api_key + ) + else: + webview_connect = webview_connect.format(host=host, client_id=client_id) + connection.update({"webview_connect": webview_connect}) + print("Connection: ", connection) + json = better_jsonify(valid=True, data=obj) + return json + return better_jsonify(valid=False, errmsg="Error getting supported IOT devices.") From 8c6f040bbd1a7c302bdddaf8fb997c6ef74ba1f2 Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Thu, 11 Aug 2022 12:14:18 +0000 Subject: [PATCH 307/371] Command instructions added to html instructions files --- templates/hue-instructions.html | 34 ++++++++++++++++++++++++++++- templates/sonos-instructions.html | 23 ++++++++++++++++++- templates/spotify-instructions.html | 17 +++++++++++++++ 3 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 templates/spotify-instructions.html diff --git a/templates/hue-instructions.html b/templates/hue-instructions.html index f1144ec5..828f385c 100644 --- a/templates/hue-instructions.html +++ b/templates/hue-instructions.html @@ -11,7 +11,7 @@

Fyrirspurnaleiðbeiningar

Kveikja og slökkva

    -
  • Kveiktu ljósin í eldhúsinu.
  • +
  • Kveiktu ljósin á skrifstofunni.
  • Slökktu á ljósunum í svefnherberginnu.
  • Kveiktu ljósin frammi í eldhúsi.
@@ -24,5 +24,37 @@

Hækka og lækka birtu

  • Lækkaðu birtustigið í svefnherberginu.
  • Láttu birtuna í eldhúsinu vera minni.
  • + +

    Breyta lit ljósa

    + +
      +
    • Gerðu lit ljóssins í eldhúsinu grænt
    • +
    • Gerðu lesstofuna rauða
    • +
    • Breyttu leslampanum í rauðan lit
    • +
    + +

    Breyta birtustigi

    + +
      +
    • Gerðu baðherbergið dimmara
    • +
    • Hækkaðu birtustigið í eldhúsinu
    • +
    + + +

    Breyta hitastigi (e. temperature)

    + +
      +
    • Gerðu birtuna í eldhúsinu hlýrri
    • +
    • Gerðu birtuna á leslampanum kaldari
    • +
    + +

    Senur (e. scenes)

    + +
      +
    • Settu á stemninguna Rómó í svefnherberginu”
    • +
    • Stilltu á Kvöldstund senuna í eldhúsinu
    • +
    • Gerðu stemninguna í bílskúrnum rómó”
    • +
    +
    {% endblock %} \ No newline at end of file diff --git a/templates/sonos-instructions.html b/templates/sonos-instructions.html index bb83b7a3..6adf7685 100644 --- a/templates/sonos-instructions.html +++ b/templates/sonos-instructions.html @@ -7,5 +7,26 @@

    Fyrirspurnaleiðbeiningar

    Embla ræður eins og stendur við eftirfarandi tegundir fyrirspurna fyrir {{ iot_name }}. Þetta er ekki tæmandi listi, þar sem hún skilur einnig ýmis tilbrigði við þessar spurningar.

    -
    + +

    Kveikja og slökkva

    + +
      +
    • Kveiktu á tónlist.
    • +
    • Slökktu á tónlistinni.
    • +
    + +

    Útvarp

    + +
      +
    • Stilltu á Rondó
    • +
    • Settu á Bylgjuna
    • +
    + +

    Breyta hljóðstyrk

    + +
      +
    • Lækkaðu í tónlistinni
    • +
    • Hækkaðu í útvarpinu
    • +
    + {% endblock %} \ No newline at end of file diff --git a/templates/spotify-instructions.html b/templates/spotify-instructions.html new file mode 100644 index 00000000..4f9e8377 --- /dev/null +++ b/templates/spotify-instructions.html @@ -0,0 +1,17 @@ +{% extends "iot-info.html" %} + + +{% block content %} +
    +

    Fyrirspurnaleiðbeiningar

    + +

    Embla ræður eins og stendur við eftirfarandi tegundir fyrirspurna fyrir {{ iot_name }}. + Þetta er ekki tæmandi listi, þar sem hún skilur einnig ýmis tilbrigði við þessar spurningar.

    + +

    Eina skipunin sem þú munt nokkurn tímann þurfa

    + +
      +
    • Spilaðu Þorparann með Pálma Gunnarssyni
    • +
    + +{% endblock %} \ No newline at end of file From e2704eceba2a03f85e243d7596dc7c63addf2447 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Thu, 11 Aug 2022 12:14:43 +0000 Subject: [PATCH 308/371] Flutter connection in javascript --- templates/iot-info.html | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/templates/iot-info.html b/templates/iot-info.html index c8d1676e..7615f56f 100644 --- a/templates/iot-info.html +++ b/templates/iot-info.html @@ -64,6 +64,13 @@

    Upplýsingar

    let disconnectButton = document.getElementById('disconnect_button'); // Delete device from database by calling API disconnectButton.onclick = async function () { + window.flutter_inappwebview.callHandler("flutter_handler", { + "method": "deleteDevice", + "clientId": clientId, + "iotName": iotName, + "iotGroup": iotGroup + }); + return; //TODO: Delete iot info from database fetch(`http://192.168.1.76:5000/delete_iot_data.api?client_id=${clientId}&iot_group=${iotGroup}&iot_name=${iotName}`, { method: "DELETE", From d0deac88018edc05178a23ed0004c2f79402b112 Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Thu, 11 Aug 2022 12:18:08 +0000 Subject: [PATCH 309/371] html instruction fix --- templates/hue-instructions.html | 20 ++++++++++---------- templates/sonos-instructions.html | 10 +++++----- templates/spotify-instructions.html | 2 +- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/templates/hue-instructions.html b/templates/hue-instructions.html index 828f385c..a09b07cd 100644 --- a/templates/hue-instructions.html +++ b/templates/hue-instructions.html @@ -28,32 +28,32 @@

    Hækka og lækka birtu

    Breyta lit ljósa

      -
    • Gerðu lit ljóssins í eldhúsinu grænt
    • -
    • Gerðu lesstofuna rauða
    • -
    • Breyttu leslampanum í rauðan lit
    • +
    • Gerðu lit ljóssins í eldhúsinu grænt.
    • +
    • Gerðu lesstofuna rauða.
    • +
    • Breyttu leslampanum í rauðan lit.

    Breyta birtustigi

      -
    • Gerðu baðherbergið dimmara
    • -
    • Hækkaðu birtustigið í eldhúsinu
    • +
    • Gerðu baðherbergið dimmara.
    • +
    • Hækkaðu birtustigið í eldhúsinu.

    Breyta hitastigi (e. temperature)

      -
    • Gerðu birtuna í eldhúsinu hlýrri
    • -
    • Gerðu birtuna á leslampanum kaldari
    • +
    • Gerðu birtuna í eldhúsinu hlýrri.
    • +
    • Gerðu birtuna á leslampanum kaldari.

    Senur (e. scenes)

      -
    • Settu á stemninguna Rómó í svefnherberginu”
    • -
    • Stilltu á Kvöldstund senuna í eldhúsinu
    • -
    • Gerðu stemninguna í bílskúrnum rómó”
    • +
    • Settu á stemninguna Rómó í svefnherberginu.
    • +
    • Stilltu á Kvöldstund senuna í eldhúsinu.
    • +
    • Gerðu stemninguna í bílskúrnum rómó.
    diff --git a/templates/sonos-instructions.html b/templates/sonos-instructions.html index 6adf7685..e4bcef87 100644 --- a/templates/sonos-instructions.html +++ b/templates/sonos-instructions.html @@ -18,15 +18,15 @@

    Kveikja og slökkva

    Útvarp

      -
    • Stilltu á Rondó
    • -
    • Settu á Bylgjuna
    • +
    • Stilltu á Rondó.
    • +
    • Settu á Bylgjuna.

    Breyta hljóðstyrk

      -
    • Lækkaðu í tónlistinni
    • -
    • Hækkaðu í útvarpinu
    • +
    • Lækkaðu í tónlistinni.
    • +
    • Hækkaðu í útvarpinu.
    - + {% endblock %} \ No newline at end of file diff --git a/templates/spotify-instructions.html b/templates/spotify-instructions.html index 4f9e8377..1855c9d6 100644 --- a/templates/spotify-instructions.html +++ b/templates/spotify-instructions.html @@ -11,7 +11,7 @@

    Fyrirspurnaleiðbeiningar

    Eina skipunin sem þú munt nokkurn tímann þurfa

      -
    • Spilaðu Þorparann með Pálma Gunnarssyni
    • +
    • Spilaðu Þorparann með Pálma Gunnarssyni.
    {% endblock %} \ No newline at end of file From 1ee3151d7c8cf451d79e241436e03565f5abff22 Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Thu, 11 Aug 2022 15:22:50 +0000 Subject: [PATCH 310/371] Size of headers in html instructions edited --- templates/hue-instructions.html | 14 +++++++------- templates/sonos-instructions.html | 8 ++++---- templates/spotify-instructions.html | 4 ++-- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/templates/hue-instructions.html b/templates/hue-instructions.html index a09b07cd..f2edb081 100644 --- a/templates/hue-instructions.html +++ b/templates/hue-instructions.html @@ -3,12 +3,12 @@ {% block content %}
    -

    Fyrirspurnaleiðbeiningar

    +

    Fyrirspurnaleiðbeiningar

    Embla ræður eins og stendur við eftirfarandi tegundir fyrirspurna fyrir {{ iot_name }}. Þetta er ekki tæmandi listi, þar sem hún skilur einnig ýmis tilbrigði við þessar spurningar.

    -

    Kveikja og slökkva

    +

    Kveikja og slökkva

    • Kveiktu ljósin á skrifstofunni.
    • @@ -16,7 +16,7 @@

      Kveikja og slökkva

    • Kveiktu ljósin frammi í eldhúsi.
    -

    Hækka og lækka birtu

    +

    Hækka og lækka birtu

    • Hækkaðu birtuna í stofunni.
    • @@ -25,7 +25,7 @@

      Hækka og lækka birtu

    • Láttu birtuna í eldhúsinu vera minni.
    -

    Breyta lit ljósa

    +

    Breyta lit ljósa

    • Gerðu lit ljóssins í eldhúsinu grænt.
    • @@ -33,7 +33,7 @@

      Breyta lit ljósa

    • Breyttu leslampanum í rauðan lit.
    -

    Breyta birtustigi

    +

    Breyta birtustigi

    • Gerðu baðherbergið dimmara.
    • @@ -41,14 +41,14 @@

      Breyta birtustigi

    -

    Breyta hitastigi (e. temperature)

    +

    Breyta hitastigi (e. temperature)

    • Gerðu birtuna í eldhúsinu hlýrri.
    • Gerðu birtuna á leslampanum kaldari.
    -

    Senur (e. scenes)

    +

    Senur (e. scenes)

    • Settu á stemninguna Rómó í svefnherberginu.
    • diff --git a/templates/sonos-instructions.html b/templates/sonos-instructions.html index e4bcef87..326dfd9b 100644 --- a/templates/sonos-instructions.html +++ b/templates/sonos-instructions.html @@ -3,26 +3,26 @@ {% block content %}
      -

      Fyrirspurnaleiðbeiningar

      +

      Fyrirspurnaleiðbeiningar

      Embla ræður eins og stendur við eftirfarandi tegundir fyrirspurna fyrir {{ iot_name }}. Þetta er ekki tæmandi listi, þar sem hún skilur einnig ýmis tilbrigði við þessar spurningar.

      -

      Kveikja og slökkva

      +

      Kveikja og slökkva

      • Kveiktu á tónlist.
      • Slökktu á tónlistinni.
      -

      Útvarp

      +

      Útvarp

      • Stilltu á Rondó.
      • Settu á Bylgjuna.
      -

      Breyta hljóðstyrk

      +

      Breyta hljóðstyrk

      • Lækkaðu í tónlistinni.
      • diff --git a/templates/spotify-instructions.html b/templates/spotify-instructions.html index 1855c9d6..438b26d6 100644 --- a/templates/spotify-instructions.html +++ b/templates/spotify-instructions.html @@ -3,12 +3,12 @@ {% block content %}
        -

        Fyrirspurnaleiðbeiningar

        +

        Fyrirspurnaleiðbeiningar

        Embla ræður eins og stendur við eftirfarandi tegundir fyrirspurna fyrir {{ iot_name }}. Þetta er ekki tæmandi listi, þar sem hún skilur einnig ýmis tilbrigði við þessar spurningar.

        -

        Eina skipunin sem þú munt nokkurn tímann þurfa

        +

        Eina skipunin sem þú munt nokkurn tímann þurfa

        • Spilaðu Þorparann með Pálma Gunnarssyni.
        • From e80a68c29c29a89eed027869a94d91009effb01d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Thu, 11 Aug 2022 15:24:04 +0000 Subject: [PATCH 311/371] HTML now uses toml variables to display info about connections --- resources/iot_supported.toml | 4 +-- routes/main.py | 43 ++++++++++++++++++++--------- templates/hue-instructions.html | 2 +- templates/iot-info.html | 35 +++++++++++------------ templates/sonos-instructions.html | 4 +-- templates/spotify-instructions.html | 6 ++-- 6 files changed, 54 insertions(+), 40 deletions(-) diff --git a/resources/iot_supported.toml b/resources/iot_supported.toml index d1b0d1f2..e2c534cd 100644 --- a/resources/iot_supported.toml +++ b/resources/iot_supported.toml @@ -19,7 +19,7 @@ db_name = "sonos" name = "Sonos" brand = "Sonos Inc." group = "iot_speakers" -logp = "sonos.png" +logo = "sonos.png" # speaker_outlined icon = 0xf3c1 webview_home = '{host}/iot/sonos-instructions?client_id={client_id}&iot_group=iot_speakers&iot_name=sonos' @@ -36,5 +36,5 @@ group = "iot_streaming" logo = "spotify.png" #music_note_outlined icon = 0xf1fb -webview_home = '{host}/iot/hue-instructions?client_id={client_id}&iot_group=iot_lights&iot_name=philips_hue' +webview_home = '{host}/iot/spotify-instructions?client_id={client_id}&iot_group=iot_streaming&iot_name=spotify' webview_connect = '{host}/iot/hue-connection?client_id={client_id}&request_url={host}' diff --git a/routes/main.py b/routes/main.py index 58d03bc2..f255c3f6 100755 --- a/routes/main.py +++ b/routes/main.py @@ -21,14 +21,20 @@ """ -from typing import Dict, Any, List, Optional, Sequence, Tuple, Union, cast +from typing import Dict, Any, List, Optional, Sequence, Tuple, TypedDict, Union, cast import platform +import os.path import sys import random import json from datetime import datetime +try: + import tomllib # type: ignore (module not available in Python <3.11) +except ModuleNotFoundError: + import tomli as tomllib # Used for Python <3.11 + from flask import render_template, request, redirect, url_for from werkzeug.wrappers import Response @@ -96,22 +102,33 @@ def analysis(): return render_template("analysis.html", title="Málgreining", default_text=txt) +class IotSupportedTOMLStructure(TypedDict): + """Structure of the iot_supported TOML file.""" + + connections: Dict[str, Dict[str, str]] + + @routes.route("/iot/") @max_age(seconds=60) def iot(device: str): """Handler for device connection views.""" - device_variables: Dict[str, Dict[str, Any]] = { - "hue-instructions": { - "iot_name": "Philips Hue", - "iot_description": "Hue er ein útlægir ljósæki sem er hægt að nota til að styrka ljósæki á Íslandi.", - }, - "sonos-instructions": { - "iot_name": "Sonos", - "iot_description": "Sonos er hátalari.", - }, - } - print("iot", device) - return render_template(f"{str(device)}.html", **device_variables.get(device, {})) + args = request.args + iot_name: str = args.get("iot_name") + basepath, _ = os.path.split(os.path.realpath(__file__)) + fpath = os.path.join(basepath, "../resources/iot_supported.toml") + print("fpath: ", fpath) + with open(fpath, mode="r") as file: + f = file.read() + # Read TOML file containing a list of resources for the dialogue + obj: IotSupportedTOMLStructure = tomllib.loads(f) # type: ignore + print("TOML: ", obj) + if obj: + # for (_, connection) in obj["connections"].items(): + print("Connection: ", obj["connections"]) + connection_info = obj["connections"][iot_name] + print("Display name: ", connection_info) + return render_template(f"{str(device)}.html", **connection_info) + # device_variables.get(device, {})) @routes.route("/correct", methods=["GET", "POST"]) diff --git a/templates/hue-instructions.html b/templates/hue-instructions.html index a09b07cd..2f38634d 100644 --- a/templates/hue-instructions.html +++ b/templates/hue-instructions.html @@ -5,7 +5,7 @@

          Fyrirspurnaleiðbeiningar

          -

          Embla ræður eins og stendur við eftirfarandi tegundir fyrirspurna fyrir {{ iot_name }}. +

          Embla ræður eins og stendur við eftirfarandi tegundir fyrirspurna fyrir {{ display_name }}. Þetta er ekki tæmandi listi, þar sem hún skilur einnig ýmis tilbrigði við þessar spurningar.

          Kveikja og slökkva

          diff --git a/templates/iot-info.html b/templates/iot-info.html index 7615f56f..8f9b8b1d 100644 --- a/templates/iot-info.html +++ b/templates/iot-info.html @@ -22,13 +22,10 @@
          -

          {{ iot_name }}

          +

          {{ display_name }}

          Upplýsingar

          -

          {{ iot_description }}

          - -
          @@ -70,21 +67,21 @@

          Upplýsingar

          "iotName": iotName, "iotGroup": iotGroup }); - return; - //TODO: Delete iot info from database - fetch(`http://192.168.1.76:5000/delete_iot_data.api?client_id=${clientId}&iot_group=${iotGroup}&iot_name=${iotName}`, { - method: "DELETE", - headers: { - "Content-Type": "application/json", - }, - }) - .then((resp) => resp.json()) - .then((obj) => { - return obj; - }) - .catch((err) => { - console.log("Error while deleting info from database: " + err); - }); + // return; + // //TODO: Delete iot info from database + // fetch(`http://192.168.1.76:5000/delete_iot_data.api?client_id=${clientId}&iot_group=${iotGroup}&iot_name=${iotName}`, { + // method: "DELETE", + // headers: { + // "Content-Type": "application/json", + // }, + // }) + // .then((resp) => resp.json()) + // .then((obj) => { + // return obj; + // }) + // .catch((err) => { + // console.log("Error while deleting info from database: " + err); + // }); } diff --git a/templates/sonos-instructions.html b/templates/sonos-instructions.html index e4bcef87..48570f96 100644 --- a/templates/sonos-instructions.html +++ b/templates/sonos-instructions.html @@ -5,7 +5,7 @@

          Fyrirspurnaleiðbeiningar

          -

          Embla ræður eins og stendur við eftirfarandi tegundir fyrirspurna fyrir {{ iot_name }}. +

          Embla ræður eins og stendur við eftirfarandi tegundir fyrirspurna fyrir {{ display_name }}. Þetta er ekki tæmandi listi, þar sem hún skilur einnig ýmis tilbrigði við þessar spurningar.

          Kveikja og slökkva

          @@ -29,4 +29,4 @@

          Breyta hljóðstyrk

        • Hækkaðu í útvarpinu.
        -{% endblock %} \ No newline at end of file + {% endblock %} \ No newline at end of file diff --git a/templates/spotify-instructions.html b/templates/spotify-instructions.html index 1855c9d6..95be67f7 100644 --- a/templates/spotify-instructions.html +++ b/templates/spotify-instructions.html @@ -5,7 +5,7 @@

        Fyrirspurnaleiðbeiningar

        -

        Embla ræður eins og stendur við eftirfarandi tegundir fyrirspurna fyrir {{ iot_name }}. +

        Embla ræður eins og stendur við eftirfarandi tegundir fyrirspurna fyrir {{ display_name }}. Þetta er ekki tæmandi listi, þar sem hún skilur einnig ýmis tilbrigði við þessar spurningar.

        Eina skipunin sem þú munt nokkurn tímann þurfa

        @@ -13,5 +13,5 @@

        Eina skipunin sem þú munt nokkurn tímann þurfa

        • Spilaðu Þorparann með Pálma Gunnarssyni.
        - -{% endblock %} \ No newline at end of file + + {% endblock %} \ No newline at end of file From 1f1a98e918333534d6ba49d77f886b34db119c1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Fri, 12 Aug 2022 11:32:14 +0000 Subject: [PATCH 312/371] Added spotify connection and api key to toml --- resources/iot_supported.toml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/resources/iot_supported.toml b/resources/iot_supported.toml index e2c534cd..3549f0a5 100644 --- a/resources/iot_supported.toml +++ b/resources/iot_supported.toml @@ -6,7 +6,7 @@ db_name = "philips_hue" name = "Hue Hub" brand = "Philips" group = "iot_lights" -logo = "philips.png" +logo = "https://upload.wikimedia.org/wikipedia/en/a/a1/Philips_hue_logo.png" # lightbulb_outline_rounded icon = 0xf854 webview_home = '{host}/iot/hue-instructions?client_id={client_id}&iot_group=iot_lights&iot_name=philips_hue' @@ -19,7 +19,7 @@ db_name = "sonos" name = "Sonos" brand = "Sonos Inc." group = "iot_speakers" -logo = "sonos.png" +logo = "https://upload.wikimedia.org/wikipedia/commons/thumb/4/4b/Sonos-company.png/800px-Sonos-company.png" # speaker_outlined icon = 0xf3c1 webview_home = '{host}/iot/sonos-instructions?client_id={client_id}&iot_group=iot_speakers&iot_name=sonos' @@ -33,8 +33,9 @@ db_name = "spotify" name = "Spotify" brand = "Spotify" group = "iot_streaming" -logo = "spotify.png" +logo = "https://upload.wikimedia.org/wikipedia/commons/thumb/1/19/Spotify_logo_without_text.svg/168px-Spotify_logo_without_text.svg.png" #music_note_outlined icon = 0xf1fb webview_home = '{host}/iot/spotify-instructions?client_id={client_id}&iot_group=iot_streaming&iot_name=spotify' -webview_connect = '{host}/iot/hue-connection?client_id={client_id}&request_url={host}' +webview_connect = 'https://accounts.spotify.com/authorize?client_id={api_key}&response_type=code&redirect_uri={host}/connect_spotify.api&state={client_id}&scope=user-read-playback-state+user-modify-playback-state+user-read-playback-position+user-read-recently-played+app-remote-control+user-top-read+user-read-currently-playing+playlist-read-private+streaming' +api_key_filename = "SpotifyKey" From cfeed9e90cdcfd0096692b38ca9827af6017de9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Fri, 12 Aug 2022 11:33:06 +0000 Subject: [PATCH 313/371] Added handling for if there is no iot data --- routes/main.py | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/routes/main.py b/routes/main.py index f255c3f6..ced70814 100755 --- a/routes/main.py +++ b/routes/main.py @@ -114,19 +114,21 @@ def iot(device: str): """Handler for device connection views.""" args = request.args iot_name: str = args.get("iot_name") - basepath, _ = os.path.split(os.path.realpath(__file__)) - fpath = os.path.join(basepath, "../resources/iot_supported.toml") - print("fpath: ", fpath) - with open(fpath, mode="r") as file: - f = file.read() - # Read TOML file containing a list of resources for the dialogue - obj: IotSupportedTOMLStructure = tomllib.loads(f) # type: ignore - print("TOML: ", obj) - if obj: - # for (_, connection) in obj["connections"].items(): - print("Connection: ", obj["connections"]) - connection_info = obj["connections"][iot_name] - print("Display name: ", connection_info) + connection_info = {} + if iot_name: + basepath, _ = os.path.split(os.path.realpath(__file__)) + fpath = os.path.join(basepath, "../resources/iot_supported.toml") + print("fpath: ", fpath) + with open(fpath, mode="r") as file: + f = file.read() + # Read TOML file containing a list of resources for the dialogue + obj: IotSupportedTOMLStructure = tomllib.loads(f) # type: ignore + print("TOML: ", obj) + if obj: + # for (_, connection) in obj["connections"].items(): + print("Connection: ", obj["connections"]) + connection_info = obj["connections"][iot_name] + print("Display name: ", connection_info) return render_template(f"{str(device)}.html", **connection_info) # device_variables.get(device, {})) From 42fd012406648e000a2db0dc16c08c38b7fe1e4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Fri, 12 Aug 2022 11:33:34 +0000 Subject: [PATCH 314/371] Added styling for h5 and h2-subsection --- static/css/style.css | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/static/css/style.css b/static/css/style.css index 0128660d..532d177a 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -53,6 +53,14 @@ h4 { text-align: center; } +h5 { + text-align: center; +} + +.h2-subsection { + font-size: 22px; +} + .content { padding-left: 15px; padding-right: 15px; From 9d447fc588a83b18a6acc83844b2aed923d84dca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Fri, 12 Aug 2022 11:34:39 +0000 Subject: [PATCH 315/371] Added more styling --- templates/hue-connection.html | 2 +- templates/hue-instructions.html | 14 +++++++------- templates/iot-info.html | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/templates/hue-connection.html b/templates/hue-connection.html index e640dcbd..8495b5dd 100644 --- a/templates/hue-connection.html +++ b/templates/hue-connection.html @@ -20,7 +20,7 @@ flex-direction: column; justify-content: center; " class="content"> -

        Leiðbeiningar

        +

        Leiðbeiningar

        1. Tengdu Philips Hue miðstöðina með netsnúru.
        2. Gættu þess að miðstöðin sé tengd sama neti og síminn þinn.
        3. diff --git a/templates/hue-instructions.html b/templates/hue-instructions.html index 9f47e9b2..3056fd3a 100644 --- a/templates/hue-instructions.html +++ b/templates/hue-instructions.html @@ -3,12 +3,12 @@ {% block content %}
          -

          Fyrirspurnaleiðbeiningar

          +

          Fyrirspurnaleiðbeiningar

          Embla ræður eins og stendur við eftirfarandi tegundir fyrirspurna fyrir {{ display_name }}. Þetta er ekki tæmandi listi, þar sem hún skilur einnig ýmis tilbrigði við þessar spurningar.

          -

          Kveikja og slökkva

          +
          Kveikja og slökkva
          • Kveiktu ljósin á skrifstofunni.
          • @@ -16,7 +16,7 @@

            Kveikja og slökkva

          • Kveiktu ljósin frammi í eldhúsi.
          -

          Hækka og lækka birtu

          +
          Hækka og lækka birtu
          • Hækkaðu birtuna í stofunni.
          • @@ -25,7 +25,7 @@

            Hækka og lækka birtu

          • Láttu birtuna í eldhúsinu vera minni.
          -

          Breyta lit ljósa

          +
          Breyta lit ljósa
          • Gerðu lit ljóssins í eldhúsinu grænt.
          • @@ -33,7 +33,7 @@

            Breyta lit ljósa

          • Breyttu leslampanum í rauðan lit.
          -

          Breyta birtustigi

          +
          Breyta birtustigi
          • Gerðu baðherbergið dimmara.
          • @@ -41,14 +41,14 @@

            Breyta birtustigi

          -

          Breyta hitastigi (e. temperature)

          +
          Breyta hitastigi (e. temperature)
          • Gerðu birtuna í eldhúsinu hlýrri.
          • Gerðu birtuna á leslampanum kaldari.
          -

          Senur (e. scenes)

          +
          Senur (e. scenes)
          • Settu á stemninguna Rómó í svefnherberginu.
          • diff --git a/templates/iot-info.html b/templates/iot-info.html index 8f9b8b1d..8ce3de22 100644 --- a/templates/iot-info.html +++ b/templates/iot-info.html @@ -24,7 +24,7 @@

            {{ display_name }}

            -

            Upplýsingar

            +
          From 37f353ddd7ee4c24cb1b184a67c671550b22148d Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Fri, 12 Aug 2022 11:37:24 +0000 Subject: [PATCH 316/371] Html connection fluff text --- templates/hue-connection.html | 10 ++++++ templates/iot-info.html | 2 ++ templates/sonos-connection.html | 56 +++++++++++++++++++++++++++++++ templates/spotify-connection.html | 54 +++++++++++++++++++++++++++++ 4 files changed, 122 insertions(+) create mode 100644 templates/sonos-connection.html create mode 100644 templates/spotify-connection.html diff --git a/templates/hue-connection.html b/templates/hue-connection.html index e640dcbd..9b702162 100644 --- a/templates/hue-connection.html +++ b/templates/hue-connection.html @@ -15,11 +15,20 @@ +

          Philips Hue Hub

          +
          " +
          +

          Philips Hue er vinsælasta snjallljósa lausn heims. + Með því að tengja Philips Hue miðstöðina þína getur þú beðið emblu um að kveikja og slökka á ljósum, breyta lit, birtustigi og fleira.

          Leiðbeiningar

          1. Tengdu Philips Hue miðstöðina með netsnúru.
          2. @@ -37,6 +46,7 @@

            Leiðbeiningar

            text-decoration: none; display: inline-block; border-radius: 15px; + box-shadow: 0px 5px 10px 0px #000000; font-size: 16px;" onclick="syncConnectHub();">Tengja
          diff --git a/templates/iot-info.html b/templates/iot-info.html index 8f9b8b1d..d1f93001 100644 --- a/templates/iot-info.html +++ b/templates/iot-info.html @@ -42,6 +42,8 @@

          Upplýsingar

          text-decoration: none; display: inline-block; border-radius: 15px; + font-family: 'Lato'; + box-shadow: 0px 5px 10px 0px #000000; font-size: 16px;">Aftengja
        diff --git a/templates/sonos-connection.html b/templates/sonos-connection.html new file mode 100644 index 00000000..06767b18 --- /dev/null +++ b/templates/sonos-connection.html @@ -0,0 +1,56 @@ + + + + + + + + + + Philips Hue + + + + + + + +

        Sonos

        +
        " +
        +
        +

        Hinir geysivinsælu Sonos snjallhálarar eru studdir af radadstýringu Emblu.
        + Með því að tengja Sonos hátalara getur þú beðið Emblu um að kveikja og slökkva á tónlist, setja á útvarpstöðvar og stjórna hljóðstyrk.

        +

        Leiðbeiningar

        +
          +
        1. Náðu í Sonos appið og tengdu hátalarinn þinn við þráðlausa netið á heimilinu.
        2. +
        3. Gættu þess að hátalarinn og síminn þinn séu tengdir sama neti.
        4. +
        5. Veldu "Tengja" hér að neðan og skráðu þig inn með Sonos aðgangi þínum.
        6. +
        + + + +
        + + + + + \ No newline at end of file diff --git a/templates/spotify-connection.html b/templates/spotify-connection.html new file mode 100644 index 00000000..7bac3c52 --- /dev/null +++ b/templates/spotify-connection.html @@ -0,0 +1,54 @@ + + + + + + + + + + Philips Hue + + + + + + + +

        Sonos

        +
        " +
        +
        +

        Spotify er vinsælasta tónlistarveita heims.
        + Með því að tengja Spotify aðgang þinn getur þú beðið Emblu um að spila lögin sem þú vilt heyra. +

        Leiðbeiningar

        +
          +
        1. Veldu "Tengja" hér að neðan og skráðu þig inn með Spotify aðgang þínum
        2. +
        + + + +
        + + + + + \ No newline at end of file From b134321606e447f93735edfb4ae05799472cc3be Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Fri, 12 Aug 2022 11:42:47 +0000 Subject: [PATCH 317/371] html class name fix --- templates/sonos-connection.html | 2 +- templates/spotify-connection.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/sonos-connection.html b/templates/sonos-connection.html index 06767b18..e7b65707 100644 --- a/templates/sonos-connection.html +++ b/templates/sonos-connection.html @@ -29,7 +29,7 @@

        Sonos

        " class="content">

        Hinir geysivinsælu Sonos snjallhálarar eru studdir af radadstýringu Emblu.
        Með því að tengja Sonos hátalara getur þú beðið Emblu um að kveikja og slökkva á tónlist, setja á útvarpstöðvar og stjórna hljóðstyrk.

        -

        Leiðbeiningar

        +

        Leiðbeiningar

        1. Náðu í Sonos appið og tengdu hátalarinn þinn við þráðlausa netið á heimilinu.
        2. Gættu þess að hátalarinn og síminn þinn séu tengdir sama neti.
        3. diff --git a/templates/spotify-connection.html b/templates/spotify-connection.html index 7bac3c52..fa7e706c 100644 --- a/templates/spotify-connection.html +++ b/templates/spotify-connection.html @@ -29,7 +29,7 @@

          Sonos

          " class="content">

          Spotify er vinsælasta tónlistarveita heims.
          Með því að tengja Spotify aðgang þinn getur þú beðið Emblu um að spila lögin sem þú vilt heyra. -

          Leiðbeiningar

          +

          Leiðbeiningar

          1. Veldu "Tengja" hér að neðan og skráðu þig inn með Spotify aðgang þínum
          From 39a370b89b7fd403eff3f9c116669fa9f8692c66 Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Fri, 12 Aug 2022 12:15:53 +0000 Subject: [PATCH 318/371] HTML drop shadow and " fix --- templates/hue-connection.html | 4 ++-- templates/sonos-connection.html | 4 ++-- templates/spotify-connection.html | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/templates/hue-connection.html b/templates/hue-connection.html index c327d757..0e1e6d15 100644 --- a/templates/hue-connection.html +++ b/templates/hue-connection.html @@ -20,7 +20,7 @@

          Philips Hue Hub

          display: flex; flex-direction: column; justify-content: center; - " class="content">" + " class="content">
        Leiðbeiningar text-decoration: none; display: inline-block; border-radius: 15px; - box-shadow: 0px 5px 10px 0px #000000; + box-shadow: 0px 4px 5px -2px rgba(0,0,0,0.8); font-size: 16px;" onclick="syncConnectHub();">Tengja
        diff --git a/templates/sonos-connection.html b/templates/sonos-connection.html index e7b65707..7880f898 100644 --- a/templates/sonos-connection.html +++ b/templates/sonos-connection.html @@ -20,7 +20,7 @@

        Sonos

        display: flex; flex-direction: column; justify-content: center; - " class="content">" + " class="content">
      Leiðbeiningar text-decoration: none; display: inline-block; border-radius: 15px; - box-shadow: 0px 5px 10px 0px #000000; + box-shadow: 0px 4px 5px -2px rgba(0,0,0,0.8); font-size: 16px;" onclick="syncConnectHub();">Tengja
      diff --git a/templates/spotify-connection.html b/templates/spotify-connection.html index fa7e706c..e0a78137 100644 --- a/templates/spotify-connection.html +++ b/templates/spotify-connection.html @@ -20,7 +20,7 @@

      Sonos

      display: flex; flex-direction: column; justify-content: center; - " class="content">" + " class="content">
    Leiðbeiningar text-decoration: none; display: inline-block; border-radius: 15px; - box-shadow: 0px 5px 10px 0px #000000; + box-shadow: 0px 4px 5px -2px rgba(0,0,0,0.8); font-size: 16px;" onclick="syncConnectHub();">Tengja
    From 0ce5db21dd98f9219dd486f8881850a5eb025cf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Fri, 12 Aug 2022 12:24:17 +0000 Subject: [PATCH 319/371] Connection html now redirect to login pages for sonos and spotify --- resources/iot_supported.toml | 6 ++++-- routes/api.py | 20 ++++++++++++-------- templates/sonos-connection.html | 14 +++++++++++++- templates/spotify-connection.html | 11 ++++++++++- 4 files changed, 39 insertions(+), 12 deletions(-) diff --git a/resources/iot_supported.toml b/resources/iot_supported.toml index 3549f0a5..fc90a9c6 100644 --- a/resources/iot_supported.toml +++ b/resources/iot_supported.toml @@ -23,7 +23,8 @@ logo = "https://upload.wikimedia.org/wikipedia/commons/thumb/4/4b/Sonos-company. # speaker_outlined icon = 0xf3c1 webview_home = '{host}/iot/sonos-instructions?client_id={client_id}&iot_group=iot_speakers&iot_name=sonos' -webview_connect = 'https://api.sonos.com/login/v3/oauth?client_id={api_key}&response_type=code&state={client_id}&scope=playback-control-all&redirect_uri={host}/connect_sonos.api' +webview_connect = '{host}/iot/sonos-connection?client_id={client_id}&request_url={host}' +connect_url = 'https://api.sonos.com/login/v3/oauth?client_id={api_key}&response_type=code&state={client_id}&scope=playback-control-all&redirect_uri={host}/connect_sonos.api' api_key_filename = "SonosKey" @@ -37,5 +38,6 @@ logo = "https://upload.wikimedia.org/wikipedia/commons/thumb/1/19/Spotify_logo_w #music_note_outlined icon = 0xf1fb webview_home = '{host}/iot/spotify-instructions?client_id={client_id}&iot_group=iot_streaming&iot_name=spotify' -webview_connect = 'https://accounts.spotify.com/authorize?client_id={api_key}&response_type=code&redirect_uri={host}/connect_spotify.api&state={client_id}&scope=user-read-playback-state+user-modify-playback-state+user-read-playback-position+user-read-recently-played+app-remote-control+user-top-read+user-read-currently-playing+playlist-read-private+streaming' +webview_connect = '{host}/iot/spotify-connection?client_id={client_id}&request_url={host}' +connect_url = 'https://accounts.spotify.com/authorize?client_id={api_key}&response_type=code&redirect_uri={host}/connect_spotify.api&state={client_id}&scope=user-read-playback-state+user-modify-playback-state+user-read-playback-position+user-read-recently-played+app-remote-control+user-top-read+user-read-currently-playing+playlist-read-private+streaming' api_key_filename = "SpotifyKey" diff --git a/routes/api.py b/routes/api.py index 036ac37d..23559627 100755 --- a/routes/api.py +++ b/routes/api.py @@ -903,15 +903,19 @@ def get_supported_iot_connections(version: int = 1) -> Response: webview_home = webview_home.format(host=host, client_id=client_id) connection.update({"webview_home": webview_home}) webview_connect = connection["webview_connect"] - if "api_key_filename" in connection: - api_key_filename: str = connection["api_key_filename"] - api_key = read_api_key(api_key_filename) - webview_connect = webview_connect.format( - host=host, client_id=client_id, api_key=api_key - ) - else: - webview_connect = webview_connect.format(host=host, client_id=client_id) + webview_connect = webview_connect.format(host=host, client_id=client_id) connection.update({"webview_connect": webview_connect}) + if "connect_url" in connection: + connect_url = connection["connect_url"] + if "api_key_filename" in connection: + api_key_filename: str = connection["api_key_filename"] + api_key = read_api_key(api_key_filename) + connect_url = connect_url.format( + host=host, client_id=client_id, api_key=api_key + ) + else: + connect_url = connect_url.format(host=host, client_id=client_id) + connection.update({"connect_url": connect_url}) print("Connection: ", connection) json = better_jsonify(valid=True, data=obj) return json diff --git a/templates/sonos-connection.html b/templates/sonos-connection.html index e7b65707..b31e71e6 100644 --- a/templates/sonos-connection.html +++ b/templates/sonos-connection.html @@ -28,7 +28,8 @@

    Sonos

    justify-content: center; " class="content">

    Hinir geysivinsælu Sonos snjallhálarar eru studdir af radadstýringu Emblu.
    - Með því að tengja Sonos hátalara getur þú beðið Emblu um að kveikja og slökkva á tónlist, setja á útvarpstöðvar og stjórna hljóðstyrk.

    + Með því að tengja Sonos hátalara getur þú beðið Emblu um að kveikja og slökkva á tónlist, setja á + útvarpstöðvar og stjórna hljóðstyrk.

    Leiðbeiningar

    1. Náðu í Sonos appið og tengdu hátalarinn þinn við þráðlausa netið á heimilinu.
    2. @@ -50,6 +51,17 @@

      Leiðbeiningar

      + + diff --git a/templates/spotify-connection.html b/templates/spotify-connection.html index fa7e706c..745f74ba 100644 --- a/templates/spotify-connection.html +++ b/templates/spotify-connection.html @@ -44,10 +44,19 @@

      Leiðbeiningar

      display: inline-block; border-radius: 15px; box-shadow: 0px 5px 10px 0px #000000; - font-size: 16px;" onclick="syncConnectHub();">Tengja + font-size: 16px;">Tengja + + From bb64aad0120b348cafc20a99de92f3f74b0a222e Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Fri, 12 Aug 2022 14:26:01 +0000 Subject: [PATCH 320/371] "aftengja" button drop shadow fix --- templates/iot-info.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/iot-info.html b/templates/iot-info.html index 2b20aec2..514ffe86 100644 --- a/templates/iot-info.html +++ b/templates/iot-info.html @@ -43,7 +43,7 @@

      {{ display_name }}

      display: inline-block; border-radius: 15px; font-family: 'Lato'; - box-shadow: 0px 5px 10px 0px #000000; + box-shadow: 0px 4px 5px -2px rgba(0,0,0,0.8); font-size: 16px;">Aftengja From d06645133e1039ccf1f86a0e9f47191f12e89ff8 Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Fri, 12 Aug 2022 15:05:27 +0000 Subject: [PATCH 321/371] Html connection button style update --- templates/hue-connection.html | 5 +++-- templates/iot-info.html | 6 +++--- templates/sonos-connection.html | 5 +++-- templates/spotify-connection.html | 5 +++-- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/templates/hue-connection.html b/templates/hue-connection.html index 0e1e6d15..ef91f74b 100644 --- a/templates/hue-connection.html +++ b/templates/hue-connection.html @@ -46,8 +46,9 @@

      Leiðbeiningar

      text-align: center; text-decoration: none; display: inline-block; - border-radius: 15px; - box-shadow: 0px 4px 5px -2px rgba(0,0,0,0.8); + border-radius: 25px; + font-family: 'Lato', sans-serif; + box-shadow: 0px 4px 5px -2px rgba(0,0,0,0.8), inset 0px 4px 5px -2px rgba(255,255,255,0.8);; font-size: 16px;" onclick="syncConnectHub();">Tengja diff --git a/templates/iot-info.html b/templates/iot-info.html index 514ffe86..53d73949 100644 --- a/templates/iot-info.html +++ b/templates/iot-info.html @@ -41,9 +41,9 @@

      {{ display_name }}

      text-align: center; text-decoration: none; display: inline-block; - border-radius: 15px; - font-family: 'Lato'; - box-shadow: 0px 4px 5px -2px rgba(0,0,0,0.8); + border-radius: 25px; + font-family: 'Lato', sans-serif; + box-shadow: 0px 4px 5px -2px rgba(0,0,0,0.8), inset 0px 4px 5px -2px rgba(255,255,255,0.8);; font-size: 16px;">Aftengja diff --git a/templates/sonos-connection.html b/templates/sonos-connection.html index 8648112a..44e14f32 100644 --- a/templates/sonos-connection.html +++ b/templates/sonos-connection.html @@ -45,8 +45,9 @@

      Leiðbeiningar

      text-align: center; text-decoration: none; display: inline-block; - border-radius: 15px; - box-shadow: 0px 4px 5px -2px rgba(0,0,0,0.8); + border-radius: 25px; + font-family: 'Lato', sans-serif; + box-shadow: 0px 4px 5px -2px rgba(0,0,0,0.8), inset 0px 4px 5px -2px rgba(255,255,255,0.8);; font-size: 16px;" onclick="syncConnectHub();">Tengja diff --git a/templates/spotify-connection.html b/templates/spotify-connection.html index 2a9adf38..03506c2b 100644 --- a/templates/spotify-connection.html +++ b/templates/spotify-connection.html @@ -42,8 +42,9 @@

      Leiðbeiningar

      text-align: center; text-decoration: none; display: inline-block; - border-radius: 15px; - box-shadow: 0px 4px 5px -2px rgba(0,0,0,0.8); + border-radius: 25px; + font-family: 'Lato', sans-serif; + box-shadow: 0px 4px 5px -2px rgba(0,0,0,0.8), inset 0px 4px 5px -2px rgba(255,255,255,0.8);; font-size: 16px;">Tengja From 946b06a03aa777cee538450dd8368bd08134e506 Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Fri, 12 Aug 2022 15:26:32 +0000 Subject: [PATCH 322/371] HTML button styling --- templates/hue-connection.html | 5 +++-- templates/iot-info.html | 3 ++- templates/sonos-connection.html | 5 +++-- templates/spotify-connection.html | 5 +++-- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/templates/hue-connection.html b/templates/hue-connection.html index ef91f74b..ab87fcaf 100644 --- a/templates/hue-connection.html +++ b/templates/hue-connection.html @@ -47,8 +47,9 @@

      Leiðbeiningar

      text-decoration: none; display: inline-block; border-radius: 25px; - font-family: 'Lato', sans-serif; - box-shadow: 0px 4px 5px -2px rgba(0,0,0,0.8), inset 0px 4px 5px -2px rgba(255,255,255,0.8);; + font-family: 'Lato', sans-serif; + padding: 10px 20px; + box-shadow: 0px 2px 5px -1px rgba(0,0,0,0.3), inset 0px 12px 0px -10px rgba(255,255,255,0.3); font-size: 16px;" onclick="syncConnectHub();">Tengja diff --git a/templates/iot-info.html b/templates/iot-info.html index 53d73949..b30ae151 100644 --- a/templates/iot-info.html +++ b/templates/iot-info.html @@ -43,7 +43,8 @@

      {{ display_name }}

      display: inline-block; border-radius: 25px; font-family: 'Lato', sans-serif; - box-shadow: 0px 4px 5px -2px rgba(0,0,0,0.8), inset 0px 4px 5px -2px rgba(255,255,255,0.8);; + padding: 10px 20px; + box-shadow: 0px 2px 5px -1px rgba(0,0,0,0.3), inset 0px 12px 0px -10px rgba(255,255,255,0.3); font-size: 16px;">Aftengja diff --git a/templates/sonos-connection.html b/templates/sonos-connection.html index 44e14f32..3c1441dd 100644 --- a/templates/sonos-connection.html +++ b/templates/sonos-connection.html @@ -46,8 +46,9 @@

      Leiðbeiningar

      text-decoration: none; display: inline-block; border-radius: 25px; - font-family: 'Lato', sans-serif; - box-shadow: 0px 4px 5px -2px rgba(0,0,0,0.8), inset 0px 4px 5px -2px rgba(255,255,255,0.8);; + font-family: 'Lato', sans-serif; + padding: 10px 20px; + box-shadow: 0px 2px 5px -1px rgba(0,0,0,0.3), inset 0px 12px 0px -10px rgba(255,255,255,0.3); font-size: 16px;" onclick="syncConnectHub();">Tengja diff --git a/templates/spotify-connection.html b/templates/spotify-connection.html index 03506c2b..b0180328 100644 --- a/templates/spotify-connection.html +++ b/templates/spotify-connection.html @@ -43,8 +43,9 @@

      Leiðbeiningar

      text-decoration: none; display: inline-block; border-radius: 25px; - font-family: 'Lato', sans-serif; - box-shadow: 0px 4px 5px -2px rgba(0,0,0,0.8), inset 0px 4px 5px -2px rgba(255,255,255,0.8);; + font-family: 'Lato', sans-serif; + padding: 10px 20px; + box-shadow: 0px 2px 5px -1px rgba(0,0,0,0.3), inset 0px 12px 0px -10px rgba(255,255,255,0.3); font-size: 16px;">Tengja From 48f28efa44a30e11993ce58d7176a975b5a3ab18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Fri, 12 Aug 2022 15:31:45 +0000 Subject: [PATCH 323/371] Iot connection success html --- routes/api.py | 3 +- templates/iot-connect-success.html | 54 ++++++++++++++++++++++++++++++ templates/spotify-connection.html | 2 +- 3 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 templates/iot-connect-success.html diff --git a/routes/api.py b/routes/api.py index 23559627..e9c2e2fb 100755 --- a/routes/api.py +++ b/routes/api.py @@ -34,7 +34,7 @@ except ModuleNotFoundError: import tomli as tomllib # Used for Python <3.11 -from flask import request, abort +from flask import render_template, request, abort from flask.wrappers import Response from settings import Settings @@ -811,6 +811,7 @@ def spotify_code(version: int = 1) -> Response: device_data = code_dict.get("iot_streaming").get("spotify") spotify_client = SpotifyClient(device_data, client_id) # Create an instance of the SonosClient class. This will automatically create the rest of the credentials needed. + return render_template("iot-connect-success.html", title="Tenging tókst") return better_jsonify(valid=True, msg="Registered spotify code") return better_jsonify(valid=False, errmsg="Error registering spotify code.") diff --git a/templates/iot-connect-success.html b/templates/iot-connect-success.html new file mode 100644 index 00000000..95e6e7b1 --- /dev/null +++ b/templates/iot-connect-success.html @@ -0,0 +1,54 @@ + + + + + + + + + + Tenging tókst + + + + + + + +
      + +

      Tenging tókst

      + + + +
      + + + + + + + \ No newline at end of file diff --git a/templates/spotify-connection.html b/templates/spotify-connection.html index 03506c2b..5a70b301 100644 --- a/templates/spotify-connection.html +++ b/templates/spotify-connection.html @@ -15,7 +15,7 @@ -

      Sonos

      +

      Spotify

      Response: smartthings_client = SmartThingsClient(device_data, client_id) # device_data = code # Create an instance of the SonosClient class. This will automatically create the rest of the credentials needed. + return render_template("iot-connect-success.html", title="Tenging tókst") return better_jsonify(valid=True, msg="Registered smartthings code") return better_jsonify(valid=False, errmsg="Error registering smartthings code.") diff --git a/static/img/checkmark.gif b/static/img/checkmark.gif new file mode 100644 index 0000000000000000000000000000000000000000..96bc147360f3be06cfcad77c1280ff405f16a51c GIT binary patch literal 58873 zcmeFZbx_=0zwO!GKm(1tC%C&ya3^?x;1D1<1QIMjNaODA?(R--*B~LdySs(HJnwtX z%$b^7b*EhAh=)&B0!T6?W6D=)(@@Bsh<|q0*0b2t?^D$TveKrl^qWF*IW1S2yg&I0Q1Xhz+WE+z`@*FmY&&% zR4$KoycB^g`r=F*<&MXHxX5z@fyw5|nf|-*=P739^ zmGfmTBggJP)~ehTSyLzW>fFcdTUXDTJe4aKPw(2iCQct=P;Vb!KmUNhpx}_uu<(e; zsOXs3xcG#`q~w&;wDgS3tn8fJy!?W~qT-U$vhs?`s_L5By84F3rskH`w)T!6on75M zy?y-ygG0k3qhsR}lT$yZXJ+Sq&Hr9lTv}dPU0dJS+}hsR-P=DnJUTu(Jv+a+yt=-* zy}N&SeERbY1HeH@<*Kr}e35V%^#`l6djc`36w>6XbNWIFxvZB4t6{kVk>rvgr1CX+ zL$UPlO7w?n@<$TcO^4IuYYWCw`M(@24b>J-WQzGh$Q0^|rgG(C84ZT(il+-za~0AR z>Pu!zwQH@HhwDp!RT}q%kiBgv`(0}}U1BiOP`=pcusNLmwy|Ql)$Q_Nd8Dy&^#>FV z<&|Pn)p}154wK<%Q}yOR6xG`d#pasrkt8mgmC@$f-H9y8&{s+=b^Fsr?@A5FTIvsf zRhf=tD77{mFE)KSTp4R^JYDVdMR~2<)^xr(7|Ucd-qw7%JDK}7Q@Oq6`f$G1W_7&1 z_4ahFC-k*SN8A17-gK$aL`VDM?b+r?rpk|wKaY2phpQ7me!yTb0GNIa3XgBO=8Z&K zwC01#ySL_xDMP>RhpT0|?oVi5v>rg}vbP>c9zeelM4e!{5lmlDv=PGGu(uJ)-cP?7 z#yw}b8P2~`v>EZ{W^Xf649u_9v%i}O1vBnt`Qcmd zWe3rg?B#^<9_-~t$uREc#c5gZ=O>w$>=&fD9PAfn1uz~It?iUj_c>m zOOG2$X#TNQkpHn(`~VXG9DpU@A8W-EL|)e%R`1K}P-(ub>8WgqoM#p7Z5@>s?8RCN zs#3@+vD?V>j+0^EselangTS^M{~Kx@b)43frw3;6XkesKpC=sbPcdevCjdUF+wBC)m>dFQ zaqEVj;O!YBKhW@hKt?l^lL#OVQ0??Qa3%!#>;=<<0%_gFj6+`6Sx|(MMajJiV_cD? zLS$Mg0{ZRw7g2#(6YfmG^yoU`(K5;)-)}_yMhlCWyxe^mwxPI9X$V~Mtu(C|f zFcHiv7xhsn3HQtiF)aO5Vci*9A*0Py>2?reQ4A|9VYDgh9}i(pAO2}Y=U#;$n&CHD zAh=ie$?)x~YNw5o+-ANIYiOhsq}8fXoA%MRb?>J(Ti8KLaAE5}N7GTvLjPNqa?cB< z^4NfoM~lv_>c@hXQ_dr@W@~F=$L7lpE5{#u`)}!bNn;0&2VlU`o$myt#b>>U9&gz? ziDA2EL$?J@pIxa2(m2N6o#@sE>S!9j_rw{$-NgGOQkLRly4buMBl-36dp_5` zTcjza$aPYoD4JVKMkLD40aihV%`xSnve43pPMDu(?7Rsfo#urTZx~Z@AnETpC67IPoE#p2H0RvSHC{N{@m?W!k(XQ zPhYk))GPp^TqlV542q|xoc7IHnr3ugiRquJ=LgFUYv&H}|yb1(ztdMSy|gX9!* zuoDM+X$8-NRl{;{3zK5u|HGCs3-}1M1Q5Jz8MXh0S4^l~2VDR>V@=W|NCgO(e^ zYi`PuyuiorpD&PTQ!G{(@)I7abqX{19s&!qpbA!nUS6DxRB-{iL{{1NEMN0NzbPot z7v2dEn|{|+KrZp}z8JLd(hq6L%&|MzwJJ}4uma@VE(ocl?4TzMC7!`igi zKVRW_+HJF1+fqQ1*)}kTc$Ruo-g=yMiKfW@?P*TP-UEJ%iKaWvF;%+fdZjcu4B+|b z*DY(^rj!1P>VreOa6$o7pA?k2bokl@RjQX>(nEKQw3b;CfFP$|JI=oOQ~vZ%P< zNpn59-^~hSeb~!Ow13zyDy(=oC~G`2gw*?a$TB`%iza=Pmy4O?9#Ie`4o9+1w5|2fzSmybRhwH%4$C z`zuL?D9jn+v!w(?tqfilYz|`IrUW*vGJh1qdF#$xf-=w|m~QG^?A5Hiw$-*wZxEKL z#IQ-s>fSj#LaQWga7Z}naFh;4E(Ih-NOkW_5r0-+QEg_m=u_3WOuMKUP^s%h%2`%!Wg_R!?Qy>Zr z*u_yT&AzJDROtl+Z6&FAKJ{s4<9?W3BpyVWiDkdN=eno*2EP&hWBUH{;rUbNaT=OXU~b??uvhw8s-4k z8yX5E*RQ`|AN3IW{_NlAbYSdbG;kt)<#M7ALQ+BZ3cXv|8nMAL)NN0=Z2ZzX0W!LI zy6rx-IA?vVe`bO;4Dl|1*MOhb^?+B$O8km{Ap1`fEzB?kv1LaQ@GDG6y4`zaf|5hhm`L=vGeIJk*LoRhE;DYZ9G6fL*J zOCBTP#!nI}O^-~2#o*3MgVng~mXE;`(U6a+Lh z=y-x@lrE@Y1x;|9bI{8aAQYg^;+&DC%k~sHj z{y`!?qr=$JA~GOFqOhRC*sO$Q^fysjS;?eP8B>-*DYA0H*F)r7%0?zg?X$H{d3QjS zbv1RF6+;b-e2|yEmJA-nu9igkP`tuc_t3i(%v@>*Za2Iu2aiXP90%@v7PD=^BOPG{ z&wJglf|tfa#Vc19P=Fl|y5uBfe-u>hJ8_WY9J^rGgz{R~HyX?r$=iaJ^Yf_3O~THgzfR*qf|dy2IQzFs>Sp_oYJ z9Af%GS}|^OGg>puHtSZjPK-bGfI~Aek<<1 z{r&d9f0gO`|AeW-<^J{y{*`_~4TEtf01AW$?K#6|Rj&7d!AP%c=>Z!9fj}fsS0qRJ(=%^B%s9xFnz9Z8ZP{pK3|3=UHiln2K}=F@M-W>mX=B+#I2$R~U&a`TZ;Mr3C_Nf)V@D#h^1GAZ8H zIyx8V=)SI#ZeSchouT1$Pn&6F0xnAHKueJh3ou-_FbX!zE-(t$jn4fREkj|M|1OS* zsNmgJ5lx|@KI8x=QeE%>J11kfH^-yIS}LMK(9x_+u}Pk;yhy@@wnFmFa4EL`v%6nq z_bU#YYH3Sr!Wx*2zWISH2ymvy*BioZ#+Uf5CE9PLsku}pO4uVjh`jhWH7{vKDpvEd zU-OadkqJt21AcQcE3bWnu3Fp5S!i_Ts(CDnENQoVc?IeHBAXcrP;tDIsM)HDZ-CDSQ|`+cshdzgWPL|AbhKQ<6Q?wm_P>GK@o- zH8ZwW%4_8*JmrctZ%5E;rB!ue4IT|^M#O_TdPV|9sup)h&Z0IZAG*0a3TfU)|AmQG zjy{K~Mpi$^(vu#s2q#{^CSd;QPtx=jGuqVr$qZ?9=7&g0ekr^jE($5H%yvGhuR>ns zlF^MlE)p>7$}a*EPCu5{XFgYwxl5$KKi-ziclC5%($W(V+^x)vbr-Gu^6pWj(I(bI zq}6o#Q8+ntMR2@2wf0`TYvRxRGJ@uK#Bvxx=J_TD^0RW&_ld*t^(-nG7XjnLaKhD1 z?udtr6aLl7)0wL8k&iz+jZ`lBPf4TiyI=RWiLZ0D->q4`MSLE=ZFZhKwfcFvbU^u8 z%yDqymElO{hrz(8g5q2gf2#C9lT%{w=%3tQE9!x^HN`?SjyzV1h!F5Tzxm`;Wk)q* zfXcxqxoTHMiKj=wE7nj!thMRgbot@Y&kd802#thi(MS?s8Xf&(lrxIP+6N^3PTU4e zsFYIx4onCn^}(t7^*tysB`&F*nRPStxd|0g*d=9Yphmn8K_+(Pkp1ou6gGC2Ah$r@ zGw{aINV80)&z>em{056dwl}CYN_zl0KOC^fHT2QREi47AhPvv3;!2fM-dbCoCOK@C zMEEnar3MpCWSC5qR?V=v-)EYJ2P$@jp%@K@o&+W$YMQTVB}r>P34iDh<$%DbDr08q zgH_ZVn%JW{yx5NAxL{7Zm|TPL4Yd(lWOFltQ1kvRwZtog9H*FE+wq(po&(5Ry%2T$Zy!rU5a6|XV$?zu zOo}f_LuH8&f~(czne&GZToq#Rt7zjf>F$qFhK{;Z4gWtkjMKv;xT;u@t^j?k~o_zJ2B?+~xz=+v$Kyimjujf`ZtSdf6kY!z>gxi5Kts`NyjxTt_!wJ>Ct7 zq1QwODsEAd+z-kr*Tf`_ZqW+e52;4f#1$%TGwR(BYme6?G>&ewdfbl~qt_-4DDH5k z-j7-;*QWd$-QjJ$A9IMPP1{x66A+ zQD1f#_a9uhIe;JFEdc#R4BGwWx-r62f}3a$JqIxT;ls`LRXYnRY+Iq`NAOxUtE}*Y zxltRP#sRMMy%5oCY&q(w1l12;62f#py$e_y{%DS|jAo1p@ucw=Z*cgN^y4f8TP$di zsFeu)tmE;mCaKJKKWY%f7VP97M)S8~``1woHyk`YxiJ9x0}MjJe{zLFLGI@p$YPzR zYkwW9qN5rEya6Gz@PzDt9jp2x8jn9o0|1jn@4kb{p;vg*C8i@cs98`Ox>Sq96_)#k z7(n4yl-Ks1jnPZJv6s9eQqa%C0kU7_5BYg7Uv~y$h>iL@>>j>JyKD%{bu>E@;6KDi zga7Jy#p5$j5#Gz|Np$fJjvV^v75W6rI^KOvc91`}0R0jaNDTN%fg}qkA=}F$1-O{6 z`Z?l85q~FC4(0)yAP=~c@sF0b%prk?@^*$e;_?##*!zJC{ya5Ply(9g6uD+J zH$~tO-zj=C5=>pUJQ6C2hAk2$$jwIVC#e9cc-yC-eA3kFhW|3hs<8jwvFeh`;c??y z0Mkj+R)Wn*^Ik#eNy|~g;YsURKhtU3)tt>~``u3IX~)ye;pqO|X%A^O6yi(%%*ql*#t0p`n5 z?q9Z-WBj{im*a14k1i*~5LvD!r3mb9bZkW%CcO~XuP+(p4GM}zn;@~ zJ-+^B9LRDrZqA-~cyeWI@p5lb29$N8)W&ZH$n(A(w0j9$bPdMuVxBFDeE<&e9bM}g zK(C0p!!hJWDFMLczTEE?;_Z9@5I+*?s+~tW@>LyTC#=i!7Vrq88ZFedk#pi*{0?v- zH?}+#pjfp9P$vPsih20^d`qS2*Wqykb`0>3iv!jI&~T$Zxaj&7>V6{uS1kmZINcG2 z9J0s}g_9b})P7b|e#%O9PWo;n_)m9Lcy?zf~4Qg7JTbk^!0vWU{vEOE|oOFqyj z?MRU?%X8;FpiLiMm???1|CyWd&iSF3F2b5PzN*NN@pm~)7LR0}aad;VP<2XTPNCix zO(@P{A;zcN_^l&KXf2qUXS=TO$$d#J-*7>ppyguAwyi8KgjHF4gLAvV{$N8#HUC0_ zsGVD2aikHvL^2)TVYcut9e!FOFOye|HmpeKH$Q5(kmeyPTfDj=jz89}#MS^8R@NCS ze(YXg%Zr9Wj9wmQmL1_eRAU$x81Viw;p(Z!Icb9_@AN7b?Hf0-GwV0mAIf^o<}*%W zO3tM5hQhKxbq9c#wFW&LL=(gU1+VyYBCm@0VrOlqd{%-OYGh)fS;l5w;|<&L@SfOV ztt{c9V6QFJ9Y-g5y{9VpiH@}rzDe*w$dxzXliB7lY#LXvSt?4D^|wq+>^lJ&6BFgJ zUtx4J0*jdzbuGIACRg>#zi|~>C5SVre(#{QkGmgzj|^5m!NaqD;E7oA!aQ$5F^WE2 z6|0;)IbeT{)i7-Hu4oYvuLZp!n2P$_)%cf{7QsytsXu~qqIJwdi*L8+Z&!>x-iVpB zTzW|z=hliy?NhxzTXOAihF2x;w3dCQtP#D#tD?C15|~>-NA&OwP7fb`@Yk9eb&%UB z8Jg`N=$asT=HArr_|8c3ArT#qa|;UcLkGOJ6eCui2w>^WzH^#y19n6O?m)9e{g8xg zuqFd}q&CpvlO$3OTwxMd$A?p{g33bBt_&T7{$$e$iozG zp1s9g>l_1IVvG@p2$RqJeZyo)!;l++{VO_Zq*A0CuY0fty&N1#S#6PCP3`stjYg7T zoi>` zZQ0zG2IL;y4Lhg%CbY+y2$&&b8k_jWW8D+8RdNXZ>Z&2`MU~@D<{0}PWEK9?VdfRu zMY+~^wEaYz9zV`cK1co#SIm8Le%riZe~oFvQ`EtYPPHOCTB-COIJB~~v2Q=F)l#Kg z$_OCxDSzyZCFiXvmPUMskjwSxqz&+#bTVCkxc`CqC#A5lE;@-;rh!P<_x(r6E zP?}$N!WxIO02|OwfY8%A zW@J+@o$`5z-qQx*L{mTC_<4lK(W@3+2m{ z#it$qiRN+F@ym?IHx$S(In=+Y=wB5>eW4=qf7eUsPXzx?MgOLkXx~6PH+X>XiZ3zL z%QYa7)fWrUS-U@-4n)HImM>;CQ)T)ItO~7<1;CjduCx=?*--%vFJCD?aX=@3d|P83 zZ@ahH8v11EVy_LjLIXnhT{;9?!q)mQjAWyQ`OY_2^l%B$J1@{c-Oj(1zjwFUUWs-d zz7qTH_iYANN3ot_`*i(F+_Rv57v8fPXlx9IeJxnSMFriFBVf7Ak>O9LYpwaCmAMjo z>F-%Ac#?ym3jrU@ntr?OQ#28Ud<>%h@SWFXCxaM1O(xflQ=!3>M0mwgFEH)TolG=w z2tKtL^H1+BGxU`{vrnFH?&qT%n=MJhz7i58S}0s_J6V4kaLq{@R%7h zWMmH2V7*{kX8IwC1}3#Bk-w-&m}Dlj!kB4ZeW~MT8BpFzfQ7LgJ;7S_v)1r%{*RLx z+qTldulC9s8#&vm_d7K@s*fj}MD1KWi=i)6#58SoGWXd^MFsoKNbKQHZmYf)&ys1g z9`dy5ShT~>=Sut7=BR_bj-Fw|T*Ad?tTd5~v5ka?mw64e)oEelEPbS(*<+s-f2NF| zmBl1ZxG{0a#jx0Mr7%;FDNQrWH`sEj7hX~GNEK33^Tb-C@%)OOJ(~J8>-X*F{H$tv zZQXpYr@EiHC{^+#p_0xmmvyL=C11?CestJMQwm4>qDxLc3% zjIJK{z8h)OZ2UqEc_Sg7bJ=p%wfO3f)I{oZo3!si1X}J0fm8csRJzyX*;>>O)$3hP zHmrtaQ?8-|1hf}iN{pkO2DLE)Mvj;(J0s=1gfX8wQglog6(`D7;L{3LcYx=l zcZbg)+9F4npOgccu#_CQ2TSVFE{aSNUJjd!PXd)!&0Y4*D$YQ))OU?&x4DfqTv(r@ zw3m}3iP4NKVJ(M@=aZ_B9_k>`w3?J}8J1`G&kw{rcSJ#>n1PYSnPSRvl7n@XubFLS z$?KbFaAaisVMW=3Cvt;=4W@zFsZ{ugRMDIbN-==1xd=UUSTZ>r5m*A`LJC0=(iuIm z;kaRUP1*AP+UmrWqq0oFK|@g7vsm9LQ}MmRh!_fWa=WK~W&@qKHofYvd(!fT7}aYC zwS3iDxN}-2qZLhLqC@*g<#>h$Yo#;iuvoIBNDOPIi zYXT8GY-)eX9tX<%LSFkowQmZN_Eh2wyeC@N35GqNX@(YPzTENB>%bMz{$5ZNZ|03< zg)5|+Vqo^Y$7&SM{lpM$H5KIEY%eHT#B`1=%rz&TNQjnBq$xL=(;S-Bw^h;yC{?g5 z2urOW&F?4IlKzu0s~S{qVL$~Qw!NUP7&bBEPaf6KJJ(8j!%Rp#job(2(9VEQAo#v5 zUwfkq@e|-G`4%qy!|Q@Ayuq0&wp_Z?kCRPrzLfrRjSc;pGcHSYEe=QQZ#CY_d8IHE zefMLnkMTb2aO?@#_3bQ5{rQW^)i(?Sg3@-J)S)G#|m!lF*G3G;!uf zoo!p?5J|KqM`2`My%m77wr&$r`-s*PCJ5*UNysP{batR3V$I4J$+{T z=BR9}|Fnx(r1LZBMl*-WU{BH_MSyyP8#Uo_UtLsoMu0G?I2DIn0|w*QCfy71&eE(}x(bKLN9Y)J!pz>7?$)7zrm6o50 z<5vYjfA&=)TV@KCuS@j)9B5Cp%mqjPeS-e4^O||!a52dNP$M`f93q(A_{D1mg4X(Q zpkhYpErjh)86LM<1i zKuJ4FZEj|OAKsUpFOJfeF1WZu%rnxe51=6qZv3cLL7RO}KN6$#KYlywz0tm()X0T5 zNGiYnGW?#?Y70pGjw$w9NbnmL>?!{y9qV{#XZ1TNyxW4O;t>G}0Lk3=wKbf~y0JB; z41bqjCYc+8bMe=rbr3>EKqkRPc7Ij?&`%B=m>!Rq;S;ARClg%qC(zV_DMuU{!j)mZ zNi6D>@WF=Moi~p}no)o_JUWjCX}MJ!G@Q9(rN^esq)|5O6yCq(a#o0) zO+zhU_@*>APaAKCwm@5KpfqRq)|{7%F)_}SS2lLWmDeF~i=8j_o)jxwm+f+U9FJo; zeDk&1F#qhBk46(8tWVc)k1x^n>f^5o&t!hds4pG&GE2%LO1s2UZmv8rpT4>C=NrWd z3X~aLZ6D4x2|b9{Pm8olclo|+lRBXsL(3oLlcExgDUO&t@5c~TyV`s62`j(NV%qW& zVukU?u~#p$^&aCxX2cz~BZ?Pa95E|q{-Fa(i`YzLZim!;==67qN4?h%asZlHnY2_w(Hu7jnh9mtQqaRtkv9Z;?=W~uOoP_Ba{0vWqDUKjBhZW2 z$v)j-;&n)Jp5hFmEOHDoL;Ag!Ro9U2wIu{-ITR?I@yq`B|kR zKlzzS;p+S#s)+L22jo+%)}t$9V}_^KZP9)d;M2XJ^0G18VsN%Xcdf@H&%h028bKPl z#Ji;EA*Ets*kF~?%~b=F%CY}=VW*g>q+e-rJWTgJp-oIFQ*wqf+`&A0V866Uv0bqa z+(B#Z16MH762Iluh)+>0(Jd)bc;`@<422rS1w5j#P|QR=*)(Mi9Af@jauif#Jnc0% z4Ik++EPCGAG?Rj1kDc2Zv)9qokurOa=QACT#!W;n;%^e(XI|`t7My&f=*5MzJ-%2O zsxNfNiwBmv*cEDUh4fQaf`!Ry8N5^uR=)ep^99%ioRUTI(pg-4H=Ow`5>^Zp#9ZvN z<2e%C_}ZGm<8f29$#tkieGj`6W$qzW1*64X{H02AGGVdXew^24{(2v3IE z`|q~j87{N!50q#lL;UNbwM-o9@# z3pRLwGm);DAlOw>o;O^d%s!ik=^Q*qY}_<>fa-SdPnlaiK!aeIt~y#F&hl?nm>_ZCYG;3t3>K=zUg zP;et&c%CG}DNUa_2)L|RzVgoIW2ABUT-*dI_du_{pE`G6D<3cH&}DCYh!1GbQI$_) zNgs>m@Pp@k2W8YO{drtwXxg<%Z~V`g_CLGS1%Q13DS+hV>s2gUem%2iNau&wt)yQk zq~8il4u+{(5nHy@l;@^YKh2GiZUnBJcc7jHpoHl5zhfz00|wRa4kL%lTQwcB3griY z+I+S44FKKfT_%(NF)rB(fc@KVC|b6-JhLnG=hfG(e5={PUSr9kiBKX+&uz&mKCH@k znoJh%dGLPMzA8&gy&n=ne%e$=VcKhso1e!i>uoJFdHJE(F3i9vE&CW%Sw#T^$_Le!7GVNw;pAGn+oHP>y1Hw7zYJxH zj#c);Pby57HWK_*3!leb7+YytUY!Fe&w)3>cU1QelaJL-k0y8DU7g=(-LDPZKDFHA zfY~)$z9ph#>*t+mcFCi?77?V6rjXzyFhzz-kU=H@@PYtl( zhLS>n6Ye@hfT*d^S0XTcn(Pn5{@WXNq|X0gUA`#>0EfFq4iB`nSSP~M<$q;?@1hL` zlBE>t1W*vOgM(-~5{ZIo0=!LvY3;$A{)TNylooKK^f~yv2~F$ffIUJ}eBpqi3=8oC zxx6S@uY|3z_qe}5#AXR2o8qbK%6+udYM_@+Sg>QzO{9&1LQ zOLvk&KDQ8(BJQ9Rl46de5|R?uq85@;rlw(%GWx*jpwlY%`A!I#$z7f~g7;U(OW=3og%RqgA- zd57xQD`;NEQ9zowSKZI34$SQ<@B8W)NKWcf83;(5P&@x0H;S;jawbkQ>B6PU!dpvD%u zD7KGLTBHMGVTLKkjmL> zDVaS9T*GWNto1p>$h}@BIV{Q(6JtDBwJ~21{W+FgS~rA*Q%crPElkZvj)=%nhFdVi zpO0CJWSKtc>7FX^dlD7d&@Sei8}UdFYR$(DLL@OTHah{e*{jAo1W{0Lv>Q7$9{E>U z=?n3g*t#t-`edK=`DnzXQ8Nrgfnm}(b>wVZ8Z+##3M6IcNW~iY#%uG##Hs;)d_nol z2>Vg^KeO~@CbwDN&%ITu0T?vJd9w%J3wgEu1bSXd4TdTEK5dEqON!@a{QiLk31+dLn9jwSRxWk49b0_Sww+= zgYDJ=EEM5+hl^#5T@(T=)#@^M=egw=*EtJutA{9{9WV|J&r~cpp%Ws==~^sk)pp=j zn3e~%?pVP|8LX9QL06^4THZP60IEc#OT;(a;eIFps^6z?OYgL(_s9ThqG$J|muAQM zb?1Ode@e`TgC+ZJI;&BTj-{1awdd@)IfvAat+R=v*`gCF;;_PPkAv02P+1`y3oLk+ zxN|=2E{LuBWyBGw8p$w0ao%Ai$y|}(s8t0Z@01dpqfod07Ni(+nJj3}+~XJ5HNNxu zthhx@tNyQq2Hj+y1d6i7==r|)S&ms)9>_6IJoBnuv?^GoRUK}7Hvh| zZc52Mj)W2}{Uz*tvYr9@znl_hy)gv0e$I@be|JjmLbYSmfLn1>{DdkE$U{MnHECNe zgqi_E{m`lEXagMrqD_Kdo*X;r%wYr+n@kEaeW6|m{>F`+myP43D89%D(E7>uY8%oh z?qnZf1~sZg9O3HkHAz(&G7Y2$r3&KY2^z5z>>(|zEWz?4_q0P2eR;P+I3JQFny7!{ zG2n)mmP{((Hg>bAnq4F7PB6YrH=fl}m)=)QvQ{t3@6AIKv(8!ynNQ$%srL1$kd~xW zHzjhZB%!cV=95+p_1oap7#=wIR{H|j|&_aAuzyF7PfRUUq(K3^57v>&{mc$jN_zOIUF zKeAAHTv&X*X_{z1ah-Tvd3?U@#OOE+RC(GUf!z(NbX+7(Jnaa1Vx>PqTfL=3B9rTy>Yd@@!h-$le~$Wyh-Q1U)_0= z6Z%l{`%r89(7O51C;2co`7qD>u-^Hw6Z&#T{VjC=bwc-V)chMY|5rxM2L^_S70@BU zw|hZao04ar))o>Cp=#r2hd0tA9_qH|jNGrITjgv(EPE`fy5A(ZV|?r}W01c`UCn(7 zXh+!+!tJ2n%Q%&|#aDZ1r9Q@(+Pi^t!Q}r)m95?#rL7sg3_2{BvjkLXGE=I_|A!oT z6jB^#ni&1|n@bvRy^}}Thdm2%8p0|ld|fYGWRNy-S?IME&_Lx;jisrZhjaScmR_I8`i3Y7-=a&r(v zM0z;kVkT<$uv=#bL98~2(7q0CRz*d*n3z%J-eF3g0HZ|w^3J*M5f#m2Vh%RVUcb4Q zAAExy=TH8T0pW|nJN%#FAFA@e(#eRL65cBJb=wM+|dfC_3wa z*%7LWrV#U_KXT@1OXi~T5g$S@&P~zjV|xg>xghgSL8Q*AtmK|JY0*PjBK?ED8qTo@ zyHa%NZAoulwe6=f1kvHn%q#HUXK~$edy8bVOEQ})eBGul;F(|_SAB*c0qVQNaFmbf0vBF@NkcT6PU=kOHN*wD3}{Jdr4zo;uq$4Ag&V0i9BK|;mQFZovZs>WX2=Zgq1NIWD;Eepnjg&u+^^tYDtMhF7hj67r!ZHD-CKb5yB z%)f+c{w~u(t0hAUuV@{cclagikdO2wACP*QL(xJubb?N=fpm>7<)lgfj)Fda$v5n_ zQJ>521vRyv%&RePx8;t?RHwyl`KTWa*sQPL8kpg`LB0u0QQ*_X0E~QVUJZTXy`jZM zH`31g`;S1pl{eelnSYO^@ZaH1qgbqI-mC~cvX#qi#5Mcsff6Y(#@od^N(f%sd-4!>!s zsrrXLt1o>ByQd++sSWrNQ*AW!l}@rxI?c6)-O!J@TBd-L;Q8YGxp4kW0v-iUvbqGzvO-h~!yaQjwFH`F8Z`=DHZPl! z-^Y;M5VQAro!k{A5=?4KB^{#CvvhvKrOeJgntJl_MO+_BD+X3@7YGC1tQrE{8mhC7+}R3N?+W0030Cl+vQKE3FiyP<7D7K+?@jOM3w>sL zK(rokBl&rvG5a!SCX9rY{PpO_vFt@mSH*as=m|3uXM`2PZGa`O9fYy0xL`SK_E z3O4!n{*yfazLEG}Qjq(bJpU%o|LWwKN|@1|7|aEK&oD}f*0WFKDAY{r8e%W;SFq`p zHoPOlXG`ysJ)};s%0BwAP^{DgQuh*ZI!?-BYDGRt@QOOi%0g*@%&5eDqno>}5|%nC z$&kMlR%H}<0a1w#j2N;{lee2+T-s62_+~oj4`ERq(s7(@^3M;^qxaY~KU4siNVhg= zr%iOCR5L0R$Y#a^L8@ZG4mQvz{wz%T>$dNz!#KpapvE17LxqbR z2+`akFXUNGir92 zgiX>lO;ZFtiCcgLeL_UMqmYgZP8Gc@u*#XIQQ2cX0br~qN?SLvF3k4Nb##rOM+U5-T!hytCcNr^;?6c<<sLwVyzWkno^{7*L_=cH^$?m*_eubEld;NlY^nT$f%uxy z5e}rmiBb;Y1XU8VL7z;kNEV;#n&34U&HUk|imm-x)S=<7vtqez*k-L%G;9=Wr5Hao zvlacbaD{MuRv4Vj+ikK=MIfn$%h)+TNY~Tx@`R9xflW#I*OkT@@`%7oI7jWhkzn*{ z{%G<$;==17@5*Yv=s3b&5cAN#Ln!KVvRm%dUl2;{Bsmn6t+pAv^a7#Nj$z3!5US&b zCr-O)8-8v|Wzk3^@E3$~e0+h>K_;)?(ei5BaeInH!m08jQEJQ(8}zoiy^F~h(lCxi zKeJwb9pwlnRGuGZrkz1{N}M++BBxRlFMg*oet&3`+8k`iQbhWPk3^Lo_k*5of`6Ca zXEwdPQu^~8g!YIUR-Gc-kFr)}jFZx=g0+-p#!8TqWu{jj0Z1wa$}aCwzou$@Kf z&)V#lkl5#AORk)yGFF~SV71+s$d`~9v@z`smkmE)@g*eo{v&EN9o)<#;~kn*>+)EF z7tRywN{D9UY{Wy9~Dzrx5N&a4N zMIgjL2=Vi`%i|=y6P9`TO?D@@k#PuTs>Sq@@Zkh7Z&}(UQfSxlFA%EMas5vgIUWA_ zu%{E3)WawDpWKU~bDzrm+ljQlrW@&Jc?W9%6bkrgfXxP30YHzK z`x5EBD1=eZ_6J*@0H3sbU(xO@^*<5wdIP%q?!WrU9R!cu4c#QL%tAtrDO~j-=d$ed z#kPh;YY`aR8wHTr3>YC&c)E%PQL96YgYnXWjNut8E+L_;JOGn0v=QlbAkS2iNtj^s z{Cdcf9AeX8@es|8K&e+rrU7#5^E7_XSc&t!RS`95y)-^2ZobiJo}+a)2q2QEoPfmUM(ey@6qBPZnGL#PUiS4J@X-KNYa{KMQxZ>LfI&2AA80Nd1t1G)a27tj6A z_y5V?)JWYw5dV({;+Fs@f50?=6A%O&Dg8f@#XqBQs}T1up6+|!;IHf({8c$Yhje~y z82~+CMRnSy0K5r3UP4N;00P>)&zL`U1ZGl0Zw^nD>!hAhK0&>@`;l(=(ESoYx*PEI z%z_dHz&b$bjTu2n;f^2y?)F^Q0*m>Qdah=;KX7MTbKUfx2L>!B?*IcCt?dm$$kw<{-v`EPdSO z{A^8X?fe{Nj*yL55*S6Kd8jNF=6e}4nn!QQ>}3Ela*&Mk*9lyU3-qTL81fhvo9KeG zBZ#(&m&0XWl;%mIZig4y4emN~tu&_>C!dGpmpGpYn3UZuAOVnyGup$wmz1qEdFJzC zWs`nQsa?e~PHTSG_gNj=jKy=XLAw>QR>M& zGHhQ!)}Oj0e3HHq=32ZJJMCSZ0s&QbG*x zxIsc3UL~sEd*C6CgR}%}e_E<;nxY9?##artuq@wAi&7)Qu2_SXjxP;9i;|%Y!=)^2 z6=zV;8t~1l!7Md3!w;XA6?w0c5an&kB6Bs!)>`whL(TiGvx~0|D^=2s<^~4xYj_h1 z&SLP04UCJ*PFX9W=01@-y{Qvi%L1eDQAUtNhK=pf*}pst7V3?&!29Th_xsJD&6!*o=y$<)rbMA6fD&c zk#Z(zi0gqtwjhIT(hsykvnBT7;?J+xwJNR&YG2R5=}ecE@jJ?Ky^1JQ9Q~aYWz>=KuVi4_Cw}M^s4rCx$%b0gHx#{116(Vm zoLmqP1}`wUt{;o-Fs)RP`gHz)kP~v%E($R#(p+z*zRl-Rw>eOtA!_gYB>B1i#YhCr zD;+X>@hKzgH;pt@eTzZ78Z3B^8Yzubt0k1Xxvk(?Z6BK>EUAe#SiIMyWl)!l)TuF{ z+UBE5vOZ#sA;(L2bufTBnattNgO>u!Anc)Kk9)Fa8vk*Gn56~9GZ5x@cDf?8F#qXz z)=hyFw-pZ%ckr}saCMj(wIU}w>2NUWpE7~HD3a1Ga4mI+ZN%Ds0PDI^^)2qDgVHGV1#HY*CfaF6jn2{2c^i7@t4M znffXFQ!J-yon7PAbjz01q#>)@n$725pQ+oKK~*n+Gl;I-%1K{#MA>u}pNeW!(8IhZ z5WyLk_oSA0G*`}zj>XGZqIq+JL}wT{H)|DI&+?_fxo4B}gEL$$a6O;=YHevLW4IMA zBk)VJAoEi1IiboK>&U^4k&~-yop-Gs9631}F8#SSjhzEer%vHrv1|W$r$cxT!m777 zCmwM|`Mg8gvvtgFe?ljr1Pxg<9;EG43H{{I!`02>yYm&0>&my86{#Jp0XM>Ez-hpv z2O!xe5#tJM-+l#r`z@1>5`7u?sLR*}3nkvl`_Plh{c4u*%O z4?m;&^FlcG%C}$L=B)Eix&atKzUR%~umk(vadPiC`5ieg*@>=i=qt*Xg~S5rrnC=r#b?gOX51qk$qDH~FasgQloWhZ(<_6E~EQ6wYD~kN1|oLp_${14%~6a!EY+0_F%DQO5SzctkPk z8U#T-q4N6fONF)DFNA0u9cCj!YF7^@};zpj=H6U7>OgYX_ARuc?DAti{JAy(MMh57aWN3PVn)YSPnk z@U=aj3bNNz=j4NI!=ED0IK$Tq&6jI;H7|xuST^n+pubvz6mQ)znA$&goqlx_tW|72 zj7q}^?Jci~He6~CJy*@fMYo^GjG>k7E zKW5ASJm7yxKtHAx91P1Bnt#X zZ-X)9c?%ldA-qc~@`Cvv8Pb3vDdrK+x+bwacmi}t2g?^yey53lDln8#{~aa1`~bY_ z<9`?zvmHR<45s3QSkvR|h>n}yjTa+xrE||(sKRTHa?2zz$dkpWv0URg_dS2^jIg~j zGBlkTMWC*dZ}P-jfGLmlL-D&;r6StY6Nu0{CGFcS(K_wfaSj4nYHmW!mS&uG9g+qHo0hSvB~+)S-kWRoT#SIg4I6DrI&~CMR;O%(J-5( zqIUJm%XPCQtX#NO6O6RwoEvdE(0DC^<4cAELAC|YVLJ?Ldxnvlu{#vrm;Do8?Rvj{ z@`VLJA}SuOa|@|zH@X_8y&wzMDEL%#fOQN0;Xn`hsEl&jXoyPT>zga&%lPoofQ&sA z-15Zxh?#7Y)9=8ydzzIh z9~cJG$~=|@bUNI|J60a2KY0sEr4kGn@jOSjl>B0Y>%QdA3#}%T0n8_RaG!OjH(v}t ze&ZIog7oaNo9xF?(6iRhMB|rzfz(K$5Ss}lglN4TIM|Trip@_tmW_J!l_ooSY+da_yB!VhhjA{l4 zG1-rc8X<;1%ak^E20#B85}Ud~t%|$_rxd`Usvr~ZN$!St#lNW_%w~q0c8RY^9T{(7 zIfM7wc$jQJ1?6d&>|?7VIa+`+V^7#8swL=PF3mJTudWzpx{T}v%QQxU-w?)rfoUQAexjoJc{StqMyFsp+o(o;E!@qasjAEg*K7fQdj_Vek+N2T(Ir6=(EIsQ0B5g(OSimV#V^@%IQ z&fdj}*_|ou!+B&NMumi;{+ynx#0_zc@X=eqaD@oIJXy3g;B~Hwhd%csuBj62^Y`5l zF6%NR943ODkRj0t>1+h$ef42A#(Ii=yKb|6s{f64pM*kOm&^4aa z+6ZXyi#{j={izS0UJxSv)Cav)Qy_!YB`~&gZV5&B1KTC*{*mn-QfT^E;TpsAK^WVy z(Ent+;s*TF9CJs#?3(xozsQ63AK6kdX-%nh5Tbx`cn=Ce17uq=HA+BJf+f?ze6~_4 zm3UL>8Dfp+3$W}+1f#48RQ#Xv;K_dBijCPwJ_o0U9f7GFU;&^=ftqSygUr*P4BqKJ zp`qqZ;B9Ea@uWKeyZr&}?qFp+p4XK?+oB2&!?H}|(%WIfE$erfhqXCE9j9PQfrEO{pRn;(>@$RbAwYCKqt&?&$ z+o2VOpXJPaTx4n8ny&SQWk)`U`~Wu!s^{GkGS=vuTiVCEZN0Un2M7kh(YRDaW81R1 z+b`LGF22n?NMh};Sng*(M?MvM*|kIF!65t0Qqw-;SYFWe@_gjANW%kHw1FUed)L== z8Si|jAFtw%yT&vBfNWWQU!PrTL|=zHIsQ)enQ&fyklpE3oFjyj$zr-wkF^zes>2P7 zoE(k(qZPRS=R#WhxBob>x?$|B!sB+|5UXXrU-7&q_4dH>S~nHc$&Pffst!D33e^2SC|>>=V&&A=R^=pyl4L*-I@r+ntkBCRnLRdC}APn=r| zh3tu`G8)H1p*DIgca)i*=qB<&Lpc-j$iG>QSMz;=(s&JhG$=?KMblQ^W_&Aj8sZ%-En_5@qzhn-@gqbefO9 zmsjZ~Jun`;9PPCet&SK4EmNl=45-Ya+O*gZfC?8FP3vn_kjX3y*%l^cjWP-lwv1G7 z;Ad*ik%c|Vm7g%_&^8j%(Y~@~(k{TmTstMCUm8?b*9@?Xl=Pn*I73)BR*ek=XXiq78dKr08n`wlCrNa?=Z(hSk+{&?1=nZjN!7>40 zop$3wjhHyFOaOEAPoipP1G-mXp_`q?og?`r)QWHsRzgH{j{H~#{;za~{Q}k6&^Vua z5E@sJMS5A;>e1Y7c5N>^k)be@+Kdo+qE!+o}|NaxYC6c7B}@UTCoWcy1)ZIX+E6bW8eXaW?xLW8L@5 z@kq+M)Lou{c0CM>|kvO{}U_PV1e5D|2|Or-C5c_Kea$k*T77%l!#ST+ zzc8E&WHLcJ9I*hg&AY`cgRy)Q@?0i(Uu9n8>t;J-oB};c7+fuf)5`Sd_(lbXwFOZ(E=uSD=Q*N-tYX$?t}N;=neN8aGP@zx_V@AO%zWdnv!QJw9$NyLOA+Y^@QX4U+f!O5P9Ti%T}2I<=|`p!g;KT; zl*rgc@+h#f9a@srOe{Z)eF!UE@Y^B6GX(hXL<6cgO;BmfANw!F7^piEt6p6HPiV!3 zAs5Oo$mK?JAIACH?RnqR(LEjA)6qR0{f;ghf1iRB^73B^QugIl`hO!xEm3>?L6C}V zWB!vMr5r_OnGb20RHU*gc(-e$N;X~mXF+O7b+)=*-k!-W(Y|(>!ziPjz{k$l!;jxA zzL8TYc_3=IpW%)pF(Sg<4hNl1Zw*!{z`u%*T1^JGnGxT@2%UB4O7)3iNrchSWB0d! ziZktT+pnYEZeK_Zutyr&`5<#H$7rC<&mkQ=jUPOLAA#yPNr;FdRp3XWg{*06$=?F}~XCP8K_e|3g9(+>d*D z+=N|0$0_S~$wmDl`q)vzRmS94Zg9L~v(em9)Krg2;A99lW0~x!X2KeJpVyznF<(u~2ynjsz9Xx1J(V<8SHL^EtLSk(orU?XNJ?Q(CG~oy zNby~X#^|1U%k_t>fS>2uShi?jPhZc&;NyLsFWI-5%bXhzo;gP0BS|K3JfgdMJ(t_*hw|S z(5>xa&6;thlJ^9wJm$(ivWDq#*+Yq09V8|H88`LvJy@|iL}TPLVasJNTSRr3 zh5S0{(q$jtSapQU$U4Q%Wj_S7CMr;VgPQDWKw7aTCUIngPVj0_DWWE>P=1q9=W0lE ztR|shWRun7YS<97HmP5JizD@F#8R;~Wo~4Pr{!wYKB6{lTYg($>1xb%tTy9fWLxCs zY8;ALmxZLTBSv;DIuWE;mqRqVBPn=285L2NN3XE^pQoDT1^9caxYtVD=JdUC{tMv| zNRq@|ewk*L2(O8)FCpHAYuo;=_#s6Q098bnk;SHWA*C5XjA)Ma-SRw%uN5|wG_rma ztQDRdk4zqvfwLPBnaLoN7!~ElhZK`0>Pi$_dwiT@dNuma%#{5zaMRF{n;|G7w#%Q^ zOdXel&QP!9EZ-0|Nn55W31{Z1!3W9eF%elL$y&_a#K9IucCWV`;zm#`G}SPzg0p=1 z-Ja(enolL?tMOX{=uNg3tl~5LQsQ%SjC~lKH28-Y=;E|B;&WfS*MLh^%1Je0`#k~+ zrINd-nmL)5jK!)Ah?30UyFST2CY4dK`Z92u0-D;p3JIopl{Defka}-g1heu|niqgf z@lh0J#_EEh*ru;F9EAb(+|_oba&98oye}XlZQpY$HsAR@YYIL z^`kvgKat^o^k_rQK+LyrFlNw3ev%pE!RRE@En|2E6F`*VffVS ztg9{V{0E3FXX1R4FmC>$v~bM$VkZl3<41Y*ZxWn{=%XcCI&tQ_zP@pFyqqV@(LNr6 zzAn%t!8-qA2*iZ&xl7hf+J%e4#=2^?}Qw2+GmT#P{#iGlxCprR$%dKc4U<-0=ZA0UXv) zGH@0U$-fgFe1!D*#VP{DC7h_t1PK4TcRdGv7dl9kqvu;D(i7KCG!a8T8q+M4yMB^Y zj5SXtT>z>tb{8>)VSs2=7KQ^cyzN<~ix9q#sVBIF5_M9Gj_WfH)wOKvR4vWHBC+LL=ZKp>g+tRF%tr1J^jYdb9 z$*(*(PUF6^U1!E@ zaUsWJ9~AUt*9?to!4CvH0S}?;v)j7*SY|+zdFtqH3uH#|Eji)m54M2q#Ugegst6nM zS^VR{V@5udX$QIx0rti**9yZ7JZPaX3G^BAj$`I`r)~IslnKI9)c|;wB2ffJZ!g9f z;HA@!IMvXd5~0eg9Kp*13HEEi3%j`72LwA(qNG!?;cd(ul7YBF;WH~S-x9b8oaZR0pgm<(m+5>~QO;ba6176v4<*`XC7Pop z>J395D%{UX4KYeJ`(FG57P@ER1qXDY?a| zl=HeGg>q|+;l_ON)!=^^K$Db}qw9E7#`@ofsANfpQ-|lU_6qC{(;k z9R9dvC2-L+5nka`D7U<)ebGEWTH)LtSHAm(pQa|Ey*DpC8gYN zepQF^k|J$1RVwcM^W@jss}fO@4rh20)=pc1oc)7I3L&YeJw|!N0yx1m$!^>ZoV*4C zJbtbBB#EL9{Vxm`*#`9A$a9c7W{mXpF_z+(4*$SK`$qD3{7s)J@e;b^kbnoX)|jwM zm*a%-s(c-B5`|u!=>}tZZ(XjyZEx72tX{9_cWn&+L2VQ7mvcHZ9w8=LytL^Mf)kl;rff zb=r^Ct#@ek1Wj0n632a<-fvE1na722$DX{DIGw^1eS0RR8!3#v`0`NvV&xma^HLz} znF!`q=1cMGz3EQR8;(}2C+H{Rhhn$qwNS4gj0fXSP=DNHqJY2V z&g@;4C6L8AJQVMUdcG4G$c`BqhQXG5JW&MTro#%iGo>J2zZ7DgQg&BrH93nak~#oZ zN79FpQMMuVhz4N!2he;<)ott~VXjdLZ|!;nSKBC^Vi*%&w?PZcmw6Dy5SuOp>=7IE zh4+q$1Fw!VCyL5hxM;d5=mrBVMo4DU~X{Cx%pfA zQCOkSw#>Nu;;kwTG+!Ew*)Z{Lw3X&zXiSXN_bDxvu_pDnyt-a>XdW%0CtZ?ss^ zWoSC%`m_*&QKA$mJCj3tRwS)ZqLMf?Qy_3wtQ7uVIDPfZ@3rRNaHRv5fSONe2n)G| z0uP?ktUg}t2|~hS3~O{S0fyY3zLsyQUL9})5m$_m905k0>0Br23B`O>hjP9z@m z+IhPq(koIGQ2+JVKF(1jas}gj@?fb!`B_u*cOG~ZulBoucC#OM_IY#^P@4_jMKkys zc0G$@+MochK&qWA(mV`4D$?a}=QUEqSM{F)EL)|L{V5Zua!hCoswwRla;kGoaO?T{ zLOTX65yOL`Eguo{QOvGJ=%)oGIYR=dHjE?)ubu|RF)&C5#Pc&qTFA%_BE+kE0@ot7 z^e1zH0?uv-v5~NjmLtb_=X|;Kw;wds?Zrm4sM%*9iLlS1^#nuuf z6?Dp4dDTAvFUm7iB$+CNf`(q;)c%ltAKi_sY*{U)BtTzt2bq{-tQAsIr>zq-lx4ws zslOy{QG`v|S*PwGhZ-BAPHP8#l_2;b(aVS0!b+$5X0YX`&!d9wboNE%yTpwbofT?> zp}`&FU&-=m;Uj5}Xi#F`vC&{8p4mUbN#(AlCdhn!OhuCWj-Bd3;h6&^RVjDPL)yyM zClm~|?>Hz}8qQulU~l2BCFkyV{f&&j=bfW>IzianxYel;(WE)C0@0KSSdVDhkk6KA zMo;=J(FX^_+Xu5I*1{BXYMR2t^UAP{iv`7`2MdV>l3%#J4PvtEt=a?FU0!gZrgK}k z!!_nKYkg$VHJE4`ZK&r*j<`e$dAuUJ9`O?3wm~AVc!hA6V4yF$mGZ2!$rlauqh}i> z!xerPg+d5^uecNwe!skB1?~VPM-TJ`Q*8b#X5+IW!NZrO*MgXBzI<;mdJ|n+(FY3~ zzoU&dTneF04)eW5{jlWPhO%(jc#FIYcO{IxM#TSPy-U@z9dT2xNn~r+<%bBu7Z-jn z__I*A4!En-raREB-1Hr2zTgH2Tk%kkGJUKNBJ{S>`%Np9SCht0A#CNli(>V-W!Uu*$O0MLM{$bb%~f*zttDl07$xb+sS z9<={lEXS2D!vC{pR2i{;sA-$DK^WnbW^cQu{9ZT+#izC#Inq5kU( zhi!egm}+{1w*ki%2Hx)MMF#$M4m$?H#?@fMaP?zL!)RIdV#9b5hh4*Dj%o&@bh=|J zqb22m-lBl`lq|v`ih{j>h%Ms9QfkG?663l8>E80tP1M|inv|{m@J<2OJj&Uz!gX_#fIRJIlYV)zRy(dWnb?5QhfZpfJ2!VRL8N)z*g<1!| z{vx+l;6QP37;vy8Sr<4oW&II2Jlk9b94Rk80E|{Fv;xK|cSksF81Kka2gHWW9VTn} z!?PKYv_v5dpexLEzN8Z?BS@87IU8veeKExwgh`a+` znC?ful<3*WQqtYHDMx-_`D|2;fo{U)BY(EAY;+6h9@535fX%vz~o?Jfh#Dt3Yn8*a6`w0i5g$jkEMhZqF z#R|j&gVf?>(^FLg{!kTiuL%9GfBF9GC>m>Pxj0_wX*ofAx$Gw?Ztv6=Xh7efegZaBwh>Ge>4ch}rVVY|!RC{bg+ zjX*(=n`!9X2Wcu_B}s`qI=iSXj!vKb@B+~?|tz^rB;;j^$`rWNm z`#y&4G^bgs?R3|z;_Zw#=eygPP(;R^EI&f)o$MgGlAW9|p1qyiC`rcMyf}61-TWl; zlHGzd=e^y+tN_NnqPzs_z2c&RlD(3$`n|o#e=Ap=v zcICzyTQa~UEf($;phK=1iqMd;KNaQ8YErls|0f4+;vYU}yaz`;*iy{L@9{Nkj(dqM z%8vWUU4Er9u>AW?kl$9vS{`nw{L}%3L!*#ytXk^{!ei1OX{=uFk9s7RA>UN9Ih@4# zVr8VMc6&TaJd{GAxo&s5NVQadwE5lsT$RaihC)mIm!(F>{gu&{hQm*tzGx2>TN{tp z2V$8F##)=cZBOLNWh%BcpY8vChMf49d5lF7xT1+8mL2Sjkyp2(h10TQ@CF=P?b5=* z0}Lr#O@7PMH2L>A?oL=LYjw zvq6Dy(8Tpf2qMEqlmwyGMzl0t@kWe1&+bO-@0fj^e}ic?roA7<`%(NmqZn-XvnrRk z8vqxhk>yPZh+6eQ_k?D>#g@DT`V)p=ce*2(?yLn;27o^Y(Ii-Y4h9z#eGXx+-}xNM z)(2h>Philips Hue Hub flex-direction: column; justify-content: center; " class="content"> -

      Philips Hue er vinsælasta snjallljósa lausn heims. - Með því að tengja Philips Hue miðstöðina þína getur þú beðið emblu um að kveikja og slökka á ljósum, breyta lit, birtustigi og fleira.

      - +

      Philips Hue er vinsælasta snjallljósa lausn heims. + Með því að tengja Philips Hue miðstöðina þína getur þú beðið emblu um að kveikja og slökka á ljósum, breyta + lit, birtustigi og fleira.

      +

      Leiðbeiningar

      1. Tengdu Philips Hue miðstöðina með netsnúru.
      2. @@ -50,7 +51,7 @@

        Leiðbeiningar

        font-family: 'Lato', sans-serif; padding: 10px 20px; box-shadow: 0px 2px 5px -1px rgba(0,0,0,0.3), inset 0px 12px 0px -10px rgba(255,255,255,0.3); - font-size: 16px;" onclick="syncConnectHub();">Tengja + font-size: 19px;" onclick="syncConnectHub();">Tengja
      {% block content %} {% endblock %} @@ -86,6 +88,32 @@

      {{ display_name }}

      // console.log("Error while deleting info from database: " + err); // }); } + + function createRipple(event) { + const button = event.currentTarget; + + const circle = document.createElement("span"); + const diameter = Math.max(button.clientWidth, button.clientHeight); + const radius = diameter / 2; + + circle.style.width = circle.style.height = `${diameter}px`; + circle.style.left = `${event.clientX - button.offsetLeft - radius}px`; + circle.style.top = `${event.clientY - button.offsetTop - radius}px`; + circle.classList.add("ripple"); + + const ripple = button.getElementsByClassName("ripple")[0]; + + if (ripple) { + ripple.remove(); + } + + button.appendChild(circle); + } + + const buttons = document.getElementsByTagName("button"); + for (const button of buttons) { + button.addEventListener("click", createRipple); + } \ No newline at end of file diff --git a/templates/sonos-connection.html b/templates/sonos-connection.html index c03eead5..54fe192d 100644 --- a/templates/sonos-connection.html +++ b/templates/sonos-connection.html @@ -37,7 +37,8 @@

      Leiðbeiningar

    3. Veldu "Tengja" hér að neðan og skráðu þig inn með Sonos aðgangi þínum.
    - + @@ -62,6 +63,32 @@

    Leiðbeiningar

    console.log('Click happened'); window.location.href = connectUrl; } + + function createRipple(event) { + const button = event.currentTarget; + + const circle = document.createElement("span"); + const diameter = Math.max(button.clientWidth, button.clientHeight); + const radius = diameter / 2; + + circle.style.width = circle.style.height = `${diameter}px`; + circle.style.left = `${event.clientX - button.offsetLeft - radius}px`; + circle.style.top = `${event.clientY - button.offsetTop - radius}px`; + circle.classList.add("ripple"); + + const ripple = button.getElementsByClassName("ripple")[0]; + + if (ripple) { + ripple.remove(); + } + + button.appendChild(circle); + } + + const buttons = document.getElementsByTagName("button"); + for (const button of buttons) { + button.addEventListener("click", createRipple); + } diff --git a/templates/spotify-connection.html b/templates/spotify-connection.html index eab0bc79..9d73682c 100644 --- a/templates/spotify-connection.html +++ b/templates/spotify-connection.html @@ -34,7 +34,9 @@

    Leiðbeiningar

  • Veldu "Tengja" hér að neðan og skráðu þig inn með Spotify aðgang þínum
  • - + + From 94e52d81543a3b0c2bec48f669327889471039e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Tue, 16 Aug 2022 10:15:35 +0000 Subject: [PATCH 326/371] Error page now shown if connection failed --- routes/api.py | 4 +- routes/main.py | 6 +++ static/img/error_outline.svg | 1 + templates/hue-connection.html | 1 + templates/iot-connect-error.html | 74 ++++++++++++++++++++++++++++++++ 5 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 static/img/error_outline.svg create mode 100644 templates/iot-connect-error.html diff --git a/routes/api.py b/routes/api.py index 8420b27c..5474f1be 100755 --- a/routes/api.py +++ b/routes/api.py @@ -763,6 +763,7 @@ def sonos_code(version: int = 1) -> Response: # ) # Send the above message to the Sonos speaker return render_template("iot-connect-success.html", title="Tenging tókst") return better_jsonify(valid=True, msg="Registered sonos code") + return render_template("iot-connect-error.html", title="Tenging mistókst") return better_jsonify(valid=False, errmsg="Error registering sonos code.") @@ -794,7 +795,7 @@ def smartthings_code(version: int = 1) -> Response: @routes.route("/connect_spotify.api", methods=["GET"]) -@routes.route("/connect_spotofy.api/v", methods=["GET", "POST"]) +@routes.route("/connect_spotify.api/v", methods=["GET", "POST"]) def spotify_code(version: int = 1) -> Response: """ API endpoint to connect Spotify account @@ -816,6 +817,7 @@ def spotify_code(version: int = 1) -> Response: # Create an instance of the SonosClient class. This will automatically create the rest of the credentials needed. return render_template("iot-connect-success.html", title="Tenging tókst") return better_jsonify(valid=True, msg="Registered spotify code") + return render_template("iot-connect-error.html", title="Tenging mistókst") return better_jsonify(valid=False, errmsg="Error registering spotify code.") diff --git a/routes/main.py b/routes/main.py index 6e782ae7..22b31c4b 100755 --- a/routes/main.py +++ b/routes/main.py @@ -140,6 +140,12 @@ def iot_connect_success(): return render_template("iot-connect-success.html", title="Tenging tókst") +@routes.route("/iot-connect-error") +def iot_connect_error(): + """Handler for unsuccessful connection view.""" + return render_template("iot-connect-error.html", title="Tenging mistókst") + + @routes.route("/correct", methods=["GET", "POST"]) def correct(): """Handler for a page for spelling and grammar correction diff --git a/static/img/error_outline.svg b/static/img/error_outline.svg new file mode 100644 index 00000000..e4dd61f4 --- /dev/null +++ b/static/img/error_outline.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/hue-connection.html b/templates/hue-connection.html index e67a06a8..52b9aa29 100644 --- a/templates/hue-connection.html +++ b/templates/hue-connection.html @@ -165,6 +165,7 @@

    Leiðbeiningar

    return "Tenging við snjalltæki tókst"; } catch (error) { console.log(error); + window.location.href = `${requestURL}/iot-connect-error`; return "Ekki tókst að tengja snjalltæki"; } } diff --git a/templates/iot-connect-error.html b/templates/iot-connect-error.html new file mode 100644 index 00000000..d2ce212c --- /dev/null +++ b/templates/iot-connect-error.html @@ -0,0 +1,74 @@ + + + + + + + + + + Tenging mistókst + + + + + + + +
    + + Tenging mistókst + +

    Tenging mistókst.

    +

    Vinsamlegast reyndu aftur.

    + + + +
    + + + + + + + \ No newline at end of file From b59c7fc4d9e613470514c32d34c89bc5dffb53a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Tue, 16 Aug 2022 11:40:52 +0000 Subject: [PATCH 327/371] Error handling for too many requests and general hub errors --- templates/hue-connection.html | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/templates/hue-connection.html b/templates/hue-connection.html index 52b9aa29..e4653ea5 100644 --- a/templates/hue-connection.html +++ b/templates/hue-connection.html @@ -81,13 +81,23 @@

    Leiðbeiningar

    // hubArr.push(hubObj); // return hubArr[0]; return fetch(`https://discovery.meethue.com`) - .then((resp) => resp.json()) + .then((resp) => { + console.log("Resp: ", resp); + if (!resp.ok) { + console.log("Resp status: ", resp.status); + throw new Error(resp.status); + } + resp.json(); + }) .then((obj) => { console.log(obj); return obj[0]; }) .catch((err) => { - console.log("No smart device found!"); + console.log("No smart device found!", err.message); + window.flutter_inappwebview.callHandler("flutter_handler", { + "hub_error": parseInt(err.message), + }); }); } @@ -138,6 +148,9 @@

    Leiðbeiningar

    let username = await createNewDeveloper(deviceInfo.internalipaddress); console.log("username: ", username); if (!username.success) { + window.flutter_inappwebview.callHandler("flutter_handler", { + "button_press_missing": true, + }); return "Ýttu á 'Philips' takkann á tengiboxinu og reyndu aftur"; } From f2de94d25754c8fd88f0afccfa681c33e6a0bcb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Wed, 17 Aug 2022 11:13:54 +0000 Subject: [PATCH 328/371] Removed commented out code --- static/css/style.css | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/static/css/style.css b/static/css/style.css index d6b99f05..78880fbb 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -99,22 +99,6 @@ button { cursor: pointer; } -/* Ripple effect */ -/* .btn { - background-position: center; - transition: background 0.8s; -} - -.btn:hover { - background: #d89b9978 radial-gradient(circle, transparent 1%, #d89b9978 1%) center/15000%; -} - -.btn:active { - background-color: #d89b99ba; - background-size: 100%; - transition: background 0s; -} */ - span.ripple { position: absolute; border-radius: 50%; From 428b248a94d5de2f50b62fdde547929171658884 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Wed, 17 Aug 2022 11:16:30 +0000 Subject: [PATCH 329/371] Added dark mode and made mdns search use ip instead of findHub --- templates/hue-connection.html | 60 ++++++++++++++++------------------- 1 file changed, 28 insertions(+), 32 deletions(-) diff --git a/templates/hue-connection.html b/templates/hue-connection.html index e4653ea5..320ebe44 100644 --- a/templates/hue-connection.html +++ b/templates/hue-connection.html @@ -7,6 +7,13 @@ + Philips Hue @@ -41,32 +48,7 @@

    Leiðbeiningar

    - - - Tenging mistókst diff --git a/templates/iot-connect-success.html b/templates/iot-connect-success.html index 3c2546b4..aac5a1fe 100644 --- a/templates/iot-connect-success.html +++ b/templates/iot-connect-success.html @@ -7,9 +7,19 @@ + + Tenging tókst - - From 1e1c69171f9837107e9a8af977c4fda66b267cea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Wed, 17 Aug 2022 11:17:36 +0000 Subject: [PATCH 331/371] Added darkmode --- templates/iot-info.html | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/templates/iot-info.html b/templates/iot-info.html index 276c628d..2034719e 100644 --- a/templates/iot-info.html +++ b/templates/iot-info.html @@ -7,6 +7,13 @@ + {{ iot_name }} upplýsingar + + Sonos @@ -38,19 +44,6 @@

    Leiðbeiningar

    - @@ -58,7 +51,6 @@

    Leiðbeiningar

    const queryParams = new URLSearchParams(window.location.search); const connectUrl = decodeURIComponent(queryParams.get('connect_url')); let connectButton = document.getElementById('connect_button'); - console.log('connect url: ', connectUrl); connectButton.onclick = function () { console.log('Click happened'); window.location.href = connectUrl; diff --git a/templates/spotify-connection.html b/templates/spotify-connection.html index 9d73682c..f7390d9b 100644 --- a/templates/spotify-connection.html +++ b/templates/spotify-connection.html @@ -7,8 +7,14 @@ - Philips Hue - + + Spotify @@ -35,20 +41,6 @@

    Leiðbeiningar

    - - + button.appendChild(circle); + } + + const buttons = document.getElementsByTagName("button"); + for (const button of buttons) { + button.addEventListener("click", createRipple); + } + From dcca7b134019ae14f05dc1d85d3ebd2cc9137903 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Wed, 17 Aug 2022 13:11:00 +0000 Subject: [PATCH 335/371] Fixed philips hue connection process obj being undefined --- templates/hue-connection.html | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/templates/hue-connection.html b/templates/hue-connection.html index b1f681a3..cf1c7c83 100644 --- a/templates/hue-connection.html +++ b/templates/hue-connection.html @@ -58,18 +58,12 @@

    Leiðbeiningar

    // return hubArr[0]; return fetch(`https://discovery.meethue.com`) .then((resp) => { - console.log("Resp: ", resp); - console.log("Resp body: ", resp.body); if (!resp.ok) { - console.log("Resp status: ", resp.status); throw new Error(resp.status); } - console.log("Making json"); - resp.json(); - console.log("json made"); + return resp.json(); }) .then((obj) => { - console.log(obj); if (obj) { return obj[0]; } else { From a97a955ee28ac14e74f92a051f05ded0fc208791 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Wed, 17 Aug 2022 14:43:51 +0000 Subject: [PATCH 336/371] Fixed error page displaying for spotify when successfully connected --- routes/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routes/api.py b/routes/api.py index e4c62d25..5474f1be 100755 --- a/routes/api.py +++ b/routes/api.py @@ -815,9 +815,9 @@ def spotify_code(version: int = 1) -> Response: device_data = code_dict.get("iot_streaming").get("spotify") spotify_client = SpotifyClient(device_data, client_id) # Create an instance of the SonosClient class. This will automatically create the rest of the credentials needed. - return render_template("iot-connect-error.html", title="Tenging mistókst") return render_template("iot-connect-success.html", title="Tenging tókst") return better_jsonify(valid=True, msg="Registered spotify code") + return render_template("iot-connect-error.html", title="Tenging mistókst") return better_jsonify(valid=False, errmsg="Error registering spotify code.") From f8206b2fe9510e0094a90f3ee9ac25df7f62c502 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Mon, 22 Aug 2022 12:59:08 +0000 Subject: [PATCH 337/371] needs_confirmation added to toml and dialogue, still needs to be added to fruitseller and theater toml --- queries/dialogue.py | 3 +++ queries/dialogues/fruitseller.toml | 2 ++ queries/dialogues/pizza.toml | 1 + queries/dialogues/theater.toml | 1 + queries/pizza.py | 4 ++-- queries/resources.py | 3 +++ 6 files changed, 12 insertions(+), 2 deletions(-) diff --git a/queries/dialogue.py b/queries/dialogue.py index 5ee65ca5..9faf5618 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -489,6 +489,9 @@ def set_resource_state(self, resource_name: str, state: ResourceState): resource = self._resources[resource_name] lowered_state = resource.state > state resource.state = state + if state == ResourceState.FULFILLED and not resource.needs_confirmation: + resource.state = ResourceState.CONFIRMED + return if resource.cascade_state and lowered_state: # Find all parent resources and set to corresponding state ancestors = set(self.get_ancestors(resource)) diff --git a/queries/dialogues/fruitseller.toml b/queries/dialogues/fruitseller.toml index 0363a099..ad9e9f72 100644 --- a/queries/dialogues/fruitseller.toml +++ b/queries/dialogues/fruitseller.toml @@ -47,3 +47,5 @@ requires = ["DateTime"] prompts.final = "Pöntunin þín er {fruits} og verður afhent {date_time}." prompts.cancelled = "Móttekið, hætti við pöntunina." prompts.timed_out = "Ávaxtapöntunin þín rann út á tíma. Vinsamlegast byrjaðu aftur." + +# TODO: Add needs_confirmation where appropriate diff --git a/queries/dialogues/pizza.toml b/queries/dialogues/pizza.toml index 8230b47c..47da9aa8 100644 --- a/queries/dialogues/pizza.toml +++ b/queries/dialogues/pizza.toml @@ -2,6 +2,7 @@ name = "PizzaOrder" type = "WrapperResource" #requires = ["Pizzas"] #, "Sides", "Sauces", "Drinks"] +needs_confirmation = true prompts.initial = "Hvað má bjóða þér?" prompts.added_pizzas = "{pizzas} var bætt við pöntunina. Pöntunin inniheldur {total_pizzas}. Var það eitthvað fleira?" prompts.confirmed_pizzas = "Var það eitthvað fleira?" diff --git a/queries/dialogues/theater.toml b/queries/dialogues/theater.toml index 7a28e689..411d8109 100644 --- a/queries/dialogues/theater.toml +++ b/queries/dialogues/theater.toml @@ -72,4 +72,5 @@ prompts.final = "Þú bókaðir sæti {seats} í röð {row} fyrir sýninguna {s prompts.cancelled = "Móttekið, hætti við leikhús pöntunina." prompts.timed_out = "Leikhúsmiðapöntunin þín rann út á tíma. Vinsamlegast byrjaðu aftur." +# TODO: Add needs_confirmation where appropriate # TODO: Add a resource for the payment method diff --git a/queries/pizza.py b/queries/pizza.py index 7877abe7..0a572034 100644 --- a/queries/pizza.py +++ b/queries/pizza.py @@ -312,8 +312,8 @@ def QPizzaNumberAndSpecificationWrapper( crust_resource.data = crust dsm.set_resource_state(crust_resource.name, ResourceState.CONFIRMED) dsm.update_wrapper_state(pizza_resource) - if pizza_resource.state == ResourceState.FULFILLED: - dsm.set_resource_state(pizza_resource.name, ResourceState.CONFIRMED) + if pizza_resource.state == ResourceState.CONFIRMED: + # dsm.set_resource_state(pizza_resource.name, ResourceState.CONFIRMED) dsm.extras["confirmed_pizzas"] = dsm.extras.get("confirmed_pizzas", 0) + 1 dsm.extras["added_pizzas"] = dsm.extras.get("added_pizzas", 0) + 1 result["new_pizza"] = pizza_resource diff --git a/queries/resources.py b/queries/resources.py index 5fa02415..119148b0 100644 --- a/queries/resources.py +++ b/queries/resources.py @@ -66,6 +66,9 @@ class Resource: # When set to True, this resource will be used # as the current resource instead of its wrapper prefer_over_wrapper: bool = False + # When set to True, this resource will need + # to be confirmed before moving on to the next resource + needs_confirmation: bool = False # Used for comparing states (which one is earlier/later in the dialogue) order_index: int = 0 # Extra variables to be used for specific situations From bb4d7a13936277998dac8f203c07a6c5ac31cbd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Mon, 22 Aug 2022 15:07:22 +0000 Subject: [PATCH 338/371] Fixed theater module date selection not working. Also temporarily removed banned_nonterminals until we get access to active_dialogue again when processing banned_nonterminals --- queries/dialogues/fruitseller.toml | 2 + queries/dialogues/theater.toml | 7 ++ queries/fruitseller.py | 27 +++--- queries/resources.py | 4 +- queries/theater.py | 145 ++++++++++++++--------------- 5 files changed, 97 insertions(+), 88 deletions(-) diff --git a/queries/dialogues/fruitseller.toml b/queries/dialogues/fruitseller.toml index ad9e9f72..b2e0faf7 100644 --- a/queries/dialogues/fruitseller.toml +++ b/queries/dialogues/fruitseller.toml @@ -3,6 +3,7 @@ expiration_time = 900 # 15 minutes [[resources]] name = "Fruits" type = "ListResource" +needs_confirmation = true # TODO: Keep singular and plural forms of fruits (or options more generally) for formatting answer? prompts.initial = "Hvaða ávexti má bjóða þér?" prompts.options = "Ávextirnir sem eru í boði eru appelsínur, bananar, epli og perur." @@ -24,6 +25,7 @@ requires = ["Fruits"] name = "DateTime" type = "WrapperResource" requires = ["Date", "Time"] +needs_confirmation = true prompts.initial = "Hvenær viltu fá ávextina?" prompts.time_fulfilled = "Afhendingin verður klukkan {time}. Hvaða dagsetningu viltu fá ávextina?" prompts.date_fulfilled = "Afhendingin verður {date}. Klukkan hvað viltu fá ávextina?" diff --git a/queries/dialogues/theater.toml b/queries/dialogues/theater.toml index 411d8109..062d58db 100644 --- a/queries/dialogues/theater.toml +++ b/queries/dialogues/theater.toml @@ -2,6 +2,7 @@ name = "Show" type = "ListResource" cascade_state = true +needs_confirmation = true prompts.initial = "Hvaða sýningu má bjóða þér að fara á?" prompts.options = "Sýningarnar sem eru í boði eru: {options}" prompts.confirm = "Þú valdir sýninguna {show}, viltu halda áfram með pöntunina?" @@ -11,11 +12,13 @@ prompts.no_show_matched_data_exists = "Því miður er þessi sýning ekki í bo [[resources]] name = "ShowDate" type = "DateResource" +needs_confirmation = true requires = ["Show"] [[resources]] name = "ShowTime" type = "TimeResource" +needs_confirmation = true requires = ["Show"] [[resources]] @@ -23,6 +26,7 @@ name = "ShowDateTime" type = "WrapperResource" requires = ["ShowDate", "ShowTime"] cascade_state = true +needs_confirmation = true prompts.initial = "Hvenær viltu fara á sýninguna {show}? Í boði eru {date_number} dagsetningar.\n{dates}" prompts.options = "Í boði eru {date_number} dagsetningar. {options}" prompts.confirm = "Þú valdir {date}, viltu halda áfram og velja fjölda sæta?" @@ -38,6 +42,7 @@ name = "ShowSeatCount" type = "NumberResource" requires = ["ShowDateTime"] cascade_state = true +needs_confirmation = true prompts.initial = "Hversu mörg sæti viltu bóka?" prompts.confirm = "Þú valdir {seats} sæti, viltu halda áfram og velja staðsetningu sætanna?" prompts.invalid_seat_count = "Fjöldi sæta þarfa að vera hærri en einn. Vinsamlegast reyndu aftur." @@ -47,6 +52,7 @@ name = "ShowSeatRow" type = "ListResource" requires = ["ShowSeatCount"] cascade_state = true +needs_confirmation = true prompts.initial = "Að minnsta kosti {seats} sæti eru í boði í röðum {seat_rows}. Í hvaða röð viltu sitja?" prompts.options = "Raðir {rows} eru með {seats} laus sæti." prompts.confirm = "Þú valdir röð {row}, viltu halda áfram?" @@ -58,6 +64,7 @@ name = "ShowSeatNumber" type = "ListResource" requires = ["ShowSeatRow"] cascade_state = true +needs_confirmation = true prompts.initial = "Sæti {seats} eru í boði í röð {row}, hvaða sæti má bjóða þér?" prompts.options = "Sætin sem eru í boði í röð {row} eru {options}" prompts.confirm = "Þú valdir sæti {seats}, viltu halda áfram?" diff --git a/queries/fruitseller.py b/queries/fruitseller.py index 55d99e80..6bca236c 100644 --- a/queries/fruitseller.py +++ b/queries/fruitseller.py @@ -52,19 +52,20 @@ def banned_nonterminals(q: Query) -> Set[str]: allowed due to the state of the dialogue """ banned_nonterminals: set[str] = set() - if q.dsm.dialogue_name != DIALOGUE_NAME: - banned_nonterminals.add("QFruitSellerQuery") - return banned_nonterminals - resource: Resource = q.dsm.current_resource - if resource.name == "Fruits": - banned_nonterminals.add("QFruitDateQuery") - if resource.is_unfulfilled: - banned_nonterminals.add("QFruitYes") - banned_nonterminals.add("QFruitNo") - elif resource.name == "DateTime": - if resource.is_unfulfilled: - banned_nonterminals.add("QFruitYes") - banned_nonterminals.add("QFruitNo") + # TODO: Put this back in when the dsm has access to the active dialogue again. + # if q.dsm.dialogue_name != DIALOGUE_NAME: + # banned_nonterminals.add("QFruitSellerQuery") + # return banned_nonterminals + # resource: Resource = q.dsm.current_resource + # if resource.name == "Fruits": + # banned_nonterminals.add("QFruitDateQuery") + # if resource.is_unfulfilled: + # banned_nonterminals.add("QFruitYes") + # banned_nonterminals.add("QFruitNo") + # elif resource.name == "DateTime": + # if resource.is_unfulfilled: + # banned_nonterminals.add("QFruitYes") + # banned_nonterminals.add("QFruitNo") return banned_nonterminals diff --git a/queries/resources.py b/queries/resources.py index 119148b0..af76b80e 100644 --- a/queries/resources.py +++ b/queries/resources.py @@ -190,7 +190,7 @@ class DateResource(Resource): @property def date(self) -> Optional[datetime.date]: - return self.data if self.is_fulfilled else None + return self.data if not self.is_unfulfilled else None def set_date(self, new_date: datetime.date) -> None: self.data = new_date @@ -209,7 +209,7 @@ class TimeResource(Resource): @property def time(self) -> Optional[datetime.time]: - return self.data if self.is_fulfilled else None + return self.data if self.is_unfulfilled else None def set_time(self, new_time: datetime.time) -> None: self.data = new_time diff --git a/queries/theater.py b/queries/theater.py index 36d03dee..27b48e39 100644 --- a/queries/theater.py +++ b/queries/theater.py @@ -90,76 +90,77 @@ def banned_nonterminals(q: Query) -> Set[str]: allowed due to the state of the dialogue """ banned_nonterminals: set[str] = set() - if q.dsm.dialogue_name != DIALOGUE_NAME: - banned_nonterminals.add("QTheaterQuery") - return banned_nonterminals - resource: Resource = q.dsm.current_resource - if resource.name == "Show": - banned_nonterminals.update( - { - "QTheaterShowDateQuery", - "QTheaterMoreDates", - "QTheaterPreviousDates", - "QTheaterShowSeatCountQuery", - "QTheaterShowLocationQuery", - "QTheaterDateOptions", - "QTheaterRowOptions", - "QTheaterSeatOptions", - } - ) - if resource.is_unfulfilled: - banned_nonterminals.update( - { - "QTheaterShowLength", - "QTheaterShowPrice", - } - ) - elif resource.name == "ShowDateTime": - banned_nonterminals.update( - { - "QTheaterShowSeatCountQuery", - "QTheaterShowLocationQuery", - "QTheaterRowOptions", - "QTheaterSeatOptions", - "QTheaterShowOnlyName", - } - ) - elif resource.name == "ShowSeatCount": - banned_nonterminals.update( - { - "QTheaterShowLocationQuery", - "QTheaterRowOptions", - "QTheaterSeatOptions", - "QTheaterRowNum", - "QTheaterShowSeatsNum", - "QTheaterShowOnlyName", - } - ) - elif resource.name == "ShowSeatRow": - banned_nonterminals.update( - { - "QTheaterShowSeats", - "QTheaterSeatCountNum", - "QTheaterShowSeatsNum", - "QTheaterSeatOptions", - "QTheaterShowOnlyName", - } - ) - elif resource.name == "ShowSeatNumber": - banned_nonterminals.update( - { - "QTheaterSeatCountNum", - "QTheaterRowNum", - "QTheaterShowOnlyName", - } - ) - if resource.is_unfulfilled: - banned_nonterminals.update( - { - "QTheaterYes", - "QTheaterNo", - } - ) + # TODO: Put this back in when the dsm has access to the active dialogue again. + # if q.dsm.dialogue_name != DIALOGUE_NAME: + # banned_nonterminals.add("QTheaterQuery") + # return banned_nonterminals + # resource: Resource = q.dsm.current_resource + # if resource.name == "Show": + # banned_nonterminals.update( + # { + # "QTheaterShowDateQuery", + # "QTheaterMoreDates", + # "QTheaterPreviousDates", + # "QTheaterShowSeatCountQuery", + # "QTheaterShowLocationQuery", + # "QTheaterDateOptions", + # "QTheaterRowOptions", + # "QTheaterSeatOptions", + # } + # ) + # if resource.is_unfulfilled: + # banned_nonterminals.update( + # { + # "QTheaterShowLength", + # "QTheaterShowPrice", + # } + # ) + # elif resource.name == "ShowDateTime": + # banned_nonterminals.update( + # { + # "QTheaterShowSeatCountQuery", + # "QTheaterShowLocationQuery", + # "QTheaterRowOptions", + # "QTheaterSeatOptions", + # "QTheaterShowOnlyName", + # } + # ) + # elif resource.name == "ShowSeatCount": + # banned_nonterminals.update( + # { + # "QTheaterShowLocationQuery", + # "QTheaterRowOptions", + # "QTheaterSeatOptions", + # "QTheaterRowNum", + # "QTheaterShowSeatsNum", + # "QTheaterShowOnlyName", + # } + # ) + # elif resource.name == "ShowSeatRow": + # banned_nonterminals.update( + # { + # "QTheaterShowSeats", + # "QTheaterSeatCountNum", + # "QTheaterShowSeatsNum", + # "QTheaterSeatOptions", + # "QTheaterShowOnlyName", + # } + # ) + # elif resource.name == "ShowSeatNumber": + # banned_nonterminals.update( + # { + # "QTheaterSeatCountNum", + # "QTheaterRowNum", + # "QTheaterShowOnlyName", + # } + # ) + # if resource.is_unfulfilled: + # banned_nonterminals.update( + # { + # "QTheaterYes", + # "QTheaterNo", + # } + # ) return banned_nonterminals @@ -596,7 +597,6 @@ def _add_date( show_times.append(date.time()) if len(show_times) == 0: dsm.set_answer(gen_answer(datetime_resource.prompts["no_date_matched"])) - # result.no_date_matched = True return if len(show_times) == 1: time_resource.set_time(show_times[0]) @@ -624,7 +624,7 @@ def _add_time( if show["title"].lower() == show_title.lower(): for date in show["date"]: if ( - date_resource.date == date.date() + date_resource.data == date.date() and result["show_time"] == date.time() ): first_matching_date = date @@ -708,7 +708,6 @@ def QTheaterDate(node: Node, params: QueryStateDict, result: Result) -> None: if m < now.month or (m == now.month and d < now.day): y += 1 result["show_date"] = datetime.date(day=d, month=m, year=y) - dsm: DialogueStateManager = Query.get_dsm(result) _add_date(cast(DateResource, dsm.get_resource("ShowDate")), dsm, result) return From 7ae7c92fc965c2725e94ec467f36201740f70ea9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Mon, 22 Aug 2022 15:41:51 +0000 Subject: [PATCH 339/371] Model for dialoguedata --- db/models.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/db/models.py b/db/models.py index be183cda..e57183e4 100755 --- a/db/models.py +++ b/db/models.py @@ -737,6 +737,33 @@ def __repr__(self): ) +class DialogueData(Base): + """Represents dialogue data for a given client.""" + + __tablename__ = "dialoguedata" + + __table_args__ = (PrimaryKeyConstraint("client_id", name="dialoguedata_pkey"),) + + client_id = Column(String(256), nullable=False) + + # Dialogue key to distinguish between different dialogues that can be stored + dialogue_key = Column(String(64), nullable=False) + + # Created timestamp + created = Column(DateTime, nullable=False) + + # Last modified timestamp + modified = Column(DateTime, nullable=False) + + # JSON data + data = Column(JSONB, nullable=False) + + def __repr__(self): + return "DialogueData(client_id='{0}', created='{1}', modified='{2}', dialogue_key='{3}', data='{4}')".format( + self.client_id, self.created, self.modified, self.dialogue_key, self.data + ) + + class Feedback(Base): """Represents a feedback form submission.""" From 59d2b4a1ed54e53513ff1d43fcca166d6fe3f575 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Tue, 23 Aug 2022 13:45:56 +0000 Subject: [PATCH 340/371] Added dialogue_key as a part of the primary key for the dialoguedata table --- db/models.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/db/models.py b/db/models.py index e57183e4..e632bcd0 100755 --- a/db/models.py +++ b/db/models.py @@ -742,7 +742,9 @@ class DialogueData(Base): __tablename__ = "dialoguedata" - __table_args__ = (PrimaryKeyConstraint("client_id", name="dialoguedata_pkey"),) + __table_args__ = ( + PrimaryKeyConstraint("client_id", "dialogue_key", name="dialoguedata_pkey"), + ) client_id = Column(String(256), nullable=False) From 44db60a9c0ad538beac046d7447af957943f740d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Tue, 23 Aug 2022 13:46:26 +0000 Subject: [PATCH 341/371] Added banned_nonterminals back in --- queries/fruitseller.py | 27 ++++---- queries/theater.py | 140 ++++++++++++++++++++--------------------- 2 files changed, 84 insertions(+), 83 deletions(-) diff --git a/queries/fruitseller.py b/queries/fruitseller.py index 6bca236c..f1b01fe2 100644 --- a/queries/fruitseller.py +++ b/queries/fruitseller.py @@ -53,19 +53,20 @@ def banned_nonterminals(q: Query) -> Set[str]: """ banned_nonterminals: set[str] = set() # TODO: Put this back in when the dsm has access to the active dialogue again. - # if q.dsm.dialogue_name != DIALOGUE_NAME: - # banned_nonterminals.add("QFruitSellerQuery") - # return banned_nonterminals - # resource: Resource = q.dsm.current_resource - # if resource.name == "Fruits": - # banned_nonterminals.add("QFruitDateQuery") - # if resource.is_unfulfilled: - # banned_nonterminals.add("QFruitYes") - # banned_nonterminals.add("QFruitNo") - # elif resource.name == "DateTime": - # if resource.is_unfulfilled: - # banned_nonterminals.add("QFruitYes") - # banned_nonterminals.add("QFruitNo") + print("Fruitseller dsm dialogue name: ", q.dsm.dialogue_name) + if q.active_dialogue != DIALOGUE_NAME: + banned_nonterminals.add("QFruitSellerQuery") + return banned_nonterminals + resource: Resource = q.dsm.current_resource + if resource.name == "Fruits": + banned_nonterminals.add("QFruitDateQuery") + if resource.is_unfulfilled: + banned_nonterminals.add("QFruitYes") + banned_nonterminals.add("QFruitNo") + elif resource.name == "DateTime": + if resource.is_unfulfilled: + banned_nonterminals.add("QFruitYes") + banned_nonterminals.add("QFruitNo") return banned_nonterminals diff --git a/queries/theater.py b/queries/theater.py index 27b48e39..89d18c32 100644 --- a/queries/theater.py +++ b/queries/theater.py @@ -91,76 +91,76 @@ def banned_nonterminals(q: Query) -> Set[str]: """ banned_nonterminals: set[str] = set() # TODO: Put this back in when the dsm has access to the active dialogue again. - # if q.dsm.dialogue_name != DIALOGUE_NAME: - # banned_nonterminals.add("QTheaterQuery") - # return banned_nonterminals - # resource: Resource = q.dsm.current_resource - # if resource.name == "Show": - # banned_nonterminals.update( - # { - # "QTheaterShowDateQuery", - # "QTheaterMoreDates", - # "QTheaterPreviousDates", - # "QTheaterShowSeatCountQuery", - # "QTheaterShowLocationQuery", - # "QTheaterDateOptions", - # "QTheaterRowOptions", - # "QTheaterSeatOptions", - # } - # ) - # if resource.is_unfulfilled: - # banned_nonterminals.update( - # { - # "QTheaterShowLength", - # "QTheaterShowPrice", - # } - # ) - # elif resource.name == "ShowDateTime": - # banned_nonterminals.update( - # { - # "QTheaterShowSeatCountQuery", - # "QTheaterShowLocationQuery", - # "QTheaterRowOptions", - # "QTheaterSeatOptions", - # "QTheaterShowOnlyName", - # } - # ) - # elif resource.name == "ShowSeatCount": - # banned_nonterminals.update( - # { - # "QTheaterShowLocationQuery", - # "QTheaterRowOptions", - # "QTheaterSeatOptions", - # "QTheaterRowNum", - # "QTheaterShowSeatsNum", - # "QTheaterShowOnlyName", - # } - # ) - # elif resource.name == "ShowSeatRow": - # banned_nonterminals.update( - # { - # "QTheaterShowSeats", - # "QTheaterSeatCountNum", - # "QTheaterShowSeatsNum", - # "QTheaterSeatOptions", - # "QTheaterShowOnlyName", - # } - # ) - # elif resource.name == "ShowSeatNumber": - # banned_nonterminals.update( - # { - # "QTheaterSeatCountNum", - # "QTheaterRowNum", - # "QTheaterShowOnlyName", - # } - # ) - # if resource.is_unfulfilled: - # banned_nonterminals.update( - # { - # "QTheaterYes", - # "QTheaterNo", - # } - # ) + if q.active_dialogue != DIALOGUE_NAME: + banned_nonterminals.add("QTheaterQuery") + return banned_nonterminals + resource: Resource = q.dsm.current_resource + if resource.name == "Show": + banned_nonterminals.update( + { + "QTheaterShowDateQuery", + "QTheaterMoreDates", + "QTheaterPreviousDates", + "QTheaterShowSeatCountQuery", + "QTheaterShowLocationQuery", + "QTheaterDateOptions", + "QTheaterRowOptions", + "QTheaterSeatOptions", + } + ) + if resource.is_unfulfilled: + banned_nonterminals.update( + { + "QTheaterShowLength", + "QTheaterShowPrice", + } + ) + elif resource.name == "ShowDateTime": + banned_nonterminals.update( + { + "QTheaterShowSeatCountQuery", + "QTheaterShowLocationQuery", + "QTheaterRowOptions", + "QTheaterSeatOptions", + "QTheaterShowOnlyName", + } + ) + elif resource.name == "ShowSeatCount": + banned_nonterminals.update( + { + "QTheaterShowLocationQuery", + "QTheaterRowOptions", + "QTheaterSeatOptions", + "QTheaterRowNum", + "QTheaterShowSeatsNum", + "QTheaterShowOnlyName", + } + ) + elif resource.name == "ShowSeatRow": + banned_nonterminals.update( + { + "QTheaterShowSeats", + "QTheaterSeatCountNum", + "QTheaterShowSeatsNum", + "QTheaterSeatOptions", + "QTheaterShowOnlyName", + } + ) + elif resource.name == "ShowSeatNumber": + banned_nonterminals.update( + { + "QTheaterSeatCountNum", + "QTheaterRowNum", + "QTheaterShowOnlyName", + } + ) + if resource.is_unfulfilled: + banned_nonterminals.update( + { + "QTheaterYes", + "QTheaterNo", + } + ) return banned_nonterminals From 4678ed4e307b0b3e8ba3dd6c7840ff8a5b8b1137 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Tue, 23 Aug 2022 13:48:15 +0000 Subject: [PATCH 342/371] Added handling for the dialoguedata table in the database. Now the current dialogue is loaded initially if present, only the active dialogue is used, and a dialogue processor only processes the incoming sentence if it is the active dialogue or if it was a hotword that came in --- query.py | 244 ++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 222 insertions(+), 22 deletions(-) diff --git a/query.py b/query.py index 1e818a11..d856debb 100755 --- a/query.py +++ b/query.py @@ -63,7 +63,7 @@ from db import SessionContext, Session, desc from db.models import Query as QueryRow -from db.models import QueryData, QueryLog +from db.models import QueryData, QueryLog, DialogueData from reynir import TOK, Tok, tokenize, correct_spaces from reynir.fastparser import ( @@ -97,6 +97,9 @@ # Client data ClientDataDict = Dict[str, Union[str, int, float, bool, Dict[str, str]]] +# Dialogue data +DialogueDatabaseDict = Dict[str, Union[str, int, float, bool, Dict[str, str]]] + # Answer tuple (corresponds to parameter list of Query.set_answer()) AnswerTuple = Tuple[ResponseType, str, Optional[str]] @@ -408,10 +411,27 @@ def __init__( # This should be a dict that can be represented in JSON self._context: Optional[ContextDict] = None # Dialogue state manager and dialogue data, used for dialogue modules - self._all_dialogue_data = ( - cast(DialogueDataDict, self.client_data(DSM.DIALOGUE_DATA_KEY)) or dict() + # The name of the active dialogue, None if no dialogue is active + active_dialogue_dict: Optional[DialogueDataDict] = self.client_data( + DSM.DIALOGUE_DATA_KEY + ) + self._active_dialogue: Optional[str] = ( + None + if not active_dialogue_dict + else (active_dialogue_dict.get("active_dialogue")) ) - self._dsm: DSM = DSM(self._all_dialogue_data) + # The active dialogue data, empty dict if no dialogue is active + self._dialogue_data: DialogueDataDict = ( + cast( + DialogueDataDict, self.dialogue_data(dialogue_key=self.active_dialogue) + ) + or dict() + ) + # The dialogue state manager + self._dsm: DSM = DSM(self._dialogue_data) + # Load the dialogue for the active dialogue if present + if self._active_dialogue: + self._dsm.load_dialogue(self._active_dialogue) def _preprocess_query_string(self, q: str) -> str: """Preprocess the query string prior to further analysis""" @@ -694,31 +714,43 @@ def execute_from_dialogue(self) -> bool: # hotword nonterminal for activating dialogue assert isinstance(hotword_nonterminals, set) assert len(hotword_nonterminals) > 0 - dialogue_data = cast( - Optional[str], self.all_dialogue_data.get(dialogue_name) - ) print( "CHECKING WHETHER PROCESSOR IS INTERESTED IN DIALOGUE:", not ( self._tree.query_nonterminals.isdisjoint( hotword_nonterminals ) - and dialogue_data is None + and dialogue_name != self._active_dialogue ), ) # Fetch saved dialogue state if ( self._tree.query_nonterminals.isdisjoint(hotword_nonterminals) - and dialogue_data is None + and dialogue_name != self._active_dialogue ): - print("NO DIALOGUE DATA AND NO HOTWORDS MATCHED") + print("NOT ACTIVE DIALOGUE AND NO HOTWORDS MATCHED") # Query's parse forest doesn't contain hotwords # and has no saved data for this dialogue, # not interested in this query continue + if not self._tree.query_nonterminals.isdisjoint( + hotword_nonterminals + ): + print("HOTWORDS MATCHED") + # Query's parse forest contains hotwords + # set this as the active dialogue + self._active_dialogue = dialogue_name + self._dialogue_data = ( + cast(DialogueDataDict, self.dialogue_data(dialogue_name)) + or dict() + ) + self._dsm = DSM(self._dialogue_data) # Query matches this dialogue processor, start DialogueStateManager self.dsm.load_dialogue(dialogue_name) if self.dsm.timed_out: + # TODO: If the DialogueStateManager timed out, + # set active_dialogue to the last active dialogue + # timed_out_ans = self.dsm.get_resource("Final").prompts[ "timed_out" ] @@ -983,22 +1015,29 @@ def dsm(self) -> DSM: return self._dsm @property - def all_dialogue_data(self) -> ClientDataDict: - if self._all_dialogue_data is None: - # Fetch dialogue data for the given client, or set it to an empty dict - self._all_dialogue_data = self.client_data(DSM.DIALOGUE_DATA_KEY) or dict() - return self._all_dialogue_data + def active_dialogue(self) -> Optional[str]: + if self._active_dialogue is None: + # Fetch the active dialogue for the given client, or set it to None + active_dialogue_dict: Optional[DialogueDataDict] = self.client_data( + DSM.DIALOGUE_DATA_KEY + ) + if active_dialogue_dict: + self._active_dialogue = active_dialogue_dict.get("active_dialogue") + else: + self._active_dialogue = None + return self._active_dialogue def update_dialogue_data(self) -> None: """Update the dialogue data for the given client if a dialogue module was used""" if self._dsm is not None: # Save the dialogue state when a dialogue module query # is successfully processed - self.set_client_data( - DSM.DIALOGUE_DATA_KEY, - cast(ClientDataDict, self._dsm.serialize_data()), - update_in_place=True, - ) + if self._active_dialogue: + self.set_dialogue_data( + self._active_dialogue, + cast(DialogueDatabaseDict, self._dsm.serialize_data()), + update_in_place=True, + ) def response(self) -> Optional[ResponseType]: """Return the detailed query answer""" @@ -1035,7 +1074,7 @@ def set_context(self, ctx: ContextDict) -> None: to the next query from the same client""" self._context = ctx - def client_data(self, key: str) -> Optional[ClientDataDict]: + def client_data(self, key: str) -> Optional[DialogueDataDict]: """Fetch client_id-associated data stored in the querydata table""" if not self.client_id: return None @@ -1049,7 +1088,7 @@ def client_data(self, key: str) -> Optional[ClientDataDict]: return ( None if client_data is None - else cast(ClientDataDict, client_data.data) + else cast(DialogueDataDict, client_data.data) ) except Exception as e: logging.error( @@ -1108,6 +1147,167 @@ def store_query_data( logging.error("Error storing query data in db: {0}".format(e)) return False + def dialogue_data( + self, dialogue_key: Optional[str] + ) -> Optional[DialogueDatabaseDict]: + """ + Fetch client_id-associated dialogue data stored + in the dialoguedata table based on the dialogue key + """ + if not self.client_id or not dialogue_key: + return None + with SessionContext(read_only=True) as session: + try: + dialogue_data = ( + session.query(DialogueData) + .filter(DialogueData.dialogue_key == dialogue_key) + .filter(DialogueData.client_id == self.client_id) + ).one_or_none() + return ( + None + if dialogue_data is None + else cast(DialogueDatabaseDict, dialogue_data.data) + ) + except Exception as e: + logging.error( + "Error fetching client '{0}' query data for key '{1}' from db: {2}".format( + self.client_id, dialogue_key, e + ) + ) + return None + + def set_dialogue_data( + self, + dialogue_key: str, + data: DialogueDatabaseDict, + *, + update_in_place: bool = False, + ) -> None: + """ + Setter for client dialogue data. + Also sets the active dialogue in the query data. + """ + if not self.client_id or not dialogue_key: + logging.warning("Couldn't save query data, no client ID or key") + return + Query.store_dialogue_data( + self.client_id, dialogue_key, data, update_in_place=update_in_place + ) + # Query.store_query_data( + # self.client_id, + # DSM.DIALOGUE_DATA_KEY, + # {"active_dialogue": dialogue_key}, + # update_in_place=True, + # ) + + @staticmethod + def store_dialogue_data( + client_id: str, + dialogue_key: str, + data: DialogueDatabaseDict, + *, + update_in_place: bool = False, + ) -> bool: + """Save client dialogue data in the database, under the given dialogue key""" + if not client_id or not dialogue_key: + return False + now = datetime.utcnow() + try: + with SessionContext(commit=True) as session: + row = ( + session.query(DialogueData) + .filter(DialogueData.dialogue_key == dialogue_key) + .filter(DialogueData.client_id == client_id) + ).one_or_none() + if row is None: + # Not already present: insert + row = DialogueData( + client_id=client_id, + dialogue_key=dialogue_key, + created=now, + modified=now, + data=data, + ) + session.add(row) + Query.store_query_data( + client_id, + DSM.DIALOGUE_DATA_KEY, + {"active_dialogue": dialogue_key}, + update_in_place=True, + ) + else: + if data.get(dialogue_key) is None: + # Delete the row + session.delete(row) + # Update the active dialogue in the query data + Query.update_active_dialogue_data( + client_id=client_id, old_dialogue_key=dialogue_key + ) + return True + if update_in_place: + stored_data = deepcopy(row.data) + data = _merge_two_dicts(stored_data, data) + # Already present: update + row.data = data # type: ignore + row.modified = now # type: ignore + # The session is auto-committed upon exit from the context manager + return True + except Exception as e: + logging.error("Error storing dialogue data in db: {0}".format(e)) + return False + + @staticmethod + def update_active_dialogue_data( + client_id: str, + old_dialogue_key: str, + ) -> bool: + """ + Update the active dialogue data in the query data. + """ + if not client_id: + return False + try: + with SessionContext(commit=True) as session: + rows = ( + session.query(DialogueData) + .filter(DialogueData.client_id == client_id) + .filter(DialogueData.dialogue_key != old_dialogue_key) + ).all() + latest_row = None + for row in rows: + if latest_row is None or row.modified > latest_row.modified: + latest_row = row + active_row = ( + session.query(QueryData) + .filter(QueryData.key == DSM.DIALOGUE_DATA_KEY) + .filter(QueryData.client_id == client_id) + ).one_or_none() + + if active_row is None: + # Not already present: insert if there is a row in the dialogue data table + if latest_row: + active_row = QueryData( + client_id=client_id, + key=DSM.DIALOGUE_DATA_KEY, + created=datetime.utcnow(), + modified=datetime.utcnow(), + data={"active_dialogue": latest_row.dialogue_key}, + ) + session.add(active_row) + elif latest_row is None: + # No row in the dialogue data table: delete the active dialogue data if it exists + if active_row: + session.delete(active_row) + else: + # Update the active dialogue in the query data + active_row.data = {"active_dialogue": latest_row.dialogue_key} + active_row.modified = datetime.utcnow() + # The session is auto-committed upon exit from the context manager + return True + except Exception as e: + logging.error("Error storing dialogue data in db: {0}".format(e)) + return False + @classmethod def try_to_help(cls, query: str, result: ResponseDict) -> None: """Attempt to help the user in the case of a failed query, From 36c7e9794e421f1223d6fb30d244558c5e801056 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Tue, 23 Aug 2022 14:20:11 +0000 Subject: [PATCH 343/371] Fixed cancelling an order not displaying the correct answer --- queries/fruitseller.py | 2 ++ queries/theater.py | 1 + 2 files changed, 3 insertions(+) diff --git a/queries/fruitseller.py b/queries/fruitseller.py index f1b01fe2..c5deceee 100644 --- a/queries/fruitseller.py +++ b/queries/fruitseller.py @@ -202,7 +202,9 @@ def QRemoveFruitQuery(node: Node, params: QueryStateDict, result: Result): def QFruitCancelOrder(node: Node, params: QueryStateDict, result: Result): dsm: DialogueStateManager = Query.get_dsm(result) dsm.set_resource_state("Final", ResourceState.CANCELLED) + dsm.set_answer(gen_answer(dsm.get_resource("Final").prompts["cancelled"])) dsm.finish_dialogue() + result.qtype = "QFruitCancel" def QFruitOptionsQuery(node: Node, params: QueryStateDict, result: Result): diff --git a/queries/theater.py b/queries/theater.py index 89d18c32..e0c8efbf 100644 --- a/queries/theater.py +++ b/queries/theater.py @@ -1078,6 +1078,7 @@ def QTheaterNum(node: Node, params: QueryStateDict, result: Result): def QTheaterCancel(node: Node, params: QueryStateDict, result: Result): dsm: DialogueStateManager = Query.get_dsm(result) dsm.set_resource_state("Final", ResourceState.CANCELLED) + dsm.set_answer(gen_answer(dsm.get_resource("Final").prompts["cancelled"])) dsm.finish_dialogue() result.qtype = "QTheaterCancel" From 807fea3c56a42fcb9c3b4e81aebd55893a924f93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Tue, 23 Aug 2022 16:46:15 +0000 Subject: [PATCH 344/371] Banned all nonterminals except the hotwords if not in the pizza dialogue --- queries/pizza.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/queries/pizza.py b/queries/pizza.py index 0a572034..21cacaa8 100644 --- a/queries/pizza.py +++ b/queries/pizza.py @@ -87,11 +87,11 @@ def banned_nonterminals(q: Query) -> Set[str]: """ # TODO: Implement this banned_nonterminals: set[str] = set() - # if q.dsm.dialogue_name != DIALOGUE_NAME: - # print("Not in pizza dialogue, BANNING QPizzaQuery") - # banned_nonterminals.add("QPizzaQuery") - # print("Banned nonterminals: ", banned_nonterminals) - # return banned_nonterminals + if q.active_dialogue != DIALOGUE_NAME: + print("Not in pizza dialogue, BANNING QPizzaQuery") + banned_nonterminals.add("QPizzaQuery") + print("Banned nonterminals: ", banned_nonterminals) + return banned_nonterminals return banned_nonterminals From f4e5d7b6cb8d6cf37a31acddd1ce2d51f7a45d6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Tue, 23 Aug 2022 16:46:32 +0000 Subject: [PATCH 345/371] Fixed bug where you could not switch between existing conversations --- query.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/query.py b/query.py index d856debb..c55f150e 100755 --- a/query.py +++ b/query.py @@ -736,7 +736,7 @@ def execute_from_dialogue(self) -> bool: if not self._tree.query_nonterminals.isdisjoint( hotword_nonterminals ): - print("HOTWORDS MATCHED") + print("HOTWORDS MATCHED: ", dialogue_name) # Query's parse forest contains hotwords # set this as the active dialogue self._active_dialogue = dialogue_name @@ -747,6 +747,8 @@ def execute_from_dialogue(self) -> bool: self._dsm = DSM(self._dialogue_data) # Query matches this dialogue processor, start DialogueStateManager self.dsm.load_dialogue(dialogue_name) + print("DIALOGUE LOADED: ", dialogue_name) + print("DSM DATA: ", self._dialogue_data) if self.dsm.timed_out: # TODO: If the DialogueStateManager timed out, # set active_dialogue to the last active dialogue @@ -1236,14 +1238,21 @@ def store_dialogue_data( update_in_place=True, ) else: + print("In else with dialogue key: {0}".format(dialogue_key)) if data.get(dialogue_key) is None: - # Delete the row + # Data is empty, delete the row session.delete(row) # Update the active dialogue in the query data Query.update_active_dialogue_data( client_id=client_id, old_dialogue_key=dialogue_key ) return True + Query.store_query_data( + client_id=client_id, + key=DSM.DIALOGUE_DATA_KEY, + data={"active_dialogue": dialogue_key}, + update_in_place=True, + ) if update_in_place: stored_data = deepcopy(row.data) data = _merge_two_dicts(stored_data, data) From 52e3cb1150800dc51ac648cedaf8d0adb7804d0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1ri=20Steinn=20A=C3=B0alsteinsson?= Date: Wed, 24 Aug 2022 14:11:01 +0000 Subject: [PATCH 346/371] Fixed template text --- templates/hue-connection.html | 4 ++-- templates/sonos-connection.html | 4 ++-- templates/spotify-connection.html | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/templates/hue-connection.html b/templates/hue-connection.html index cf1c7c83..c9957cc4 100644 --- a/templates/hue-connection.html +++ b/templates/hue-connection.html @@ -28,8 +28,8 @@ justify-content: center; " class="content">

    Philips Hue Hub

    -

    Philips Hue er vinsælasta snjallljósa lausn heims. - Með því að tengja Philips Hue miðstöðina þína getur þú beðið emblu um að kveikja og slökka á ljósum, breyta +

    Philips Hue eru meðal vinsælustu snjallljósa heims. + Með því að tengja Philips Hue miðstöðina þína getur þú beðið Emblu um að kveikja og slökka á ljósum, breyta lit, birtustigi og fleira.

    Leiðbeiningar

    diff --git a/templates/sonos-connection.html b/templates/sonos-connection.html index 3d376f62..897bf6b7 100644 --- a/templates/sonos-connection.html +++ b/templates/sonos-connection.html @@ -27,14 +27,14 @@ justify-content: center; " class="content">

    Sonos

    -

    Hinir geysivinsælu Sonos snjallhálarar eru studdir af radadstýringu Emblu.
    +

    Hinir geysivinsælu Sonos snjallhátalarar eru studdir af raddstýringu Emblu.
    Með því að tengja Sonos hátalara getur þú beðið Emblu um að kveikja og slökkva á tónlist, setja á útvarpstöðvar og stjórna hljóðstyrk.

    Leiðbeiningar

    1. Náðu í Sonos appið og tengdu hátalarinn þinn við þráðlausa netið á heimilinu.
    2. Gættu þess að hátalarinn og síminn þinn séu tengdir sama neti.
    3. -
    4. Veldu "Tengja" hér að neðan og skráðu þig inn með Sonos aðgangi þínum.
    5. +
    6. Veldu „Tengja“ hér að neðan og skráðu þig inn með Sonos aðgangi þínum.
    diff --git a/templates/spotify-connection.html b/templates/spotify-connection.html index a9910802..2e65e4b1 100644 --- a/templates/spotify-connection.html +++ b/templates/spotify-connection.html @@ -29,10 +29,10 @@

    Spotify

    Spotify er vinsælasta tónlistarveita heims.
    - Með því að tengja Spotify aðgang þinn getur þú beðið Emblu um að spila lögin sem þú vilt heyra. + Með því að tengja Spotify aðganginn þinn getur þú beðið Emblu um að spila lögin sem þú vilt heyra.

    Leiðbeiningar

      -
    1. Veldu "Tengja" hér að neðan og skráðu þig inn með Spotify aðgang þínum
    2. +
    3. Veldu „Tengja“ hér að neðan og skráðu þig inn með Spotify aðgangnum þínum
    From baa7e8d9c9d50680b083686d7b86d4c0c528df50 Mon Sep 17 00:00:00 2001 From: Logi E Date: Thu, 25 Aug 2022 14:46:50 +0000 Subject: [PATCH 347/371] Added colors (now use xy coordinates), fixed output from set_lights (WIP) --- queries/grammars/iot_hue.grammar | 4 +- queries/iot_hue.py | 68 ++---- .../js/IoT_Embla/Philips_Hue/fuse_search.js | 17 +- .../js/IoT_Embla/Philips_Hue/set_lights.js | 202 +++++++++--------- routes/api.py | 3 +- routes/main.py | 3 +- 6 files changed, 136 insertions(+), 161 deletions(-) diff --git a/queries/grammars/iot_hue.grammar b/queries/grammars/iot_hue.grammar index 141728dc..b28bd632 100644 --- a/queries/grammars/iot_hue.grammar +++ b/queries/grammars/iot_hue.grammar @@ -9,7 +9,7 @@ QIoT → QIoTQuery QIoTQuery -> - QIoTMakeVerb QIoTMakeRest + QIoTMakeVerb? QIoTMakeRest | QIoTSetVerb QIoTSetRest | QIoTChangeVerb QIoTChangeRest | QIoTLetVerb QIoTLetRest @@ -198,6 +198,8 @@ QIoTLightName/fall -> QIoTColorName -> {color_names} +$score(+100) QIoTColorName + QIoTSceneName -> # QIoTLightsBanwords/fall no diff --git a/queries/iot_hue.py b/queries/iot_hue.py index bd2909e8..98f97d75 100755 --- a/queries/iot_hue.py +++ b/queries/iot_hue.py @@ -36,17 +36,14 @@ # TODO: Add functionality for robot-like commands "ljós í eldhúsinu", "rauður í eldhúsinu" # TODO: Heldur að 'gerðu ljósið kaldara' sé senan 'köld' -from typing import Dict, Mapping, Optional, cast, FrozenSet +from typing import Dict, List, Optional, cast, FrozenSet from typing_extensions import TypedDict import logging import random import json -import flask -from reynir.lemmatize import simple_lemmatize - -from query import Query, QueryStateDict, AnswerTuple +from query import Query, QueryStateDict from queries import gen_answer, read_jsfile, read_grammar_file from tree import Result, Node, TerminalNode @@ -91,29 +88,25 @@ def help_text(lemma: str) -> str: ) -_COLORS = { - "gulur": 60 * 65535 / 360, - "rauður": 360 * 65535 / 360, - "grænn": 120 * 65535 / 360, - "blár": 240 * 65535 / 360, - "ljósblár": 180 * 65535 / 360, - "bleikur": 300 * 65535 / 360, - "hvítur": [], - "fjólublár": [], - "brúnn": [], - "appelsínugulur": [], -} - - # This module wants to handle parse trees for queries HANDLE_TREE = True # The grammar nonterminals this module wants to handle QUERY_NONTERMINALS = {"QIoT"} -# The context-free grammar for the queries recognized by this plug-in module -# GRAMMAR = read_grammar_file("iot_hue") +_COLORS: Dict[str, List[float]] = { + "appelsínugulur": [0.6195, 0.3624], + "bleikur": [0.4443, 0.2006], + "blár": [0.1545, 0.0981], + "fjólublár": [0.2291, 0.0843], + "grænn": [0.2458, 0.6431], + "gulur": [0.4833, 0.4647], + "hvítur": [0.3085, 0.3275], + "ljósblár": [0.1581, 0.2395], + "rauður": [0.7, 0.3], +} +# The context-free grammar for the queries recognized by this plug-in module GRAMMAR = read_grammar_file( "iot_hue", color_names=" | ".join(f"'{color}:lo'" for color in _COLORS.keys()) ) @@ -158,9 +151,9 @@ def QIoTNewColor(node: Node, params: QueryStateDict, result: Result) -> None: print(color_hue) if color_hue is not None: if "hue_obj" not in result: - result["hue_obj"] = {"on": True, "hue": int(color_hue)} + result["hue_obj"] = {"on": True, "xy": color_hue} else: - result["hue_obj"]["hue"] = int(color_hue) + result["hue_obj"]["xy"] = color_hue result["hue_obj"]["on"] = True @@ -271,18 +264,6 @@ def QIoTSpeakerHotwords(node: Node, params: QueryStateDict, result: Result) -> N result.abort = True -# Convert color name into hue -# Taken from home.py -_COLOR_NAME_TO_CIE: Mapping[str, float] = { - "gulur": 60 * 65535 / 360, - "grænn": 120 * 65535 / 360, - "ljósblár": 180 * 65535 / 360, - "blár": 240 * 65535 / 360, - "bleikur": 300 * 65535 / 360, - "rauður": 360 * 65535 / 360, - # "Rauð": 360 * 65535 / 360, -} - _SPEAKER_WORDS: FrozenSet[str] = frozenset( ( "tónlist", @@ -339,7 +320,7 @@ def sentence(state: QueryStateDict, result: Result) -> None: i[0].root(state, result.params) for i in result.enum_descendants(lambda x: isinstance(x, TerminalNode)) ) - if not _SPEAKER_WORDS.isdisjoint(lemmas): + if not lemmas.isdisjoint(_SPEAKER_WORDS): print("matched with music word list") q.set_error("E_QUERY_NOT_UNDERSTOOD") return @@ -403,16 +384,9 @@ def sentence(state: QueryStateDict, result: Result) -> None: print("COLOR NAME:", color_name) print(result.hue_obj) q.set_answer( - *gen_answer( - "ég var að kveikja ljósin! " - # + group_name - # + " " - # + color_name - # + " " - # + result.action - # + " " - # + str(result.hue_obj.get("hue", "enginn litur")) - ) + {"answer": "Skal gert."}, + "Skal gert.", + '', ) js = ( read_jsfile("IoT_Embla/fuse.js") @@ -421,7 +395,7 @@ def sentence(state: QueryStateDict, result: Result) -> None: + read_jsfile("IoT_Embla/Philips_Hue/lights.js") + read_jsfile("IoT_Embla/Philips_Hue/set_lights.js") ) - js += f"setLights('{light_or_group_name}', '{json.dumps(result.hue_obj)}');" + js += f"return setLights('{light_or_group_name}', '{json.dumps(result.hue_obj)}');" q.set_command(js) except Exception as e: logging.warning("Exception while processing random query: {0}".format(e)) diff --git a/queries/js/IoT_Embla/Philips_Hue/fuse_search.js b/queries/js/IoT_Embla/Philips_Hue/fuse_search.js index 78ed6e7d..57f3dec9 100644 --- a/queries/js/IoT_Embla/Philips_Hue/fuse_search.js +++ b/queries/js/IoT_Embla/Philips_Hue/fuse_search.js @@ -1,3 +1,5 @@ +"use strict"; + /* Fuzzy search function that returns an object in the form of {result: (Object), score: (Number)} * @param {String} query - The search term @@ -5,7 +7,7 @@ Fuzzy search function that returns an object in the form of {result: (Object), s */ function philipsFuzzySearch(query, data) { // Restructure data to be searchable by name - var newData = Object.keys(data).map(function (key) { + let newData = Object.keys(data).map(function (key) { return { ID: key, info: data[key] }; }); // Fuzzy search for the query term (returns an array of objects) @@ -17,16 +19,15 @@ function philipsFuzzySearch(query, data) { }); let searchResult = fuse.search(query); - let resultObject = new Object(); + let resultObject = {}; console.log("result: ", searchResult); if (searchResult[0] === undefined) { console.log("no match found"); return null; - } else { - // Structure the return object to be in the form of {result: (Object), score: (Number)} - resultObject.result = searchResult[0].item; - resultObject.score = searchResult[0].score; - console.log("resultObject :", resultObject); - return resultObject; } + // Structure the return object to be in the form of {result: (Object), score: (Number)} + resultObject.result = searchResult[0].item; + resultObject.score = searchResult[0].score; + console.log("resultObject :", resultObject); + return resultObject; } diff --git a/queries/js/IoT_Embla/Philips_Hue/set_lights.js b/queries/js/IoT_Embla/Philips_Hue/set_lights.js index 257940b5..1f9ec1ed 100644 --- a/queries/js/IoT_Embla/Philips_Hue/set_lights.js +++ b/queries/js/IoT_Embla/Philips_Hue/set_lights.js @@ -1,83 +1,6 @@ "use strict"; -// Constants to be used when setting lights from HTML -// var BRIDGE_IP = "192.168.1.68"; -// var USERNAME = "BzdNyxr6mGSHVdQN86UeZP67qp5huJ2Q6TWyTzvz"; - -// TODO: Implement a hotfix for Ikea Tradfri bulbs, since it can only take one argument at a time - -/** Gets a target for the given query and sets the state of the target to the given state using a fetch request. - * @param {String} target - the target to find the target e.g. "eldhús" or "lampi" - * @param {String} state - the state to set the target to e.g. "{"on": true}" or "{"scene": "energize"}" - */ -function setLights(target, state) { - let parsedState = JSON.parse(state); - let promiseList = [getAllGroups(), getAllLights()]; - let sceneName; - if (parsedState.scene) { - sceneName = parsedState.scene; - promiseList.push(getAllScenes()); - } - // Get all lights and all groups from the API (and all scenes if "scene" was a paramater) - Promise.allSettled(promiseList).then((resolvedPromises) => { - let allGroups = resolvedPromises[0].value; - let allLights = resolvedPromises[1].value; - let allScenes; - try { - allScenes = resolvedPromises[2].value; - } catch (e) { - console.log("No scene in state"); - } - - // Get the target object for the given target - let targetObject = getTargetObject(target, allLights, allGroups); - if (targetObject === undefined) { - return "Ekki tókst að finna ljós"; - } - - // Check if state includes a scene or a brightness change - if (sceneName) { - let sceneID = getSceneID(parsedState.scene, allScenes); - if (sceneID === undefined) { - return "Ekki tókst að finna senu"; - } else { - parsedState.scene = sceneID; // Change the scene parameter to the scene ID - state = JSON.stringify(parsedState); - } - } else if (parsedState.bri_inc) { - state = JSON.stringify(parsedState); - } - - // Send data to API - let url = targetObject.url; - call_api(url, state); - let isTradfriBulb = check_if_if_ikea_bulb_in_group( - targetObject, - allLights - ); - if (sceneName && isTradfriBulb) { - const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); - sleep(450).then(() => { - call_api(url, state); - }); - } - }); -} -// fetch(`http://${BRIDGE_IP}/api/${USERNAME}/${url}`, { -// method: "PUT", -// body: state, -// }) -// .then((resp) => resp.json()) -// .then((obj) => { -// console.log(obj); -// }) -// .catch((err) => { -// console.log("an error occurred!"); -// }); -// }); -// } - -function call_api(url, state) { +async function call_api(url, state) { console.log("call api"); fetch(`http://${BRIDGE_IP}/api/${USERNAME}/${url}`, { method: "PUT", @@ -99,7 +22,7 @@ function call_api(url, state) { * @param {Object} allGroups - an object of all groups from the API */ function getTargetObject(target, allLights, allGroups) { - let targetObject, selection, url; + let targetObject; let lightsResult = philipsFuzzySearch(target, allLights); let groupsResult = philipsFuzzySearch(target, allGroups); @@ -148,32 +71,31 @@ function getSceneID(scene_name, allScenes) { return; } } +// TODO: Remove me +// /* Tester function for setting lights directly from HTML controls */ +// function setLightsFromHTML() { +// let target = document.getElementById("queryInput").value; +// let stateObject = new Object(); +// stateObject.bri_inc = Number(document.getElementById("brightnessInput").value); +// stateObject = JSON.stringify(stateObject); +// return setLights(target, stateObject); +// } -/* Tester function for setting lights directly from HTML controls */ -function setLightsFromHTML() { - let target = document.getElementById("queryInput").value; - let stateObject = new Object(); - stateObject.bri_inc = Number( - document.getElementById("brightnessInput").value - ); - stateObject = JSON.stringify(stateObject); - setLights(target, stateObject); -} - -/* Tester function for setting lights directly from HTML input fields */ -function queryTestFromHTML() { - let target = document.getElementById("queryInput").value; - let bool = document.getElementById("boolInput").value; - let scene = document.getElementById("sceneInput").value; - console.log(target); - if (scene === "") { - setLights(target, `{"on": ${bool}}`); - } else { - setLights(target, `{"scene": "${scene}"}`); - } -} +// /* Tester function for setting lights directly from HTML input fields */ +// function queryTestFromHTML() { +// let target = document.getElementById("queryInput").value; +// let bool = document.getElementById("boolInput").value; +// let scene = document.getElementById("sceneInput").value; +// console.log(target); +// if (scene === "") { +// setLights(target, `{"on": ${bool}}`); +// } else { +// setLights(target, `{"scene": "${scene}"}`); +// } +// } -function check_if_if_ikea_bulb_in_group(groupsObject, all_lights) { +// TODO: Add docstring +function check_if_ikea_bulb_in_group(groupsObject, all_lights) { for (let key in groupsObject.lights) { let lightID = groupsObject.lights[key]; let light = all_lights[lightID]; @@ -190,6 +112,80 @@ function check_if_if_ikea_bulb_in_group(groupsObject, all_lights) { } } +/** Gets a target for the given query and sets the state of the target to the given state using a fetch request. + * @param {String} target - the target to find the target e.g. "eldhús" or "lampi" + * @param {String} state - the state to set the target to e.g. "{"on": true}" or "{"scene": "energize"}" + */ +async function setLights(target, state) { + let parsedState = JSON.parse(state); + let promiseList = [getAllGroups(), getAllLights()]; + let sceneName; + if (parsedState.scene) { + sceneName = parsedState.scene; + promiseList.push(getAllScenes()); + } + // Get all lights and all groups from the API (and all scenes if "scene" was a paramater) + return await Promise.allSettled(promiseList).then((resolvedPromises) => { + let allGroups = resolvedPromises[0].value; + let allLights = resolvedPromises[1].value; + let allScenes; + try { + allScenes = resolvedPromises[2].value; + } catch (e) { + console.log("No scene in state"); + } + + // Get the target object for the given target + let targetObject = getTargetObject(target, allLights, allGroups); + if (targetObject === undefined) { + return "Ekki tókst að finna ljós"; + } + + // Check if state includes a scene or a brightness change + if (sceneName) { + let sceneID = getSceneID(parsedState.scene, allScenes); + if (sceneID === undefined) { + return "Ekki tókst að finna senu"; + } + parsedState.scene = sceneID; // Change the scene parameter to the scene ID + state = JSON.stringify(parsedState); + } else if (parsedState.bri_inc) { + state = JSON.stringify(parsedState); + } + + // Send data to API + let url = targetObject.url; + call_api(url, state); + + let isTradfriBulb = check_if_ikea_bulb_in_group(targetObject, allLights); + if (sceneName && isTradfriBulb) { + let sleep = (ms) => new Promise((r) => setTimeout(r, ms)); + sleep(450).then(() => { + call_api(url, state); + }); + } + if (parsedState.scene) { + return "Ég breytti um senu."; + } + if (parsedState.on == false) { + return "Ég slökkti ljósin."; + } + if (parsedState.on == true && Object.keys(parsedState).length == 1) { + return "Ég kveikti ljósin."; + } + if (parsedState.bri_inc && parsedState.bri_inc > 0) { + return "Ég hækkaði birtuna."; + } + if (parsedState.bri_inc && parsedState.bri_inc < 0) { + return "Ég minnkaði birtuna."; + } + if (parsedState.xy || parsedState.hue) { + return "Ég breytti lit ljóssins."; + } + return "Stillingu hefur verið breytt."; + }); +} + // /** Finds a matching light or group and returns an object with the ID, name and url for the target // * @param {String} target - the target to find the target e.g. "eldhús" // * @param {Object} allLights - an array of all lights from the API diff --git a/routes/api.py b/routes/api.py index 5474f1be..cfea1112 100755 --- a/routes/api.py +++ b/routes/api.py @@ -22,7 +22,8 @@ """ -from typing import Dict, Any, List, Optional, TypedDict, cast +from typing import Dict, Any, List, Optional, cast +from typing_extensions import TypedDict from datetime import datetime import logging diff --git a/routes/main.py b/routes/main.py index 22b31c4b..c520350a 100755 --- a/routes/main.py +++ b/routes/main.py @@ -21,7 +21,8 @@ """ -from typing import Dict, Any, List, Optional, Sequence, Tuple, TypedDict, Union, cast +from typing import Dict, Any, List, Optional, Sequence, Tuple, Union, cast +from typing_extensions import TypedDict import platform import os.path From 50934fc42d887446e8e1579b044f10f690af7aca Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Wed, 31 Aug 2022 14:14:19 +0000 Subject: [PATCH 348/371] Spotify album functionality & type hinting and cleanup for spotify and sonos --- breytingar.diff | 127 ++++++++++++++ queries/iot_hue.py | 15 +- queries/iot_spotify.py | 29 +++- .../js/IoT_Embla/Philips_Hue/set_lights.js | 1 + queries/sonos.py | 139 +++++----------- queries/spotify.py | 155 ++++++++++++------ query.py | 4 +- 7 files changed, 307 insertions(+), 163 deletions(-) create mode 100644 breytingar.diff diff --git a/breytingar.diff b/breytingar.diff new file mode 100644 index 00000000..aa019ab1 --- /dev/null +++ b/breytingar.diff @@ -0,0 +1,127 @@ +diff --git a/queries/iot_hue.py b/queries/iot_hue.py +index bd2909e8..19555335 100755 +--- a/queries/iot_hue.py ++++ b/queries/iot_hue.py +@@ -403,16 +403,9 @@ def sentence(state: QueryStateDict, result: Result) -> None: + print("COLOR NAME:", color_name) + print(result.hue_obj) + q.set_answer( +- *gen_answer( +- "ég var að kveikja ljósin! " +- # + group_name +- # + " " +- # + color_name +- # + " " +- # + result.action +- # + " " +- # + str(result.hue_obj.get("hue", "enginn litur")) +- ) ++ {"answer": "Ég var að kveikja ljósin."}, ++ "Ég var að kveikja ljósin.", ++ "Ég var að kveikja ljósin.", + ) + js = ( + read_jsfile("IoT_Embla/fuse.js") +@@ -421,7 +414,7 @@ def sentence(state: QueryStateDict, result: Result) -> None: + + read_jsfile("IoT_Embla/Philips_Hue/lights.js") + + read_jsfile("IoT_Embla/Philips_Hue/set_lights.js") + ) +- js += f"setLights('{light_or_group_name}', '{json.dumps(result.hue_obj)}');" ++ js += f"return setLights('{light_or_group_name}', '{json.dumps(result.hue_obj)}');" + q.set_command(js) + except Exception as e: + logging.warning("Exception while processing random query: {0}".format(e)) +diff --git a/queries/iot_spotify.py b/queries/iot_spotify.py +index 34a8ffaf..eaf3fd8a 100644 +--- a/queries/iot_spotify.py ++++ b/queries/iot_spotify.py +@@ -51,6 +51,8 @@ _SPOTIFY_REGEXES = [ + # r"^spilaðu ([\w|\s]+) með ([\w|\s]+) á spotify?$", + r"^spilaðu ([\w|\s]+) á spotify$", + r"^spilaðu ([\w|\s]+) á spotify", ++ r"^spilaðu plötuna ([\w|\s]+)$", ++ r"^spilaðu plötuna ([\w|\s]+)$ með ([\w|\s]+)$ á spotify$", + ] + + +@@ -74,7 +76,10 @@ def handle_plain_text(q) -> bool: + artist_name = m.group(2).strip() + print("SONG NAME :", song_name) + print("ARTIST NAME :", artist_name) +- device_data = q.client_data("iot").get("iot_streaming").get("spotify") ++ try: ++ device_data = q.client_data("iot").get("iot_streaming").get("spotify") ++ except AttributeError: ++ device_data = None + if device_data is not None: + client_id = str(q.client_id) + spotify_client = SpotifyClient( +@@ -83,7 +88,13 @@ def handle_plain_text(q) -> bool: + song_name=song_name, + artist_name=artist_name, + ) +- song_url = spotify_client.get_song_by_artist() ++ if "plötuna" in ql: ++ type = "album" ++ elif "lagalistann" in ql: ++ type = "playlist" ++ else: ++ type = "song" ++ song_url = spotify_client.get_song_by_artist(type=type) + response = spotify_client.play_song_on_device() + # response = None + print("RESPONSE FROM SPOTIFY:", response) +diff --git a/queries/js/IoT_Embla/Philips_Hue/set_lights.js b/queries/js/IoT_Embla/Philips_Hue/set_lights.js +index 257940b5..08bd1b03 100644 +--- a/queries/js/IoT_Embla/Philips_Hue/set_lights.js ++++ b/queries/js/IoT_Embla/Philips_Hue/set_lights.js +@@ -62,6 +62,7 @@ function setLights(target, state) { + }); + } + }); ++ return "Ég var að kveikja ljósin."; + } + // fetch(`http://${BRIDGE_IP}/api/${USERNAME}/${url}`, { + // method: "PUT", +diff --git a/queries/spotify.py b/queries/spotify.py +index fef24c7f..25093339 100644 +--- a/queries/spotify.py ++++ b/queries/spotify.py +@@ -147,16 +147,14 @@ class SpotifyClient: + ) + return cred_dict + +- def get_song_by_artist(self): ++ def get_song_by_artist(self, type): + print("get song by artist") + print("accesss token get song; ", self._access_token) + song_name = self._song_name.replace(" ", "%20") + artist_name = self._artist_name.replace(" ", "%20") + print("song name: ", song_name) + print("artist name: ", artist_name) +- url = ( +- f"https://api.spotify.com/v1/search?type=track&q={song_name}+{artist_name}" +- ) ++ url = f"https://api.spotify.com/v1/search?type=track,album&q={song_name}+{artist_name}" + print("url: ", url) + + payload = "" +@@ -164,12 +162,15 @@ class SpotifyClient: + "Content-Type": "application/json", + "Authorization": f"Bearer {self._access_token}", + } +- ++ if type == "song": ++ type = "tracks" ++ elif type == "album": ++ type = "albums" + response = query_json_api(url, headers) + # print(response) + try: +- self._song_url = response["tracks"]["items"][0]["external_urls"]["spotify"] +- self._song_uri = response["tracks"]["items"][0]["uri"] ++ self._song_url = response[type]["items"][0]["external_urls"]["spotify"] ++ self._song_uri = response[type]["items"][0]["uri"] + except IndexError: + print("No song found.") + return diff --git a/queries/iot_hue.py b/queries/iot_hue.py index bd2909e8..19555335 100755 --- a/queries/iot_hue.py +++ b/queries/iot_hue.py @@ -403,16 +403,9 @@ def sentence(state: QueryStateDict, result: Result) -> None: print("COLOR NAME:", color_name) print(result.hue_obj) q.set_answer( - *gen_answer( - "ég var að kveikja ljósin! " - # + group_name - # + " " - # + color_name - # + " " - # + result.action - # + " " - # + str(result.hue_obj.get("hue", "enginn litur")) - ) + {"answer": "Ég var að kveikja ljósin."}, + "Ég var að kveikja ljósin.", + "Ég var að kveikja ljósin.", ) js = ( read_jsfile("IoT_Embla/fuse.js") @@ -421,7 +414,7 @@ def sentence(state: QueryStateDict, result: Result) -> None: + read_jsfile("IoT_Embla/Philips_Hue/lights.js") + read_jsfile("IoT_Embla/Philips_Hue/set_lights.js") ) - js += f"setLights('{light_or_group_name}', '{json.dumps(result.hue_obj)}');" + js += f"return setLights('{light_or_group_name}', '{json.dumps(result.hue_obj)}');" q.set_command(js) except Exception as e: logging.warning("Exception while processing random query: {0}".format(e)) diff --git a/queries/iot_spotify.py b/queries/iot_spotify.py index 34a8ffaf..9662b261 100644 --- a/queries/iot_spotify.py +++ b/queries/iot_spotify.py @@ -47,10 +47,12 @@ def help_text(lemma: str) -> str: # The context-free grammar for the queries recognized by this plug-in module _SPOTIFY_REGEXES = [ - r"^spilaðu ([\w|\s]+) með ([\w|\s]+)$", # r"^spilaðu ([\w|\s]+) með ([\w|\s]+) á spotify?$", - r"^spilaðu ([\w|\s]+) á spotify$", - r"^spilaðu ([\w|\s]+) á spotify", + # r"^spilaðu ([\w|\s]+) á spotify$", + # r"^spilaðu ([\w|\s]+) á spotify", + r"^spilaðu plötuna ([\w|\s]+) með ([\w|\s]+)$", + r"^spilaðu ([\w|\s]+) með ([\w|\s]+)$", + # r"^spilaðu plötuna ([\w|\s]+)$ með ([\w|\s]+)$ á spotify$", ] @@ -74,17 +76,30 @@ def handle_plain_text(q) -> bool: artist_name = m.group(2).strip() print("SONG NAME :", song_name) print("ARTIST NAME :", artist_name) - device_data = q.client_data("iot").get("iot_streaming").get("spotify") + try: + device_data = q.client_data("iot")["iot_streaming"]["spotify"] + except AttributeError: + device_data = None + if "plötuna" in ql: + album_name = m.group(1) + else: + album_name = None if device_data is not None: client_id = str(q.client_id) spotify_client = SpotifyClient( device_data, client_id, - song_name=song_name, + song_name=song_name or None, artist_name=artist_name, + album_name=album_name or None, ) - song_url = spotify_client.get_song_by_artist() - response = spotify_client.play_song_on_device() + if album_name != None: + song_url = spotify_client.get_album_by_artist() + song_url = spotify_client.get_first_track_on_album() + response = spotify_client.play_song_on_device() + else: + song_url = spotify_client.get_song_by_artist() + response = spotify_client.play_song_on_device() # response = None print("RESPONSE FROM SPOTIFY:", response) answer = "Ég spilaði lagið" diff --git a/queries/js/IoT_Embla/Philips_Hue/set_lights.js b/queries/js/IoT_Embla/Philips_Hue/set_lights.js index 257940b5..08bd1b03 100644 --- a/queries/js/IoT_Embla/Philips_Hue/set_lights.js +++ b/queries/js/IoT_Embla/Philips_Hue/set_lights.js @@ -62,6 +62,7 @@ function setLights(target, state) { }); } }); + return "Ég var að kveikja ljósin."; } // fetch(`http://${BRIDGE_IP}/api/${USERNAME}/${url}`, { // method: "PUT", diff --git a/queries/sonos.py b/queries/sonos.py index f2c56060..556c5500 100644 --- a/queries/sonos.py +++ b/queries/sonos.py @@ -1,3 +1,16 @@ +from typing import Dict, Optional, Union, List, Any +from inspect import getargs +import requests +from datetime import datetime, timedelta +import flask +import random + +from util import read_api_key +from queries import query_json_api, post_to_json_api +from query import Query + +import json + """ Greynir: Natural language processing for Icelandic @@ -69,20 +82,10 @@ } -from inspect import getargs -import requests -from datetime import datetime, timedelta -import flask -import random - -from util import read_api_key -from queries import query_json_api, post_to_json_api -from query import Query -from typing import Dict - -import json - # TODO - Decide what should happen if user does not designate a speaker but owns multiple speakers +# TODO - Remove debug print statements +# TODO - Testing and proper error handling +# TODO - Implement a cleaner create_or_join_session function that doesn't rely on recursion class SonosClient: def __init__( self, @@ -127,7 +130,7 @@ def __init__( ------------------------------------- PRIVATE METHODS -------------------------------------------------------------------------------- """ - def _check_token_expiration(self): + def _check_token_expiration(self) -> None: """ Checks if access token is expired, and calls a function to refresh it if necessary. """ @@ -140,7 +143,7 @@ def _check_token_expiration(self): if (datetime.now() - timestamp) > timedelta(hours=24): self._update_sonos_token() - def _update_sonos_token(self): + def _update_sonos_token(self) -> None: """ Updates the access token """ @@ -158,7 +161,7 @@ def _update_sonos_token(self): self._store_data(sonos_dict) - def _refresh_expired_token(self): + def _refresh_expired_token(self) -> Union[None, List[Any], Dict[str, Any]]: """ Helper function for updating the access token. """ @@ -173,7 +176,7 @@ def _refresh_expired_token(self): return response - def _create_token(self): + def _create_token(self) -> Union[None, List[Any], Dict[str, Any]]: """ Creates a token given a code """ @@ -193,21 +196,18 @@ def _create_token(self): self._timestamp = str(datetime.now()) return response - def _get_households(self): + def _get_households(self) -> Dict: """ Returns the list of households of the user """ print("get households") - # try: - # return self._device_data["sonos"]["data"]["households"] - # except (KeyError, TypeError): url = f"https://api.ws.sonos.com/control/api/v1/households" headers = {"Authorization": f"Bearer {self._access_token}"} response = query_json_api(url, headers=headers) return response["households"] - def _get_household_id(self): + def _get_household_id(self) -> str: """ Returns the household id for the given query """ @@ -221,14 +221,11 @@ def _get_household_id(self): response = query_json_api(url, headers) return response["households"][0]["id"] - def _get_groups(self): + def _get_groups(self) -> Dict: """ Returns the list of groups of the user """ print("get groups") - # try: - # return self._device_data["sonos"]["data"]["groups"] - # except (KeyError, TypeError): for i in range(len(self._households)): url = f"https://api.ws.sonos.com/control/api/v1/households/{self._household_id}/groups" headers = {"Authorization": f"Bearer {self._access_token}"} @@ -237,7 +234,7 @@ def _get_groups(self): cleaned_groups_dict = self._create_groupdict_for_db(response["groups"]) return cleaned_groups_dict - def _get_group_id(self): + def _get_group_id(self) -> str: """ Returns the group id for the given query """ @@ -282,15 +279,11 @@ def _translate_group_name(self): except (KeyError, TypeError): return self._group_name - def _get_players(self): + def _get_players(self) -> Dict: """ Returns the list of groups of the user """ print("get players") - # try: - # return self._device_data["sonos"]["data"]["players"] - # except (KeyError, TypeError): - print("keyerror") for i in range(len(self._households)): url = f"https://api.ws.sonos.com/control/api/v1/households/{self._household_id}/groups" headers = {"Authorization": f"Bearer {self._access_token}"} @@ -299,7 +292,7 @@ def _get_players(self): cleaned_players_dict = self._create_playerdict_for_db(response["players"]) return cleaned_players_dict - def _get_player_id(self): + def _get_player_id(self) -> str: """ Returns the player id for the given query """ @@ -318,7 +311,7 @@ def _get_player_id(self): return response["players"][0]["id"] - def _create_data_dict(self): + def _create_data_dict(self) -> Dict: print("_create_data_dict") data_dict = {"households": self._households} for i in range(len(self._households)): @@ -329,7 +322,7 @@ def _create_data_dict(self): data_dict["players"] = players_dict return data_dict - def _create_cred_dict(self): + def _create_cred_dict(self) -> Dict: print("_create_cred_dict") cred_dict = {} cred_dict.update( @@ -341,35 +334,33 @@ def _create_cred_dict(self): ) return cred_dict - def _store_data_and_credentials(self): + def _store_data_and_credentials(self) -> None: print("_store_data_and_credentials") - # data_dict = self._create_data_dict() cred_dict = self._create_cred_dict() sonos_dict = {} sonos_dict["sonos"] = {"credentials": cred_dict} self._store_data(sonos_dict) - def _store_data(self, data): + def _store_data(self, data: Dict) -> None: new_dict = {"iot_speakers": data} Query.store_query_data(self._client_id, "iot", new_dict, update_in_place=True) - def _create_groupdict_for_db(self, groups: list): + def _create_groupdict_for_db(self, groups: list) -> Dict: print("create_groupdict_for_db") groups_dict = {} for i in range(len(groups)): groups_dict[groups[i]["name"].casefold()] = groups[i]["id"] return groups_dict - def _create_playerdict_for_db(self, players: list): + def _create_playerdict_for_db(self, players: list) -> Dict: print("create_playerdict_for_db") players_dict = {} for i in range(len(players)): players_dict[players[i]["name"]] = players[i]["id"] return players_dict - def _create_or_join_session(self, recursion=None): + def _create_or_join_session(self, recursion=None) -> Optional[str]: print("_create_or_join_session") - # group_id = self._get_group_id() url = f"https://api.ws.sonos.com/control/api/v1/groups/{self._group_id}/playbackSession/joinOrCreate" payload = json.dumps({"appId": "com.mideind.embla", "appContext": "embla123"}) @@ -399,7 +390,7 @@ def _create_or_join_session(self, recursion=None): ------------------------------------- PUBLIC METHODS -------------------------------------------------------------------------------- """ - def play_radio_stream(self, radio_url): #: Optional[str] = self._device_data.get] + def play_radio_stream(self, radio_url: str) -> Optional[str]: print("play radio stream") session_id = self._create_or_join_session() print("exited create or join session") @@ -431,9 +422,8 @@ def play_radio_stream(self, radio_url): #: Optional[str] = self._device_data.ge self._store_data(data_dict) print(response.get("text")) - def increase_volume(self): + def increase_volume(self) -> None: print("increase_volume") - # group_id = self._get_group_id() url = f"https://api.ws.sonos.com/control/api/v1/groups/{self._group_id}/groupVolume/relative" payload = json.dumps({"volumeDelta": 10}) @@ -447,7 +437,7 @@ def increase_volume(self): self._refresh_data("increase_volume") print(response.get("text")) - def decrease_volume(self): + def decrease_volume(self) -> None: print("decrease volume") group_id = self._get_group_id() url = f"https://api.ws.sonos.com/control/api/v1/groups/{group_id}/groupVolume/relative" @@ -463,12 +453,11 @@ def decrease_volume(self): return "Group not found" print(response.get("text")) - def toggle_play(self): + def toggle_play(self) -> Union[None, List[Any], Dict[str, Any]]: """ Toggles play/pause of a group """ print("toggle playpause") - # group_id = self._get_group_id() print("exited group_id") url = f"https://api.ws.sonos.com/control/api/v1/groups/{self._group_id}/playback/play" headers = { @@ -476,7 +465,6 @@ def toggle_play(self): "Authorization": f"Bearer {self._access_token}", } - # response = requests.request("POST", url, headers=headers, data=payload) response = post_to_json_api(url, headers=headers) print("response :", response) if response is None: @@ -484,7 +472,7 @@ def toggle_play(self): return response - def toggle_pause(self): + def toggle_pause(self) -> Union[None, List[Any], Dict[str, Any]]: """ Toggles play/pause of a group """ @@ -505,7 +493,9 @@ def toggle_pause(self): return response - def play_audio_clip(self, audioclip_url: str): + def play_audio_clip( + self, audioclip_url: str + ) -> Union[None, List[Any], Dict[str, Any]]: """ Plays an audioclip from link to .mp3 file """ @@ -533,7 +523,7 @@ def play_audio_clip(self, audioclip_url: str): return "Group not found" return response - def play_chime(self): + def play_chime(self) -> Union[None, List[Any], Dict[str, Any]]: player_id = self._get_player_id() url = f"https://api.ws.sonos.com/control/api/v1/players/{player_id}/audioClip" @@ -555,7 +545,7 @@ def play_chime(self): return response - def next_song(self): + def next_song(self) -> Union[None, List[Any], Dict[str, Any]]: url = f"https://api.ws.sonos.com/control/api/v1/groups/{self._group_id}/playback/skipToNextTrack" headers = { @@ -567,7 +557,7 @@ def next_song(self): return response - def prev_song(self): + def prev_song(self) -> Union[None, List[Any], Dict[str, Any]]: url = f"https://api.ws.sonos.com/control/api/v1/groups/{self._group_id}/playback/skipToPreviousTrack" headers = { @@ -578,44 +568,3 @@ def prev_song(self): response = post_to_json_api(url, headers=headers) return response - - # def _refresh_data(self, function): - # print("refresh data") - # print("device_data: ", self._device_data) - # # self._device_data["sonos"]["data"] = None - # # print("device_data after deletion: ", self._device_data) - # self._households = self._get_households() - # self._groups = self._get_groups() - # self._players = self._get_players() - # # self._store_data_and_credentials() - # getattr(self, function)() - - # def get_groups_and_players(self): - # """ - # Returns the list of groups of the user - # """ - # print("get groups and players") - - # url = f"https://api.ws.sonos.com/control/api/v1/households/{self._household_id}/groups" - # headers = {"Authorization": f"Bearer {self._access_token}"} - - # response = query_json_api(url, headers) - # return response - - # def set_credentials(self, access_token, refresh_token): - # print("set_credentials") - # self._access_token = access_token - # self._refresh_token = refresh_token - # return - - # def set_data(self): - # print("set_data") - # try: - # self._households = self._get_households() - # self._household_id = self._get_household_id() - # self._groups = self._get_groups() - # self._players = self._get_players() - # self._group_id = self._get_group_id() - # except KeyError: - # print("Missing device data for this account") - # return diff --git a/queries/spotify.py b/queries/spotify.py index fef24c7f..171ff9b6 100644 --- a/queries/spotify.py +++ b/queries/spotify.py @@ -1,3 +1,4 @@ +from typing import Dict, Optional, Union, List, Any from inspect import getargs import requests from datetime import datetime, timedelta @@ -7,26 +8,33 @@ from util import read_api_key from queries import query_json_api, post_to_json_api, put_to_json_api from query import Query -from typing import Dict import json -# TODO Finna út af hverju self token virkar ekki +# TODO Find a better way to play albums +# TODO - Remove debug print statements +# TODO - Testing and proper error handling class SpotifyClient: def __init__( self, device_data: Dict[str, str], client_id: str, - song_name=None, - artist_name=None, + song_name: str = None, + artist_name: str = None, + album_name: str = None, ): + self._api_url = "https://api.spotify.com/v1" self._client_id = client_id self._device_data = device_data self._encoded_credentials = read_api_key("SpotifyEncodedCredentials") self._code = self._device_data["credentials"]["code"] self._song_name = song_name self._artist_name = artist_name + self._song_name = song_name + self._song_uri = None + self._album_name = album_name self._song_url = None + self._album_url = None print("code :", self._code) self._timestamp = self._device_data.get("credentials").get("timestamp") print("device data :", self._device_data) @@ -38,7 +46,7 @@ def __init__( self._check_token_expiration() self._store_credentials() - def _create_token(self): + def _create_token(self) -> Union[None, List[Any], Dict[str, Any]]: """ Create a new access token for the Spotify API. """ @@ -58,7 +66,7 @@ def _create_token(self): self._timestamp = str(datetime.now()) return response - def _check_token_expiration(self): + def _check_token_expiration(self) -> None: """ Checks if access token is expired, and calls a function to refresh it if necessary. """ @@ -66,28 +74,21 @@ def _check_token_expiration(self): try: timestamp = self._device_data["credentials"]["timestamp"] except (KeyError, TypeError): - print("No timestamp found for Sonos token.") + print("No timestamp found for spotify token.") return timestamp = datetime.strptime(timestamp, "%Y-%m-%d %H:%M:%S.%f") if (datetime.now() - timestamp) > timedelta(hours=1): print("more than 1 hour") - self._update_sonos_token() + self._update_spotify_token() - def _update_sonos_token(self): + def _update_spotify_token(self) -> None: """ Updates the access token """ - print("update sonos token") + print("update spotify token") self._refresh_expired_token() - cred_dict = { - "credentials": { - "access_token": self._access_token, - "timestamp": self._timestamp, - } - } - self._store_data(cred_dict) - def _refresh_expired_token(self): + def _refresh_expired_token(self) -> None: """ Helper function for updating the access token. """ @@ -106,13 +107,12 @@ def _refresh_expired_token(self): self._access_token = response.get("access_token") self._timestamp = str(datetime.now()) - def _store_credentials(self): + def _store_credentials(self) -> None: print("_store_spotify_cred") - # data_dict = self._create_sonos_data_dict() cred_dict = self._create_cred_dict() self._store_data(cred_dict) - def _create_cred_dict(self): + def _create_cred_dict(self) -> Dict[str, str]: print("_create_spotify_cred_dict") cred_dict = {} cred_dict.update( @@ -123,19 +123,18 @@ def _create_cred_dict(self): ) return cred_dict - def _store_data(self, data): + def _store_data(self, data: Dict) -> None: new_dict = {"iot_streaming": {"spotify": data}} Query.store_query_data(self._client_id, "iot", new_dict, update_in_place=True) - def _store_credentials(self): + def _store_credentials(self) -> None: print("_store_spotify credentials") - # data_dict = self._create_sonos_data_dict() cred_dict = self._create_cred_dict() spotify_dict = {} spotify_dict["credentials"] = cred_dict self._store_data(spotify_dict) - def _create_cred_dict(self): + def _create_cred_dict(self) -> Dict[str, str, str]: print("_create_spotify_cred_dict") cred_dict = {} cred_dict.update( @@ -147,16 +146,14 @@ def _create_cred_dict(self): ) return cred_dict - def get_song_by_artist(self): + def get_song_by_artist(self) -> Optional[str]: print("get song by artist") print("accesss token get song; ", self._access_token) song_name = self._song_name.replace(" ", "%20") artist_name = self._artist_name.replace(" ", "%20") print("song name: ", song_name) print("artist name: ", artist_name) - url = ( - f"https://api.spotify.com/v1/search?type=track&q={song_name}+{artist_name}" - ) + url = f"{self._api_url}/search?type=track&q={song_name}+{artist_name}" print("url: ", url) payload = "" @@ -164,31 +161,101 @@ def get_song_by_artist(self): "Content-Type": "application/json", "Authorization": f"Bearer {self._access_token}", } - response = query_json_api(url, headers) - # print(response) try: self._song_url = response["tracks"]["items"][0]["external_urls"]["spotify"] self._song_uri = response["tracks"]["items"][0]["uri"] except IndexError: print("No song found.") return - print("SONG URI: ", self._song_uri) + print("SONG URI: ", self._song_url) return self._song_url - def play_song_on_device(self): + def get_album_by_artist(self) -> Optional[str]: + print("get albuym by artist") + print("accesss token get song; ", self._access_token) + album_name = self._album_name.replace(" ", "%20") + artist_name = self._artist_name.replace(" ", "%20") + print("song name: ", album_name) + print("artist name: ", artist_name) + url = f"{self._api_url}/search?type=album&q={album_name}+{artist_name}" + print("url: ", url) + + payload = "" + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {self._access_token}", + } + response = query_json_api(url, headers) + try: + self._album_id = response["albums"]["items"][0]["id"] + self._album_url = response["albums"]["items"][0]["external_urls"]["spotify"] + self._album_uri = response["albums"]["items"][0]["uri"] + except IndexError: + print("No song found.") + return + print("ALBUM URI: ", self._album_url) + + return self._album_url + + def get_first_track_on_album(self) -> Optional[str]: + print("get first track on album") + print("accesss token get song; ", self._access_token) + album_name = self._album_name.replace(" ", "%20") + artist_name = self._artist_name.replace(" ", "%20") + print("song name: ", album_name) + print("artist name: ", artist_name) + url = f"{self._api_url}/albums/{self._album_id}/tracks" + print("url: ", url) + + payload = "" + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {self._access_token}", + } + response = query_json_api(url, headers) + try: + self._song_uri = response["items"][0]["uri"] + self._first_album_track_url = response["items"][0]["external_urls"][ + "spotify" + ] + except IndexError: + print("No song found.") + return + print("ALBUM URI: ", self._first_album_track_url) + + return self._first_album_track_url + + def play_song_on_device(self) -> Union[None, List[Any], Dict[str, Any]]: print("play song from device") print("accesss token play song; ", self._access_token) - # self._devices = self._get_devices() print("exited get devices") - url = "https://api.spotify.com/v1/me/player/play" + url = f"{self._api_url}/me/player/play" + + payload = json.dumps( + { + "context_uri": self._song_uri, + } + ) + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {self._access_token}", + } + + response = put_to_json_api(url, payload, headers) + + print(response) + return response + + def play_album_on_device(self) -> Union[None, List[Any], Dict[str, Any]]: + print("play song from device") + print("accesss token play song; ", self._access_token) + url = f"{self._api_url}/me/player/play" payload = json.dumps( { - "uris": [ - f"{self._song_uri}", - ] + "context_uri": self._album_uri, } ) headers = { @@ -201,10 +268,10 @@ def play_song_on_device(self): print(response) return response - def _get_devices(self): + def _get_devices(self) -> Dict: print("get devices") print("accesss token get devices; ", self._access_token) - url = f"https://api.spotify.com/v1/me/player/devices" + url = f"{self._api_url}/me/player/devices" headers = { "Content-Type": "application/json", @@ -214,11 +281,3 @@ def _get_devices(self): response = query_json_api(url, headers) print("devices: ", response) return response.get("devices") - - # def filter_devices(self): - # print("filter devices") - # filtered_devices = [] - # for device in self._devices: - # if device["type"] == "Smartphone": - # filtered_devices.append(device) - # return filtered_devices diff --git a/query.py b/query.py index ca355915..ad4ed4d7 100755 --- a/query.py +++ b/query.py @@ -958,9 +958,9 @@ def store_query_data( if update_in_place: print("update in place") stored_data = deepcopy(row.data) - print("stored data: ", stored_data) + # print("stored data: ", stored_data) data = merge_two_dicts(stored_data, data) - print("merged data :", data) + # print("merged data :", data) # Already present: update row.data = data # type: ignore row.modified = now # type: ignore From b3e5d350bc1a3477bffa66ac1e52f6f0f7e4a7cb Mon Sep 17 00:00:00 2001 From: johannkarlsson Date: Wed, 31 Aug 2022 16:24:22 +0000 Subject: [PATCH 349/371] More type hinting --- queries/sonos.py | 21 ++++++++++----------- queries/spotify.py | 26 +++++++++++++------------- 2 files changed, 23 insertions(+), 24 deletions(-) diff --git a/queries/sonos.py b/queries/sonos.py index 556c5500..6d1c1263 100644 --- a/queries/sonos.py +++ b/queries/sonos.py @@ -91,8 +91,8 @@ def __init__( self, device_data: Dict[str, str], client_id: str, - group_name=None, - radio_name=None, + group_name: str = None, + radio_name: str = None, ): self._client_id = client_id self._device_data = device_data @@ -196,7 +196,7 @@ def _create_token(self) -> Union[None, List[Any], Dict[str, Any]]: self._timestamp = str(datetime.now()) return response - def _get_households(self) -> Dict: + def _get_households(self) -> Dict[str, str]: """ Returns the list of households of the user """ @@ -221,7 +221,7 @@ def _get_household_id(self) -> str: response = query_json_api(url, headers) return response["households"][0]["id"] - def _get_groups(self) -> Dict: + def _get_groups(self) -> Dict[str, str]: """ Returns the list of groups of the user """ @@ -252,7 +252,6 @@ def _get_group_id(self) -> str: else: print("GROUP NAME IS NONE") if len(self._groups) == 1: - print("LEN 1") group_name = iter(self._groups[0]) return self._groups[0][group_name] @@ -267,7 +266,7 @@ def _get_group_id(self) -> str: response = query_json_api(url, headers) return response["groups"][0]["id"] - def _translate_group_name(self): + def _translate_group_name(self) -> str: """ Translates the group name to the correct group name """ @@ -279,7 +278,7 @@ def _translate_group_name(self): except (KeyError, TypeError): return self._group_name - def _get_players(self) -> Dict: + def _get_players(self) -> Dict[str, str]: """ Returns the list of groups of the user """ @@ -311,7 +310,7 @@ def _get_player_id(self) -> str: return response["players"][0]["id"] - def _create_data_dict(self) -> Dict: + def _create_data_dict(self) -> Dict[str, str]: print("_create_data_dict") data_dict = {"households": self._households} for i in range(len(self._households)): @@ -322,7 +321,7 @@ def _create_data_dict(self) -> Dict: data_dict["players"] = players_dict return data_dict - def _create_cred_dict(self) -> Dict: + def _create_cred_dict(self) -> Dict[str, str]: print("_create_cred_dict") cred_dict = {} cred_dict.update( @@ -345,14 +344,14 @@ def _store_data(self, data: Dict) -> None: new_dict = {"iot_speakers": data} Query.store_query_data(self._client_id, "iot", new_dict, update_in_place=True) - def _create_groupdict_for_db(self, groups: list) -> Dict: + def _create_groupdict_for_db(self, groups: list) -> Dict[str, str]: print("create_groupdict_for_db") groups_dict = {} for i in range(len(groups)): groups_dict[groups[i]["name"].casefold()] = groups[i]["id"] return groups_dict - def _create_playerdict_for_db(self, players: list) -> Dict: + def _create_playerdict_for_db(self, players: list) -> Dict[str, str]: print("create_playerdict_for_db") players_dict = {} for i in range(len(players)): diff --git a/queries/spotify.py b/queries/spotify.py index 171ff9b6..4a013fb8 100644 --- a/queries/spotify.py +++ b/queries/spotify.py @@ -123,7 +123,7 @@ def _create_cred_dict(self) -> Dict[str, str]: ) return cred_dict - def _store_data(self, data: Dict) -> None: + def _store_data(self, data: Dict[str, str]) -> None: new_dict = {"iot_streaming": {"spotify": data}} Query.store_query_data(self._client_id, "iot", new_dict, update_in_place=True) @@ -134,7 +134,7 @@ def _store_credentials(self) -> None: spotify_dict["credentials"] = cred_dict self._store_data(spotify_dict) - def _create_cred_dict(self) -> Dict[str, str, str]: + def _create_cred_dict(self) -> Dict[str, str]: print("_create_spotify_cred_dict") cred_dict = {} cred_dict.update( @@ -268,16 +268,16 @@ def play_album_on_device(self) -> Union[None, List[Any], Dict[str, Any]]: print(response) return response - def _get_devices(self) -> Dict: - print("get devices") - print("accesss token get devices; ", self._access_token) - url = f"{self._api_url}/me/player/devices" + # def _get_devices(self) -> Dict[str, str]: + # print("get devices") + # print("accesss token get devices; ", self._access_token) + # url = f"{self._api_url}/me/player/devices" - headers = { - "Content-Type": "application/json", - "Authorization": f"Bearer {self._access_token}", - } + # headers = { + # "Content-Type": "application/json", + # "Authorization": f"Bearer {self._access_token}", + # } - response = query_json_api(url, headers) - print("devices: ", response) - return response.get("devices") + # response = query_json_api(url, headers) + # print("devices: ", response) + # return response.get("devices") From 99f8b7721caa8643f569523674effb5af8d88fbc Mon Sep 17 00:00:00 2001 From: Logi E Date: Wed, 7 Sep 2022 10:34:03 +0000 Subject: [PATCH 350/371] Reload server upon grammar changes (uses pathlib) --- main.py | 58 ++++++++++++++++++++------------------------------------- 1 file changed, 20 insertions(+), 38 deletions(-) diff --git a/main.py b/main.py index 9a3c69fa..fa4b5e4f 100755 --- a/main.py +++ b/main.py @@ -31,12 +31,13 @@ """ -from typing import Callable, Dict, Pattern, Optional, Union +from typing import Dict, List, Pattern, Optional, Union import sys import os import re import logging +from pathlib import Path from datetime import datetime from flask import Flask, send_from_directory, render_template @@ -236,45 +237,26 @@ def inject_nn_bools() -> Dict[str, Union[str, bool]]: # Additional files that should cause a reload of the web server application # Note: Greynir.grammar is automatically reloaded if its timestamp changes - extra_files = [ - "Greynir.conf", - "GreynirPackage.conf", - "Index.conf", - "Verbs.conf", - "Adjectives.conf", - "AdjectivePredicates.conf", - "Prepositions.conf", - "Prefs.conf", - "Phrases.conf", - "Vocab.conf", - "Names.conf", - "GreynirCorrect.conf", - ] - - # Hack to satisfy the Mypy type checker, which sometimes confuses str and AnyStr - _dirname: Callable[[str], str] = lambda s: os.path.dirname(s) - - dirs = list(map(_dirname, [__file__, reynir.__file__, reynir_correct.__file__])) - for i, fname in enumerate(extra_files): - # Look for the extra file in the different package directories - for directory in dirs: - path = os.path.join(directory, "config", fname) - path = os.path.realpath(path) - if os.path.isfile(path): - extra_files[i] = path - break - else: - print("Extra file '{0}' not found".format(fname)) + extra_files: List[str] = [] + + # Parent directories of our modules + greynir_dir = Path(__file__).parent + greynirpackage_dir = Path(reynir.__file__).parent + reynir_correct_dir = Path(reynir_correct.__file__).parent + + # Reload web server when config files change + extra_files.extend(str(p.resolve()) for p in greynir_dir.glob("config/*.conf")) + extra_files.extend(str(p.resolve()) for p in greynirpackage_dir.glob("config/*.conf")) + extra_files.extend(str(p.resolve()) for p in reynir_correct_dir.glob("config/*.conf")) - # Add ord.compressed from GeynirPackage + # Add dialogue TOML files + extra_files.extend(str(p.resolve()) for p in greynir_dir.glob("queries/dialogues/*.toml")) + # Add grammar files + extra_files.extend(str(p.resolve()) for p in greynir_dir.glob("queries/grammars/*.grammar")) + + # Add ord.compressed from GreynirPackage extra_files.append( - os.path.join( - os.path.dirname(reynir.__file__), - "src", - "reynir", - "resources", - "ord.compressed", - ) + str(greynirpackage_dir / "src" / "reynir" / "resources" / "ord.compressed") ) from socket import error as socket_error From ecad8afb81acc732e90af2d4adfd5ba9ff0f80e0 Mon Sep 17 00:00:00 2001 From: Logi E Date: Wed, 7 Sep 2022 15:11:40 +0000 Subject: [PATCH 351/371] re-add disabled query modules --- main.py | 16 ++++++++++++---- queries/{disabled => }/builtin.py | 0 queries/{disabled => }/bus.py | 0 queries/{disabled => }/counting.py | 0 queries/{disabled => }/currency.py | 0 queries/{disabled => }/date.py | 0 queries/{disabled => }/dictionary.py | 0 queries/{disabled => }/distance.py | 0 queries/{disabled => }/flights.py | 0 queries/{disabled => }/geography.py | 0 queries/{disabled => }/ja.py | 0 queries/{disabled => }/news.py | 0 queries/{disabled => }/opinion.py | 0 queries/{disabled => }/petrol.py | 0 queries/{disabled => }/pic.py | 0 queries/{disabled => }/places.py | 0 queries/{disabled => }/play.py | 0 queries/{disabled => }/rand.py | 0 queries/{disabled => }/repeat.py | 0 queries/{disabled => }/schedules.py | 0 queries/{disabled => }/special.py | 0 queries/{disabled => }/stats.py | 0 queries/{disabled => }/sunpos.py | 0 queries/{disabled => }/tel.py | 0 queries/{disabled => }/unit.py | 0 queries/{disabled => }/userinfo.py | 0 queries/{disabled => }/userloc.py | 0 queries/{disabled => }/weather.py | 0 queries/{disabled => }/whatis.py | 0 queries/{disabled => }/words.py | 0 queries/{disabled => }/yulelads.py | 0 31 files changed, 12 insertions(+), 4 deletions(-) rename queries/{disabled => }/builtin.py (100%) rename queries/{disabled => }/bus.py (100%) rename queries/{disabled => }/counting.py (100%) rename queries/{disabled => }/currency.py (100%) rename queries/{disabled => }/date.py (100%) rename queries/{disabled => }/dictionary.py (100%) rename queries/{disabled => }/distance.py (100%) rename queries/{disabled => }/flights.py (100%) rename queries/{disabled => }/geography.py (100%) rename queries/{disabled => }/ja.py (100%) rename queries/{disabled => }/news.py (100%) rename queries/{disabled => }/opinion.py (100%) rename queries/{disabled => }/petrol.py (100%) rename queries/{disabled => }/pic.py (100%) rename queries/{disabled => }/places.py (100%) rename queries/{disabled => }/play.py (100%) rename queries/{disabled => }/rand.py (100%) rename queries/{disabled => }/repeat.py (100%) rename queries/{disabled => }/schedules.py (100%) rename queries/{disabled => }/special.py (100%) rename queries/{disabled => }/stats.py (100%) rename queries/{disabled => }/sunpos.py (100%) rename queries/{disabled => }/tel.py (100%) rename queries/{disabled => }/unit.py (100%) rename queries/{disabled => }/userinfo.py (100%) rename queries/{disabled => }/userloc.py (100%) rename queries/{disabled => }/weather.py (100%) rename queries/{disabled => }/whatis.py (100%) rename queries/{disabled => }/words.py (100%) rename queries/{disabled => }/yulelads.py (100%) diff --git a/main.py b/main.py index fa4b5e4f..df58dbbe 100755 --- a/main.py +++ b/main.py @@ -246,13 +246,21 @@ def inject_nn_bools() -> Dict[str, Union[str, bool]]: # Reload web server when config files change extra_files.extend(str(p.resolve()) for p in greynir_dir.glob("config/*.conf")) - extra_files.extend(str(p.resolve()) for p in greynirpackage_dir.glob("config/*.conf")) - extra_files.extend(str(p.resolve()) for p in reynir_correct_dir.glob("config/*.conf")) + extra_files.extend( + str(p.resolve()) for p in greynirpackage_dir.glob("config/*.conf") + ) + extra_files.extend( + str(p.resolve()) for p in reynir_correct_dir.glob("config/*.conf") + ) # Add dialogue TOML files - extra_files.extend(str(p.resolve()) for p in greynir_dir.glob("queries/dialogues/*.toml")) + extra_files.extend( + str(p.resolve()) for p in greynir_dir.glob("queries/dialogues/*.toml") + ) # Add grammar files - extra_files.extend(str(p.resolve()) for p in greynir_dir.glob("queries/grammars/*.grammar")) + extra_files.extend( + str(p.resolve()) for p in greynir_dir.glob("queries/grammars/*.grammar") + ) # Add ord.compressed from GreynirPackage extra_files.append( diff --git a/queries/disabled/builtin.py b/queries/builtin.py similarity index 100% rename from queries/disabled/builtin.py rename to queries/builtin.py diff --git a/queries/disabled/bus.py b/queries/bus.py similarity index 100% rename from queries/disabled/bus.py rename to queries/bus.py diff --git a/queries/disabled/counting.py b/queries/counting.py similarity index 100% rename from queries/disabled/counting.py rename to queries/counting.py diff --git a/queries/disabled/currency.py b/queries/currency.py similarity index 100% rename from queries/disabled/currency.py rename to queries/currency.py diff --git a/queries/disabled/date.py b/queries/date.py similarity index 100% rename from queries/disabled/date.py rename to queries/date.py diff --git a/queries/disabled/dictionary.py b/queries/dictionary.py similarity index 100% rename from queries/disabled/dictionary.py rename to queries/dictionary.py diff --git a/queries/disabled/distance.py b/queries/distance.py similarity index 100% rename from queries/disabled/distance.py rename to queries/distance.py diff --git a/queries/disabled/flights.py b/queries/flights.py similarity index 100% rename from queries/disabled/flights.py rename to queries/flights.py diff --git a/queries/disabled/geography.py b/queries/geography.py similarity index 100% rename from queries/disabled/geography.py rename to queries/geography.py diff --git a/queries/disabled/ja.py b/queries/ja.py similarity index 100% rename from queries/disabled/ja.py rename to queries/ja.py diff --git a/queries/disabled/news.py b/queries/news.py similarity index 100% rename from queries/disabled/news.py rename to queries/news.py diff --git a/queries/disabled/opinion.py b/queries/opinion.py similarity index 100% rename from queries/disabled/opinion.py rename to queries/opinion.py diff --git a/queries/disabled/petrol.py b/queries/petrol.py similarity index 100% rename from queries/disabled/petrol.py rename to queries/petrol.py diff --git a/queries/disabled/pic.py b/queries/pic.py similarity index 100% rename from queries/disabled/pic.py rename to queries/pic.py diff --git a/queries/disabled/places.py b/queries/places.py similarity index 100% rename from queries/disabled/places.py rename to queries/places.py diff --git a/queries/disabled/play.py b/queries/play.py similarity index 100% rename from queries/disabled/play.py rename to queries/play.py diff --git a/queries/disabled/rand.py b/queries/rand.py similarity index 100% rename from queries/disabled/rand.py rename to queries/rand.py diff --git a/queries/disabled/repeat.py b/queries/repeat.py similarity index 100% rename from queries/disabled/repeat.py rename to queries/repeat.py diff --git a/queries/disabled/schedules.py b/queries/schedules.py similarity index 100% rename from queries/disabled/schedules.py rename to queries/schedules.py diff --git a/queries/disabled/special.py b/queries/special.py similarity index 100% rename from queries/disabled/special.py rename to queries/special.py diff --git a/queries/disabled/stats.py b/queries/stats.py similarity index 100% rename from queries/disabled/stats.py rename to queries/stats.py diff --git a/queries/disabled/sunpos.py b/queries/sunpos.py similarity index 100% rename from queries/disabled/sunpos.py rename to queries/sunpos.py diff --git a/queries/disabled/tel.py b/queries/tel.py similarity index 100% rename from queries/disabled/tel.py rename to queries/tel.py diff --git a/queries/disabled/unit.py b/queries/unit.py similarity index 100% rename from queries/disabled/unit.py rename to queries/unit.py diff --git a/queries/disabled/userinfo.py b/queries/userinfo.py similarity index 100% rename from queries/disabled/userinfo.py rename to queries/userinfo.py diff --git a/queries/disabled/userloc.py b/queries/userloc.py similarity index 100% rename from queries/disabled/userloc.py rename to queries/userloc.py diff --git a/queries/disabled/weather.py b/queries/weather.py similarity index 100% rename from queries/disabled/weather.py rename to queries/weather.py diff --git a/queries/disabled/whatis.py b/queries/whatis.py similarity index 100% rename from queries/disabled/whatis.py rename to queries/whatis.py diff --git a/queries/disabled/words.py b/queries/words.py similarity index 100% rename from queries/disabled/words.py rename to queries/words.py diff --git a/queries/disabled/yulelads.py b/queries/yulelads.py similarity index 100% rename from queries/disabled/yulelads.py rename to queries/yulelads.py From 0748ca45d6ca84091c0506d216cbdb4425e49994 Mon Sep 17 00:00:00 2001 From: Logi E Date: Thu, 8 Sep 2022 13:22:13 +0000 Subject: [PATCH 352/371] Cleaning up files and folders --- breytingar.diff | 127 ------ queries/iot_connect.py | 6 +- queries/iot_hue.py | 8 +- queries/js/IoT_Embla/.gitignore | 1 - .../Philips Hue.postman_collection.json | 385 ------------------ queries/js/IoT_Embla/Postman/ikea.json | 318 --------------- queries/js/IoT_Embla/README.md | 4 - queries/js/IoT_Embla/main.js | 55 --- queries/js/IoT_Embla/philips_hue.html | 75 ---- queries/js/{IoT_Embla => Libraries}/fuse.js | 0 .../Philips_Hue/fuse_search.js | 0 .../js/{IoT_Embla => }/Philips_Hue/groups.js | 0 queries/js/{IoT_Embla => }/Philips_Hue/hub.js | 9 +- .../js/{IoT_Embla => }/Philips_Hue/lights.js | 0 .../js/{IoT_Embla => }/Philips_Hue/scenes.js | 0 .../{IoT_Embla => }/Philips_Hue/set_lights.js | 0 .../Smart_Things/fuse_search_st.js | 0 .../js/{IoT_Embla => }/Smart_Things/st.html | 0 queries/js/{IoT_Embla => }/Smart_Things/st.js | 0 .../Smart_Things/st_connecthub.js | 0 .../js/{IoT_Embla => }/Smart_Things/test.js | 0 queries/js/{IoT_Embla => }/Sonos/connect.js | 0 queries/js/{IoT_Embla => }/Sonos/groups.js | 0 queries/js/{IoT_Embla => }/Sonos/household.js | 0 queries/js/{IoT_Embla => }/Sonos/playback.js | 0 queries/js/{IoT_Embla => }/Sonos/sonos.html | 0 queries/js/{IoT_Embla => }/Sonos/sonos.js | 0 queries/js/connectHub.js | 92 ----- queries/js/hubService.js | 57 --- queries/js/light.js | 48 --- queries/js/lightInfo.js | 82 ---- queries/js/lightService.js | 121 ------ queries/js/package.json | 15 - queries/js/selectLight.js | 34 -- queries/js/test.js | 104 ----- templates/hue-connection.html | 1 - templates/iot-connect-error.html | 1 - 37 files changed, 8 insertions(+), 1535 deletions(-) delete mode 100644 breytingar.diff delete mode 100644 queries/js/IoT_Embla/.gitignore delete mode 100644 queries/js/IoT_Embla/Postman/Philips Hue.postman_collection.json delete mode 100644 queries/js/IoT_Embla/Postman/ikea.json delete mode 100644 queries/js/IoT_Embla/README.md delete mode 100644 queries/js/IoT_Embla/main.js delete mode 100644 queries/js/IoT_Embla/philips_hue.html rename queries/js/{IoT_Embla => Libraries}/fuse.js (100%) rename queries/js/{IoT_Embla => }/Philips_Hue/fuse_search.js (100%) rename queries/js/{IoT_Embla => }/Philips_Hue/groups.js (100%) rename queries/js/{IoT_Embla => }/Philips_Hue/hub.js (92%) rename queries/js/{IoT_Embla => }/Philips_Hue/lights.js (100%) rename queries/js/{IoT_Embla => }/Philips_Hue/scenes.js (100%) rename queries/js/{IoT_Embla => }/Philips_Hue/set_lights.js (100%) rename queries/js/{IoT_Embla => }/Smart_Things/fuse_search_st.js (100%) rename queries/js/{IoT_Embla => }/Smart_Things/st.html (100%) rename queries/js/{IoT_Embla => }/Smart_Things/st.js (100%) rename queries/js/{IoT_Embla => }/Smart_Things/st_connecthub.js (100%) rename queries/js/{IoT_Embla => }/Smart_Things/test.js (100%) rename queries/js/{IoT_Embla => }/Sonos/connect.js (100%) rename queries/js/{IoT_Embla => }/Sonos/groups.js (100%) rename queries/js/{IoT_Embla => }/Sonos/household.js (100%) rename queries/js/{IoT_Embla => }/Sonos/playback.js (100%) rename queries/js/{IoT_Embla => }/Sonos/sonos.html (100%) rename queries/js/{IoT_Embla => }/Sonos/sonos.js (100%) delete mode 100644 queries/js/connectHub.js delete mode 100644 queries/js/hubService.js delete mode 100644 queries/js/light.js delete mode 100644 queries/js/lightInfo.js delete mode 100644 queries/js/lightService.js delete mode 100644 queries/js/package.json delete mode 100644 queries/js/selectLight.js delete mode 100644 queries/js/test.js diff --git a/breytingar.diff b/breytingar.diff deleted file mode 100644 index aa019ab1..00000000 --- a/breytingar.diff +++ /dev/null @@ -1,127 +0,0 @@ -diff --git a/queries/iot_hue.py b/queries/iot_hue.py -index bd2909e8..19555335 100755 ---- a/queries/iot_hue.py -+++ b/queries/iot_hue.py -@@ -403,16 +403,9 @@ def sentence(state: QueryStateDict, result: Result) -> None: - print("COLOR NAME:", color_name) - print(result.hue_obj) - q.set_answer( -- *gen_answer( -- "ég var að kveikja ljósin! " -- # + group_name -- # + " " -- # + color_name -- # + " " -- # + result.action -- # + " " -- # + str(result.hue_obj.get("hue", "enginn litur")) -- ) -+ {"answer": "Ég var að kveikja ljósin."}, -+ "Ég var að kveikja ljósin.", -+ "Ég var að kveikja ljósin.", - ) - js = ( - read_jsfile("IoT_Embla/fuse.js") -@@ -421,7 +414,7 @@ def sentence(state: QueryStateDict, result: Result) -> None: - + read_jsfile("IoT_Embla/Philips_Hue/lights.js") - + read_jsfile("IoT_Embla/Philips_Hue/set_lights.js") - ) -- js += f"setLights('{light_or_group_name}', '{json.dumps(result.hue_obj)}');" -+ js += f"return setLights('{light_or_group_name}', '{json.dumps(result.hue_obj)}');" - q.set_command(js) - except Exception as e: - logging.warning("Exception while processing random query: {0}".format(e)) -diff --git a/queries/iot_spotify.py b/queries/iot_spotify.py -index 34a8ffaf..eaf3fd8a 100644 ---- a/queries/iot_spotify.py -+++ b/queries/iot_spotify.py -@@ -51,6 +51,8 @@ _SPOTIFY_REGEXES = [ - # r"^spilaðu ([\w|\s]+) með ([\w|\s]+) á spotify?$", - r"^spilaðu ([\w|\s]+) á spotify$", - r"^spilaðu ([\w|\s]+) á spotify", -+ r"^spilaðu plötuna ([\w|\s]+)$", -+ r"^spilaðu plötuna ([\w|\s]+)$ með ([\w|\s]+)$ á spotify$", - ] - - -@@ -74,7 +76,10 @@ def handle_plain_text(q) -> bool: - artist_name = m.group(2).strip() - print("SONG NAME :", song_name) - print("ARTIST NAME :", artist_name) -- device_data = q.client_data("iot").get("iot_streaming").get("spotify") -+ try: -+ device_data = q.client_data("iot").get("iot_streaming").get("spotify") -+ except AttributeError: -+ device_data = None - if device_data is not None: - client_id = str(q.client_id) - spotify_client = SpotifyClient( -@@ -83,7 +88,13 @@ def handle_plain_text(q) -> bool: - song_name=song_name, - artist_name=artist_name, - ) -- song_url = spotify_client.get_song_by_artist() -+ if "plötuna" in ql: -+ type = "album" -+ elif "lagalistann" in ql: -+ type = "playlist" -+ else: -+ type = "song" -+ song_url = spotify_client.get_song_by_artist(type=type) - response = spotify_client.play_song_on_device() - # response = None - print("RESPONSE FROM SPOTIFY:", response) -diff --git a/queries/js/IoT_Embla/Philips_Hue/set_lights.js b/queries/js/IoT_Embla/Philips_Hue/set_lights.js -index 257940b5..08bd1b03 100644 ---- a/queries/js/IoT_Embla/Philips_Hue/set_lights.js -+++ b/queries/js/IoT_Embla/Philips_Hue/set_lights.js -@@ -62,6 +62,7 @@ function setLights(target, state) { - }); - } - }); -+ return "Ég var að kveikja ljósin."; - } - // fetch(`http://${BRIDGE_IP}/api/${USERNAME}/${url}`, { - // method: "PUT", -diff --git a/queries/spotify.py b/queries/spotify.py -index fef24c7f..25093339 100644 ---- a/queries/spotify.py -+++ b/queries/spotify.py -@@ -147,16 +147,14 @@ class SpotifyClient: - ) - return cred_dict - -- def get_song_by_artist(self): -+ def get_song_by_artist(self, type): - print("get song by artist") - print("accesss token get song; ", self._access_token) - song_name = self._song_name.replace(" ", "%20") - artist_name = self._artist_name.replace(" ", "%20") - print("song name: ", song_name) - print("artist name: ", artist_name) -- url = ( -- f"https://api.spotify.com/v1/search?type=track&q={song_name}+{artist_name}" -- ) -+ url = f"https://api.spotify.com/v1/search?type=track,album&q={song_name}+{artist_name}" - print("url: ", url) - - payload = "" -@@ -164,12 +162,15 @@ class SpotifyClient: - "Content-Type": "application/json", - "Authorization": f"Bearer {self._access_token}", - } -- -+ if type == "song": -+ type = "tracks" -+ elif type == "album": -+ type = "albums" - response = query_json_api(url, headers) - # print(response) - try: -- self._song_url = response["tracks"]["items"][0]["external_urls"]["spotify"] -- self._song_uri = response["tracks"]["items"][0]["uri"] -+ self._song_url = response[type]["items"][0]["external_urls"]["spotify"] -+ self._song_uri = response[type]["items"][0]["uri"] - except IndexError: - print("No song found.") - return diff --git a/queries/iot_connect.py b/queries/iot_connect.py index cd6da227..70fd7412 100644 --- a/queries/iot_connect.py +++ b/queries/iot_connect.py @@ -160,8 +160,8 @@ def sentence(state: QueryStateDict, result: Result) -> None: client_id = str(q.client_id) if result.qtype == "connect_lights": - js = read_jsfile("IoT_Embla/Philips_Hue/hub.js") - js += f"syncConnectHub('{client_id}','{host}');" + js = read_jsfile("Philips_Hue/hub.js") + js += f"return connectHub('{client_id}','{host}');" answer = "Philips Hue miðstöðin hefur verið tengd" voice_answer = answer # audioClip(text_to_audio_url(voice_answer)) @@ -181,7 +181,7 @@ def sentence(state: QueryStateDict, result: Result) -> None: f"https://graph.api.smartthings.com/oauth/confirm_access?response_type=code&scope=devices&client_id={smartthings_key}&redirect_uri=http://{host}/connect_smartthings.api&state={client_id}" ) return - # js = read_jsfile("IoT_Embla/Smart_Things/st_connecthub.js") + # js = read_jsfile("Smart_Things/st_connecthub.js") # js += f"syncConnectHub('{client_id}','{host}');" # answer = "Smart Things miðstöðin hefur verið tengd" # voice_answer, response = answer, dict(answer=answer) diff --git a/queries/iot_hue.py b/queries/iot_hue.py index 98f97d75..80d12a5a 100755 --- a/queries/iot_hue.py +++ b/queries/iot_hue.py @@ -389,11 +389,11 @@ def sentence(state: QueryStateDict, result: Result) -> None: '', ) js = ( - read_jsfile("IoT_Embla/fuse.js") + read_jsfile("Libraries/fuse.js") + f"var BRIDGE_IP = '{bridge_ip}';var USERNAME = '{username}';" - + read_jsfile("IoT_Embla/Philips_Hue/fuse_search.js") - + read_jsfile("IoT_Embla/Philips_Hue/lights.js") - + read_jsfile("IoT_Embla/Philips_Hue/set_lights.js") + + read_jsfile("Philips_Hue/fuse_search.js") + + read_jsfile("Philips_Hue/lights.js") + + read_jsfile("Philips_Hue/set_lights.js") ) js += f"return setLights('{light_or_group_name}', '{json.dumps(result.hue_obj)}');" q.set_command(js) diff --git a/queries/js/IoT_Embla/.gitignore b/queries/js/IoT_Embla/.gitignore deleted file mode 100644 index e43b0f98..00000000 --- a/queries/js/IoT_Embla/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.DS_Store diff --git a/queries/js/IoT_Embla/Postman/Philips Hue.postman_collection.json b/queries/js/IoT_Embla/Postman/Philips Hue.postman_collection.json deleted file mode 100644 index 6c8ee25d..00000000 --- a/queries/js/IoT_Embla/Postman/Philips Hue.postman_collection.json +++ /dev/null @@ -1,385 +0,0 @@ -{ - "info": { - "_postman_id": "1c4f14ca-93cc-4d58-974a-5f8ad6dff167", - "name": "Philips Hue", - "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", - "_exporter_id": "15284646" - }, - "item": [ - { - "name": "Discover bridges (local network)", - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "https://discovery.meethue.com/", - "protocol": "https", - "host": [ - "discovery", - "meethue", - "com" - ], - "path": [ - "" - ] - } - }, - "response": [] - }, - { - "name": "Create username and clientkey", - "request": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"devicetype\": \"mideind_hue_communication#smartdevice\",\n \"generateclientkey\": true\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "https://{{bridge_IP}}/api", - "protocol": "https", - "host": [ - "{{bridge_IP}}" - ], - "path": [ - "api" - ] - } - }, - "response": [] - }, - { - "name": "Get bridge config (basic)", - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "http://{{bridge_IP}}/api/0/config", - "protocol": "http", - "host": [ - "{{bridge_IP}}" - ], - "path": [ - "api", - "0", - "config" - ] - } - }, - "response": [] - }, - { - "name": "Get bridge config (detailed)", - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "http://{{bridge_IP}}/api/{{username}}/config", - "protocol": "http", - "host": [ - "{{bridge_IP}}" - ], - "path": [ - "api", - "{{username}}", - "config" - ] - } - }, - "response": [] - }, - { - "name": "Get lights", - "request": { - "method": "GET", - "header": [ - { - "key": "hue-application-key", - "value": "{{appkey}}", - "type": "text" - } - ], - "url": { - "raw": "http://{{bridge_IP}}/api/{{username}}/lights", - "protocol": "http", - "host": [ - "{{bridge_IP}}" - ], - "path": [ - "api", - "{{username}}", - "lights" - ] - } - }, - "response": [] - }, - { - "name": "Get scenes", - "request": { - "method": "GET", - "header": [ - { - "key": "hue-application-key", - "value": "{{appkey}}", - "type": "text" - } - ], - "url": { - "raw": "http://{{bridge_IP}}/api/{{username}}/scenes", - "protocol": "http", - "host": [ - "{{bridge_IP}}" - ], - "path": [ - "api", - "{{username}}", - "scenes" - ] - } - }, - "response": [] - }, - { - "name": "Get groups", - "request": { - "method": "GET", - "header": [ - { - "key": "hue-application-key", - "value": "{{appkey}}", - "type": "text" - } - ], - "url": { - "raw": "http://{{bridge_IP}}/api/{{username}}/groups", - "protocol": "http", - "host": [ - "{{bridge_IP}}" - ], - "path": [ - "api", - "{{username}}", - "groups" - ] - } - }, - "response": [] - }, - { - "name": "Get light info", - "request": { - "method": "GET", - "header": [ - { - "key": "hue-application-key", - "value": "{{appkey}}", - "type": "text" - } - ], - "url": { - "raw": "http://{{bridge_IP}}/api/{{username}}/lights/{{light_id}}", - "protocol": "http", - "host": [ - "{{bridge_IP}}" - ], - "path": [ - "api", - "{{username}}", - "lights", - "{{light_id}}" - ] - } - }, - "response": [] - }, - { - "name": "Turn light on", - "request": { - "method": "PUT", - "header": [ - { - "key": "hue-application-key", - "value": "{{appkey}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"on\": true\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "http://{{bridge_IP}}/api/{{username}}/lights/{{light_id}}/state", - "protocol": "http", - "host": [ - "{{bridge_IP}}" - ], - "path": [ - "api", - "{{username}}", - "lights", - "{{light_id}}", - "state" - ] - } - }, - "response": [] - }, - { - "name": "Turn light off", - "request": { - "method": "PUT", - "header": [ - { - "key": "hue-application-key", - "value": "{{appkey}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"on\": false\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "http://{{bridge_IP}}/api/{{username}}/lights/{{light_id}}/state", - "protocol": "http", - "host": [ - "{{bridge_IP}}" - ], - "path": [ - "api", - "{{username}}", - "lights", - "{{light_id}}", - "state" - ] - } - }, - "response": [] - }, - { - "name": "Turn light purple", - "request": { - "method": "PUT", - "header": [ - { - "key": "hue-application-key", - "value": "{{appkey}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"on\": true,\n \"bri\": 100,\n \"xy\": [{{purple}}]\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "http://{{bridge_IP}}/api/{{username}}/lights/{{light_id}}/state", - "protocol": "http", - "host": [ - "{{bridge_IP}}" - ], - "path": [ - "api", - "{{username}}", - "lights", - "{{light_id}}", - "state" - ] - } - }, - "response": [] - } - ], - "event": [ - { - "listen": "prerequest", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - }, - { - "listen": "test", - "script": { - "type": "text/javascript", - "exec": [ - "" - ] - } - } - ], - "variable": [ - { - "key": "bridge_IP", - "value": "192.168.1.68", - "type": "string" - }, - { - "key": "bridge_id", - "value": "ecb5fafffe1be1a4", - "type": "string" - }, - { - "key": "username", - "value": "q2jNarhGHO9izO0xZZXcoww5GYANGi6mZyJYgMdL", - "type": "string" - }, - { - "key": "clientkey", - "value": "FDE5EC6CF6B56049E9DE0C88B2485145", - "type": "string" - }, - { - "key": "appkey", - "value": "q2jNarhGHO9izO0xZZXcoww5GYANGi6mZyJYgMdL", - "type": "string" - }, - { - "key": "light_rid", - "value": "fbfb447e-1c21-4bb4-bacd-53353dec5349", - "type": "string" - }, - { - "key": "light_id", - "value": "1", - "type": "string" - }, - { - "key": "purple", - "value": "0.25, 0.06", - "type": "string" - }, - { - "key": "red", - "value": "0.675, 0.322", - "type": "string" - }, - { - "key": "green", - "value": "0.3091, 0.618", - "type": "string" - } - ] -} \ No newline at end of file diff --git a/queries/js/IoT_Embla/Postman/ikea.json b/queries/js/IoT_Embla/Postman/ikea.json deleted file mode 100644 index aaa32f82..00000000 --- a/queries/js/IoT_Embla/Postman/ikea.json +++ /dev/null @@ -1,318 +0,0 @@ -{ - "info": { - "_postman_id": "f0e8ea0e-501e-4b89-a45d-c99278219f2b", - "name": "ikea", - "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" - }, - "item": [ - { - "name": "http://localhost:3500/test", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "type": "text", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n\t\"groupOperation\":{\n\t\t\"group\": \"stue loft\",\n\t\t\"operation\": {\n\t\t\t\"onOff\": true\n\t\t}\n\t}\n}" - }, - "url": { - "raw": "http://localhost:3500/test", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "3500", - "path": [ - "test" - ] - } - }, - "response": [] - }, - { - "name": "http://localhost:3500/devices", - "request": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "http://localhost:3500/devices", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "3500", - "path": [ - "devices" - ], - "query": [ - { - "key": "asdf", - "value": "asdf", - "disabled": true - } - ] - } - }, - "response": [] - }, - { - "name": "http://localhost:3500/devices/get-single-device", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "value": "application/json", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\"deviceNameOrId\": \"Sov.loft.E27WS\"}" - }, - "url": { - "raw": "http://localhost:3500/devices/get-single-device", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "3500", - "path": [ - "devices", - "get-single-device" - ], - "query": [ - { - "key": "asdf", - "value": "asdf", - "disabled": true - } - ] - } - }, - "response": [] - }, - { - "name": "http://localhost:3500/devices/set-device", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "value": "application/json", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n\t\"deviceNameOrId\": \"Stue.gulvlampe.E27WS\",\n\t\"action\": {\n\t\t\"name\": \"toggle\"\n\t}\n}" - }, - "url": { - "raw": "http://localhost:3500/devices/set-device", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "3500", - "path": [ - "devices", - "set-device" - ] - } - }, - "response": [] - }, - { - "name": "http://localhost:3500/rooms", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "value": "application/json", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "http://localhost:3500/rooms", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "3500", - "path": [ - "rooms" - ] - } - }, - "response": [] - }, - { - "name": "http://localhost:3500/rooms/set-room", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "value": "application/json", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n\t\"room\": {\n\t\t\"name\": \"STUE LOFT\",\n\t\t\"scene\": \"Lidt lys\"\n\t}\n}" - }, - "url": { - "raw": "http://localhost:3500/rooms/set-room", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "3500", - "path": [ - "rooms", - "set-room" - ] - } - }, - "response": [] - }, - { - "name": "http://localhost:3500/rooms/get-single-room", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "value": "application/json", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n\t\"room\": {\n\t\t\"name\": \"STUE LOFT\"\n\t}\n}" - }, - "url": { - "raw": "http://localhost:3500/rooms/get-single-room", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "3500", - "path": [ - "rooms", - "get-single-room" - ] - } - }, - "response": [] - }, - { - "name": "http://localhost:3500/masterswitch", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "value": "application/json", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n\t\"confirmation\": true\n}" - }, - "url": { - "raw": "http://localhost:3500/masterswitch", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "3500", - "path": [ - "masterswitch" - ] - } - }, - "response": [] - }, - { - "name": "http://localhost:3500/masterswitch/all-on", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "name": "Content-Type", - "value": "application/json", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n\t\"confirmation\": true\n}" - }, - "url": { - "raw": "http://localhost:3500/masterswitch/all-on", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "3500", - "path": [ - "masterswitch", - "all-on" - ] - } - }, - "response": [] - }, - { - "name": "http://localhost:3500/devices/get-battery-life", - "request": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "" - }, - "url": { - "raw": "http://localhost:3500/devices/get-battery-life", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "3500", - "path": [ - "devices", - "get-battery-life" - ] - } - }, - "response": [] - } - ] -} \ No newline at end of file diff --git a/queries/js/IoT_Embla/README.md b/queries/js/IoT_Embla/README.md deleted file mode 100644 index 2604b49e..00000000 --- a/queries/js/IoT_Embla/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# IoT_Embla - -Javascript for connecting to IoT devices. - diff --git a/queries/js/IoT_Embla/main.js b/queries/js/IoT_Embla/main.js deleted file mode 100644 index 0820dc62..00000000 --- a/queries/js/IoT_Embla/main.js +++ /dev/null @@ -1,55 +0,0 @@ - - -function light_show() { - fetch(`http://${BRIDGE_IP}/api/${USERNAME}/lights/1/state`, { - method: "PUT", - body: JSON.stringify({ on: true, bri: 100, xy: [0.55, 0.4] }), - }) - .then((resp) => resp.json()) - .then((j) => { - console.log(j); - }) - .catch((err) => { - console.log("an error occurred!"); - }); -} - -// function find_hub() { -// fetch(`https://discovery.meethue.com`) -// .then((resp) => resp.json()) -// .then((j) => { -// console.log(j); -// }) -// .catch((err) => { -// console.log("an error occurred!"); -// }); -// } - -function get_lights() { - fetch(`http://${BRIDGE_IP}/api/${USERNAME}/lights`,{ - method: "GET" - }) - .then((resp) => resp.json()) - .then((j) => { - console.log(j); - document.write(j) - return(j) - }) - .catch((err) => { - console.log("an error occured!") - }) -} - -function turn_off_lights() { - fetch(`http://${BRIDGE_IP}/api/${USERNAME}/lights/1/state`,{ - method: "PUT", - body: JSON.stringify({ on: false}) - }) - .then((resp) => resp.json()) - .then((j) => { - console.log(j); - }) - .catch((err) => { - console.log("an error occured!") - }) -} diff --git a/queries/js/IoT_Embla/philips_hue.html b/queries/js/IoT_Embla/philips_hue.html deleted file mode 100644 index 7b751ad4..00000000 --- a/queries/js/IoT_Embla/philips_hue.html +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - JS Hue test - - - - - - - - - - -
    -

    Testing

    - - -

    - -
    -

    Brightness

    - -
    - -
    -

    Color

    - - - - - -

    -
    - -
    - -
    -
    - - -
    -
    - -
    -
    -

    Brightness increase/decrease

    - - - - -

    -
    -
    - -
    -
    -
    - - - - - - - -
    - - -
    - - - diff --git a/queries/js/IoT_Embla/fuse.js b/queries/js/Libraries/fuse.js similarity index 100% rename from queries/js/IoT_Embla/fuse.js rename to queries/js/Libraries/fuse.js diff --git a/queries/js/IoT_Embla/Philips_Hue/fuse_search.js b/queries/js/Philips_Hue/fuse_search.js similarity index 100% rename from queries/js/IoT_Embla/Philips_Hue/fuse_search.js rename to queries/js/Philips_Hue/fuse_search.js diff --git a/queries/js/IoT_Embla/Philips_Hue/groups.js b/queries/js/Philips_Hue/groups.js similarity index 100% rename from queries/js/IoT_Embla/Philips_Hue/groups.js rename to queries/js/Philips_Hue/groups.js diff --git a/queries/js/IoT_Embla/Philips_Hue/hub.js b/queries/js/Philips_Hue/hub.js similarity index 92% rename from queries/js/IoT_Embla/Philips_Hue/hub.js rename to queries/js/Philips_Hue/hub.js index 2904845c..1e4c8407 100644 --- a/queries/js/IoT_Embla/Philips_Hue/hub.js +++ b/queries/js/Philips_Hue/hub.js @@ -94,14 +94,7 @@ async function connectHub(clientID, requestURL) { } } -function syncConnectHub(clientID, requestURL) { +async function syncConnectHub(clientID, requestURL) { connectHub(clientID, requestURL); return "Philips Hue miðstöðin hefur verið tengd"; } - -function syncConnectHubFromHTML() { - let clientID = "82AD3C91-7DA2-4502-BB17-075CEC090B14"; - let requestURL = "192.168.1.69:5000"; - connectHub(clientID, requestURL); - return clientID; -} diff --git a/queries/js/IoT_Embla/Philips_Hue/lights.js b/queries/js/Philips_Hue/lights.js similarity index 100% rename from queries/js/IoT_Embla/Philips_Hue/lights.js rename to queries/js/Philips_Hue/lights.js diff --git a/queries/js/IoT_Embla/Philips_Hue/scenes.js b/queries/js/Philips_Hue/scenes.js similarity index 100% rename from queries/js/IoT_Embla/Philips_Hue/scenes.js rename to queries/js/Philips_Hue/scenes.js diff --git a/queries/js/IoT_Embla/Philips_Hue/set_lights.js b/queries/js/Philips_Hue/set_lights.js similarity index 100% rename from queries/js/IoT_Embla/Philips_Hue/set_lights.js rename to queries/js/Philips_Hue/set_lights.js diff --git a/queries/js/IoT_Embla/Smart_Things/fuse_search_st.js b/queries/js/Smart_Things/fuse_search_st.js similarity index 100% rename from queries/js/IoT_Embla/Smart_Things/fuse_search_st.js rename to queries/js/Smart_Things/fuse_search_st.js diff --git a/queries/js/IoT_Embla/Smart_Things/st.html b/queries/js/Smart_Things/st.html similarity index 100% rename from queries/js/IoT_Embla/Smart_Things/st.html rename to queries/js/Smart_Things/st.html diff --git a/queries/js/IoT_Embla/Smart_Things/st.js b/queries/js/Smart_Things/st.js similarity index 100% rename from queries/js/IoT_Embla/Smart_Things/st.js rename to queries/js/Smart_Things/st.js diff --git a/queries/js/IoT_Embla/Smart_Things/st_connecthub.js b/queries/js/Smart_Things/st_connecthub.js similarity index 100% rename from queries/js/IoT_Embla/Smart_Things/st_connecthub.js rename to queries/js/Smart_Things/st_connecthub.js diff --git a/queries/js/IoT_Embla/Smart_Things/test.js b/queries/js/Smart_Things/test.js similarity index 100% rename from queries/js/IoT_Embla/Smart_Things/test.js rename to queries/js/Smart_Things/test.js diff --git a/queries/js/IoT_Embla/Sonos/connect.js b/queries/js/Sonos/connect.js similarity index 100% rename from queries/js/IoT_Embla/Sonos/connect.js rename to queries/js/Sonos/connect.js diff --git a/queries/js/IoT_Embla/Sonos/groups.js b/queries/js/Sonos/groups.js similarity index 100% rename from queries/js/IoT_Embla/Sonos/groups.js rename to queries/js/Sonos/groups.js diff --git a/queries/js/IoT_Embla/Sonos/household.js b/queries/js/Sonos/household.js similarity index 100% rename from queries/js/IoT_Embla/Sonos/household.js rename to queries/js/Sonos/household.js diff --git a/queries/js/IoT_Embla/Sonos/playback.js b/queries/js/Sonos/playback.js similarity index 100% rename from queries/js/IoT_Embla/Sonos/playback.js rename to queries/js/Sonos/playback.js diff --git a/queries/js/IoT_Embla/Sonos/sonos.html b/queries/js/Sonos/sonos.html similarity index 100% rename from queries/js/IoT_Embla/Sonos/sonos.html rename to queries/js/Sonos/sonos.html diff --git a/queries/js/IoT_Embla/Sonos/sonos.js b/queries/js/Sonos/sonos.js similarity index 100% rename from queries/js/IoT_Embla/Sonos/sonos.js rename to queries/js/Sonos/sonos.js diff --git a/queries/js/connectHub.js b/queries/js/connectHub.js deleted file mode 100644 index 1f66cdd4..00000000 --- a/queries/js/connectHub.js +++ /dev/null @@ -1,92 +0,0 @@ -"use strict"; - -//const XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest; - -function getSmartDeviceAddress() { - var request = new XMLHttpRequest(); - request.open('GET', 'https://discovery.meethue.com', false); // `false` makes the request synchronous - request.send(null); - - if (request.status === 200) { - return JSON.parse(request.responseText)[0]; - } - else { - return 'No smart device found'; - } -} - -function createNewDeveloper(ipAddress) { - - const body = JSON.stringify({ - 'devicetype': 'mideind_hue_communication#smartdevice' - }); - - var request = new XMLHttpRequest(); - request.open('POST', `http://${ipAddress}/api`, false); // `false` makes the request synchronous - request.send(body); - - - if (request.status === 200) { - return JSON.parse(request.responseText)[0]; - } - else { - throw new Error('Error while creating new user'); - } -} - -function storeDevice(data, requestURL) { - - let request = new XMLHttpRequest(); - - request.open('POST', `http://${requestURL}/register_query_data.api`, false); - request.setRequestHeader('Content-Type', 'application/json'); - - request.send(JSON.stringify(data)); - - if (request.status === 200) { - return JSON.parse(request.responseText); - } - else { - throw new Error('Error while storing user'); - } - -} - -function connectHub(device_id, requestURL) { - - let deviceInfo = getSmartDeviceAddress(); - - try { - let username = createNewDeveloper(deviceInfo.internalipaddress); - - if (!username.success) { - return 'Ýttu á \'Philips\' takkann á tengiboxinu og reyndu aftur'; - } - - const data = { - 'device_id': device_id, - 'key': 'smartlights', - 'data': { - 'smartlights': { - 'selected_light': 'philips_hue', - 'philips_hue': { - 'username':username.success.username, - 'ipAddress':deviceInfo.internalipaddress - } - } - } - }; - - const result = storeDevice(data, requestURL); - - return 'Tenging við snjalltæki tókst'; - } catch(error) { - console.log(error); - return 'Ekki tókst að tengja snjalltæki'; - } - // Errors for connectHub - // {"error":{"address":"","description":"link button not pressed","type":101}} - // {"error":"Failed to execute 'send' on 'XMLHttpRequest': Failed to load 'http://192.168.1.140/api'."} - // {"success":{"username":"2GTB-NVq68YwLRA43AZLPHmMiuvRL8yaZJykuJBg"}} -} - diff --git a/queries/js/hubService.js b/queries/js/hubService.js deleted file mode 100644 index 2f764271..00000000 --- a/queries/js/hubService.js +++ /dev/null @@ -1,57 +0,0 @@ -"use strict"; - -//const XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest; - -function getSmartDeviceAddress() { - let request = new XMLHttpRequest(); - request.open('GET', 'https://discovery.meethue.com', false); // `false` makes the request synchronous - request.send(null); - - if (request.status === 200) { - return JSON.parse(request.responseText)[0]; - } - else { - return 'No smart device found'; - } -} - -function createNewDeveloper(ipAddress) { - - const body = JSON.stringify({ - 'devicetype': 'mideind_hue_communication#smartdevice' - }); - - let request = new XMLHttpRequest(); - request.open('POST', `http://${ipAddress}/api`, false); // `false` makes the request synchronous - request.send(body); - - - if (request.status === 200) { - return JSON.parse(request.responseText)[0]; - } - else { - throw new Error('Error while creating new user'); - } -} - -function connectHub() { - serviceStorage = {}; - let deviceInfo = getSmartDeviceAddress(); - - try { - let username = createNewDeveloper(deviceInfo.internalipaddress); - console.log('username'); - console.log(username); - serviceStorage.username = username.success.username; - return username; - } catch(error) { - serviceStorage.username = null; - return {error:error.message}; - } - // Errors for connectHub - // {"error":{"address":"","description":"link button not pressed","type":101}} - // {"error":"Failed to execute 'send' on 'XMLHttpRequest': Failed to load 'http://192.168.1.140/api'."} - // {"success":{"username":"2GTB-NVq68YwLRA43AZLPHmMiuvRL8yaZJykuJBg"}} -} - -connectHub(); diff --git a/queries/js/light.js b/queries/js/light.js deleted file mode 100644 index 1dd91030..00000000 --- a/queries/js/light.js +++ /dev/null @@ -1,48 +0,0 @@ -"use strict"; - -function changeLight(ipAddress, username, on = null, dimmer) { - - let body = {}; - - if (on) { - body.on = on; - } - - if (dimmer) { - body.dimmer = parseInt(parseInt(dimmer) * 1/254); - } - /* - const body = JSON.stringify({ - on: on, - bri:254, - hue: 65280, - sat: 254, - - }); - */ - - var request = new XMLHttpRequest(); - request.open('PUT', `http://${ipAddress}/api/${username}/lights/1/state`, on); // `false` makes the request synchronous - request.send(JSON.stringify(body)); - -} - -function main(on = null, dimmer = null) { - if (!window.serviceStorage) { - return 'Snjalltæki er ekki tengt'; - } - - let { ipAddress, username } = serviceStorage; - - if (!ipAddress || !username) { - return 'Snjalltæki er ekki tengt'; - } - else { - try { - changeLight(ipAddress, username, on, dimmer); - return 'Skal gert'; - } catch(error) { - return error.message; - } - } -} diff --git a/queries/js/lightInfo.js b/queries/js/lightInfo.js deleted file mode 100644 index 538f2fa6..00000000 --- a/queries/js/lightInfo.js +++ /dev/null @@ -1,82 +0,0 @@ -"use strict"; - -const XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest; - -function lightInfo(ipAddress, username) { - - var request = new XMLHttpRequest(); - request.open('GET', `http://${ipAddress}/api/${username}/lights`, false); // `false` makes the request synchronous - request.send(null); - - if (request.status === 200) { - return JSON.parse(request.responseText); - } - else { - throw new Error('Error while fetching light info'); - } - -} - -function groupInfo(ipAddress, username) { - let request = new XMLHttpRequest(); - request.open('GET', `http://${ipAddress}/api/${username}/groups`, false); - request.send(null); - - if (request.status === 200) { - return JSON.parse(request.responseText); - } else { - throw new Error ('Error while fetching group info'); - } - -} -/* -serviceStorage = { - username: 'oL27u8MQOYqD486TKcg7ki8OosDx982M5l2oqToP', - ipAddress: '192.168.1.140' -} -*/ -function main() { - if (!serviceStorage) { - return 'Snjalltæki ekki tengt'; - } - let { username, ipAddress } = serviceStorage; - - if (!username || !ipAddress) { - return 'Snjalltæki ekki tengt'; - } - - let lights = null; - let groups = null; - let deviceString = 'Ljós:'; - - try { - lights = lightInfo(ipAddress, username); - } catch(error) { - return 'Villa kom upp í samskiptum við snjalltæki'; - } - - for (let key in lights) { - deviceString += ` ${lights[key].name},`; - } - - try { - groups = groupInfo(ipAddress, username); - } catch(error) { - console.log(error); - return 'Villa kom upp í samskiptum við snjalltæki'; - } - - deviceString = deviceString.slice(0, -1); - - deviceString += ' Hópar:'; - - for (let key in groups) { - deviceString += ` ${groups[key].name},`; - } - - deviceString = deviceString.slice(0, -1); - - return deviceString; - -} -main(); \ No newline at end of file diff --git a/queries/js/lightService.js b/queries/js/lightService.js deleted file mode 100644 index 59fb70fb..00000000 --- a/queries/js/lightService.js +++ /dev/null @@ -1,121 +0,0 @@ -"use strict"; - -//const XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest; - -// Returns ID of lights and groups with the provided name -function getIdByName(ipAddress, username, name) { - - let ids = {}; - - let request = new XMLHttpRequest(); - - request.open('GET', `http://${ipAddress}/api/${username}`, false); - request.send(null); - - if (request.status === 200) { - let hub = JSON.parse(request.responseText); - let groupId = Object.keys(hub.groups).find(key => hub.groups[key].name.toLowerCase() === name.toLowerCase()); - if (groupId) { - ids.groupId = groupId; - } - - let lightId = Object.keys(hub.lights).find(key => hub.lights[key].name.toLowerCase() === name.toLowerCase()); - if (lightId) { - ids.lightId = lightId; - } - - return ids; - } - -} - -function changeGroup(ipAddress, username, groupId, on = null, bri = null, hue = null, sat = null) { - - let body = {}; - - if (on !== null) { - body.on = on; - } - - if (bri !== null) { - body.bri = parseInt(parseInt(bri) * 254/100); - } - - if (hue !== null) { - body.hue = parseInt(hue); - body.sat = 254; - } - - if (sat !== null) { - body.sat = parseInt(parseInt(sat) * (254/100)); - } - //console.log('body'); - //console.log(body); - let request = new XMLHttpRequest(); - request.open('PUT', `http://${ipAddress}/api/${username}/groups/${groupId}/action`, false); // `false` makes the request synchronous - request.send(JSON.stringify(body)); - - if (request.status === 200) { - let result = JSON.parse(request.responseText); - return 'Tókst'; - } - throw new Error('Hópur fannst ekki'); - -} - -function changeLight(ipAddress, username, lightId, on = null, bri = null, hue = null, sat = null) { - - let body = {}; - - if (on !== null) { - body.on = on; - } - - if (bri !== null) { - body.bri = parseInt(parseInt(bri) * 254/100); - } - - if (hue !== null) { - body.hue = parseInt(hue); - body.sat = 254; - } - - if (sat !== null) { - body.sat = parseInt(parseInt(sat) * 254/100); - } - - let request = new XMLHttpRequest(); - request.open('PUT', `http://${ipAddress}/api/${username}/lights/${lightId}/state`, false); // `false` makes the request synchronous - request.send(JSON.stringify(body)); - - if (request.status === 200) { - let result = JSON.parse(request.responseText); - return 'Tókst'; - } - throw new Error('Hópur fannst ekki'); - -} - -function main(ipAddress = null, username = null, name = null, on = null, bri = null, hue = null, sat = null) { - - if ( !ipAddress || !username) { - return 'Snjalltæki er ekki tengt'; - } - - if (name) { - try { - let idDict = getIdByName(ipAddress, username, name); - if (idDict.groupId) { - return changeGroup(ipAddress, username, idDict.groupId, on, bri, hue, sat); - } - if (idDict.lightId) { - return changeLight(ipAddress, username, idDict.lightId, on, bri, hue, sat); - } - } catch(error) { - return error.message; - } - } - - return `Hópur eða ljós ${name} fannst ekki.`; - -} diff --git a/queries/js/package.json b/queries/js/package.json deleted file mode 100644 index 4cd6dac9..00000000 --- a/queries/js/package.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "name": "js-service", - "version": "1.0.0", - "description": "", - "main": "hubService.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "author": "", - "license": "ISC", - "dependencies": { - "node-fetch": "^2.6.0", - "xmlhttprequest": "^1.8.0" - } -} diff --git a/queries/js/selectLight.js b/queries/js/selectLight.js deleted file mode 100644 index 3972bdab..00000000 --- a/queries/js/selectLight.js +++ /dev/null @@ -1,34 +0,0 @@ -"use strict"; - -function changeLight(ipAddress, username, lightNo) { - - const body = JSON.stringify({ - alert: 'lselect' - }); - - var request = new XMLHttpRequest(); - request.open('PUT', `http://${ipAddress}/api/${username}/lights/${lightNo}/state`, false); // `false` makes the request synchronous - request.send(body); - -} -// lightNo: int -// If authorized then light with id lightNo will blink -function main(lightNo) { - if (!window.serviceStorage) { - return 'Snjalltæki er ekki tengt'; - } - - let { ipAddress, username } = serviceStorage; - - if ( !ipAddress || !username) { - return 'Snjalltæki er ekki tengt'; - } - else { - try { - changeLight(ipAddress, username, lightNo); - return 'Skal gert'; - } catch(error) { - return error.message; - } - } -} diff --git a/queries/js/test.js b/queries/js/test.js deleted file mode 100644 index 7f9d1f24..00000000 --- a/queries/js/test.js +++ /dev/null @@ -1,104 +0,0 @@ -"use strict"; - -async function getSmartDeviceInfo(ipAddress, username) { - - const headers = { - 'headers':'application/json' - }; - - const response = await fetch( - `http://${ipAddress}/api/${username}`, - { - method: 'GET', - } - ) - .catch((error) =>{ - console.log('error'); - console.log(error); - }); - return response.json(); -} - -async function changeLight(ipAddress, username, on) { - const headers = { - 'headers':'application/json' - }; - - const body = { - on: on, - bri:40, - hue: 65280, - sat: 254, - effect: 'colorloop' - }; - - const response = await fetch( - `http://${ipAddress}/api/${username}/lights/1/state`, - { - method: 'PUT', - body: JSON.stringify(body) - } - ) - .catch((error) =>{ - console.log('error'); - console.log(error); - }); - return response.json(); -} - -function getSmartDeviceAddress() { - var request = new XMLHttpRequest(); - request.open('GET', 'https://discovery.meethue.com', false); // `false` makes the request synchronous - request.send(null); - - if (request.status === 200) { - return JSON.parse(request.responseText)[0]; - } - else { - return 'No smart device found'; - } -} - -function createNewDeveloper(ipAddress) { - - const body = JSON.stringify({ - 'devicetype': 'mideind_hue_communication#smartdevice' - }); - - var request = new XMLHttpRequest(); - request.open('POST', `http://${ipAddress}/api`, false); // `false` makes the request synchronous - request.send(body); - - - if (request.status === 200) { - return JSON.parse(request.responseText)[0]; - } - else { - throw new Error('Error while creating new user'); - } -} - -function connectHub() { - serviceStorage = {}; - let deviceInfo = getSmartDeviceAddress(); - - try { - let username = createNewDeveloper(deviceInfo.internalipaddress); - - if (username.success){ - serviceStorage.username = username.success.username; - } else { - return 'Ýttu á \'Philips\' takkann á tengiboxinu og reyndu aftur'; - } - - return 'Tenging við snjalltæki tókst'; - } catch(error) { - return 'Ekki tókst að tengja snjalltæki'; - } - // Errors for connectHub - // {"error":{"address":"","description":"link button not pressed","type":101}} - // {"error":"Failed to execute 'send' on 'XMLHttpRequest': Failed to load 'http://192.168.1.140/api'."} - // {"success":{"username":"2GTB-NVq68YwLRA43AZLPHmMiuvRL8yaZJykuJBg"}} -} - -connectHub(); diff --git a/templates/hue-connection.html b/templates/hue-connection.html index c9957cc4..91481355 100644 --- a/templates/hue-connection.html +++ b/templates/hue-connection.html @@ -15,7 +15,6 @@ } Philips Hue - diff --git a/templates/iot-connect-error.html b/templates/iot-connect-error.html index 7fc50553..9360ed4b 100644 --- a/templates/iot-connect-error.html +++ b/templates/iot-connect-error.html @@ -19,7 +19,6 @@ }); Tenging mistókst - From 8d9f95bd72414b4633800c38034ea40cc401e622 Mon Sep 17 00:00:00 2001 From: Logi E Date: Thu, 8 Sep 2022 15:01:27 +0000 Subject: [PATCH 353/371] More cleanup, hopefully sonos/st js isn't used --- queries/iot_connect.py | 231 +- queries/iot_hue.py | 61 +- queries/iot_speakers.py | 91 +- queries/iot_spotify.py | 50 +- queries/js/Philips_Hue/fuse_search.js | 4 +- queries/js/Philips_Hue/groups.js | 0 queries/js/Philips_Hue/hub.js | 50 +- queries/js/Philips_Hue/lights.js | 73 +- queries/js/Philips_Hue/scenes.js | 5 - queries/js/Philips_Hue/set_lights.js | 187 +- queries/js/Smart_Things/fuse_search_st.js | 32 - queries/js/Smart_Things/st.html | 28 - queries/js/Smart_Things/st.js | 169 - queries/js/Smart_Things/st_connecthub.js | 48 - queries/js/Smart_Things/test.js | 24426 -------------------- queries/js/Sonos/connect.js | 5 - queries/js/Sonos/groups.js | 18 - queries/js/Sonos/household.js | 15 - queries/js/Sonos/playback.js | 48 - queries/js/Sonos/sonos.html | 59 - queries/js/Sonos/sonos.js | 3 - queries/wip/home.py | 470 - 22 files changed, 119 insertions(+), 25954 deletions(-) delete mode 100644 queries/js/Philips_Hue/groups.js delete mode 100644 queries/js/Philips_Hue/scenes.js delete mode 100644 queries/js/Smart_Things/fuse_search_st.js delete mode 100644 queries/js/Smart_Things/st.html delete mode 100644 queries/js/Smart_Things/st.js delete mode 100644 queries/js/Smart_Things/st_connecthub.js delete mode 100644 queries/js/Smart_Things/test.js delete mode 100644 queries/js/Sonos/connect.js delete mode 100644 queries/js/Sonos/groups.js delete mode 100644 queries/js/Sonos/household.js delete mode 100644 queries/js/Sonos/playback.js delete mode 100644 queries/js/Sonos/sonos.html delete mode 100644 queries/js/Sonos/sonos.js delete mode 100644 queries/wip/home.py diff --git a/queries/iot_connect.py b/queries/iot_connect.py index 70fd7412..30b4f7dd 100644 --- a/queries/iot_connect.py +++ b/queries/iot_connect.py @@ -22,29 +22,23 @@ of random numbers, e.g. "Kastaðu tengingi", "Nefndu tölu milli 5 og 10", etc. """ -_BREAK_LENGTH = 5 # Seconds -_BREAK_SSML = ''.format(_BREAK_LENGTH) - -from sqlite3 import Timestamp -from typing import Dict, Mapping, Optional, cast +from typing import Dict from typing_extensions import TypedDict -import logging import random -import json import flask -import requests -from datetime import datetime -import time -from query import Query, QueryStateDict, AnswerTuple -from queries import gen_answer, read_jsfile, read_grammar_file +from query import Query, QueryStateDict +from queries import gen_answer, read_jsfile from queries.sonos import SonosClient from tree import Result, Node -from routes import better_jsonify + from util import read_api_key from speech import text_to_audio_url +_BREAK_LENGTH = 5 # Seconds +_BREAK_SSML = f'' + class SpeakerCredentials(TypedDict): tokens: Dict[str, str] @@ -54,8 +48,6 @@ class DeviceData(TypedDict): sonos: SpeakerCredentials -_IoT_QTYPE = "IoTConnect" - TOPIC_LEMMAS = [ "tengja", ] @@ -65,7 +57,12 @@ def help_text(lemma: str) -> str: """Help text to return when query.py is unable to parse a query but one of the above lemmas is found in it""" return "Ég skil þig ef þú segir til dæmis: {0}.".format( - random.choice(("Tengu miðstöðina", "Tengdu ljósin" "Tengdu hátalarann")) + random.choice( + ( + "Tengdu ljósin", + "Tengdu hátalarann", + ) + ) ) @@ -76,8 +73,6 @@ def help_text(lemma: str) -> str: QUERY_NONTERMINALS = {"QIoTConnect"} # The context-free grammar for the queries recognized by this plug-in module -# GRAMMAR = read_grammar_file("iot_hue") - GRAMMAR = f""" /þgf = þgf @@ -88,7 +83,6 @@ def help_text(lemma: str) -> str: QIoTConnect → QIoTConnectLights - | QIoTConnectHub | QIoTConnectSpeaker | QIoTCreateSpeakerToken | QIoTConnectSpotify @@ -96,9 +90,6 @@ def help_text(lemma: str) -> str: QIoTConnectLights → "tengdu" "ljósin" -QIoTConnectHub → - "tengdu" "miðstöðina" - QIoTConnectSpeaker → "tengdu" "hátalarann" @@ -116,12 +107,6 @@ def QIoTConnectLights(node: Node, params: QueryStateDict, result: Result) -> Non result.action = "connect_lights" -def QIoTConnectHub(node: Node, params: QueryStateDict, result: Result) -> None: - print("Connect Hub") - result.qtype = "connect_hub" - result.action = "connect_hub" - - def QIoTConnectSpeaker(node: Node, params: QueryStateDict, result: Result) -> None: print("Connect Speaker") result.qtype = "connect_speaker" @@ -143,58 +128,25 @@ def QIoTConnectSpotify(node: Node, params: QueryStateDict, result: Result) -> No def sentence(state: QueryStateDict, result: Result) -> None: """Called when sentence processing is complete""" q: Query = state["query"] - changing_color = result.get("changing_color", False) - changing_scene = result.get("changing_scene", False) - changing_brightness = result.get("changing_brightness", False) - print("error?", sum((changing_color, changing_scene, changing_brightness)) > 1) - if ( - sum((changing_color, changing_scene, changing_brightness)) > 1 - or "qtype" not in result - ): + if "qtype" not in result: q.set_error("E_QUERY_NOT_UNDERSTOOD") return q.set_qtype(result.qtype) host = str(flask.request.host) - print("host :", host) client_id = str(q.client_id) if result.qtype == "connect_lights": js = read_jsfile("Philips_Hue/hub.js") js += f"return connectHub('{client_id}','{host}');" - answer = "Philips Hue miðstöðin hefur verið tengd" - voice_answer = answer - # audioClip(text_to_audio_url(voice_answer)) - response = dict(answer=answer) - q.set_answer(response, answer, voice_answer) + q.set_answer(*gen_answer("Philips Hue miðstöðin hefur verið tengd.")) q.set_command(js) return - elif result.qtype == "connect_hub": - - smartthings_key = read_api_key("SmartThingsKey") - answer = "Skráðu þig inn hjá SmartThings" - voice_answer, response = answer, dict(answer=answer) - q.set_answer(response, answer, voice_answer) - # Redirect the user to a Sonos login screen, which will then forward the neccessary credentials to the connect_sonos.api found in api.py - q.set_url( - f"https://graph.api.smartthings.com/oauth/confirm_access?response_type=code&scope=devices&client_id={smartthings_key}&redirect_uri=http://{host}/connect_smartthings.api&state={client_id}" - ) - return - # js = read_jsfile("Smart_Things/st_connecthub.js") - # js += f"syncConnectHub('{client_id}','{host}');" - # answer = "Smart Things miðstöðin hefur verið tengd" - # voice_answer, response = answer, dict(answer=answer) - # q.set_answer(response, answer, voice_answer) - # q.set_command(js) - # return - elif result.qtype == "connect_speaker": sonos_key = read_api_key("SonosKey") - answer = "Skráðu þig inn hjá Sonos" - voice_answer, response = answer, dict(answer=answer) - q.set_answer(response, answer, voice_answer) - # Redirect the user to a Sonos login screen, which will then forward the neccessary credentials to the connect_sonos.api found in api.py + q.set_answer(*gen_answer("Skráðu þig inn hjá Sonos")) + # Redirect the user to a Sonos login screen q.set_url( f"https://api.sonos.com/login/v3/oauth?client_id={sonos_key}&response_type=code&state={client_id}&scope=playback-control-all&redirect_uri=http://{host}/connect_sonos.api" ) @@ -216,7 +168,6 @@ def sentence(state: QueryStateDict, result: Result) -> None: answer = "Ég bjó til tóka frá Sónos" response = dict(answer=answer) - voice_answer = answer voice_answer = f"Ég ætla að tengja Sónos hátalarann. Hlustaðu vel. {_BREAK_SSML} Ég tengdi Sónos hátalarann. Góða skemmtun." sonos_voice_clip = ( f"{_BREAK_SSML} Hæ!, ég er búin að tengja þennan Sónos hátalara." @@ -226,155 +177,9 @@ def sentence(state: QueryStateDict, result: Result) -> None: return elif result.qtype == "connect_spotify": - print("connect spotify") spotify_key = read_api_key("SpotifyKey") - answer = "Skráðu þig inn hjá Spotify" - voice_answer, response = answer, dict(answer=answer) - q.set_answer(response, answer, voice_answer) - # Redirect the user to a Sonos login screen, which will then forward the neccessary credentials to the connect_sonos.api found in api.py + q.set_answer(*gen_answer("Skráðu þig inn hjá Spotify")) q.set_url( f"https://accounts.spotify.com/authorize?client_id={spotify_key}&response_type=code&redirect_uri=http://{host}/connect_spotify.api&state={client_id}&scope=user-read-playback-state+user-modify-playback-state+user-read-playback-position+user-read-recently-played+app-remote-control+user-top-read+user-read-currently-playing+playlist-read-private+streaming" ) return - - -# def create_sonos_data_dict(access_token, q): -# data_dict = {} -# households = get_households(access_token).json() -# data_dict.update(households) -# groups_list = [] -# players_list = [] -# for i in range(len(households)): -# groups_object = get_groups( -# households["households"][i]["id"], access_token -# ).json() -# groups_raw = groups_object.get("groups") -# players_raw = groups_object.get("players") -# groups_list += create_grouplist_for_db(groups_raw) -# players_list += create_playerlist_for_db(players_raw) - -# data_dict["groups"] = groups_list -# data_dict["players"] = players_list -# return data_dict - - -# def create_sonos_cred_dict(access_token, refresh_token, timestamp, q): -# cred_dict = {} -# cred_dict.update( -# { -# "access_token": access_token, -# "refresh_token": refresh_token, -# "timestamp": timestamp, -# } -# ) -# return cred_dict - - -# def store_sonos_data_and_credentials(data_dict, cred_dict, q): -# sonos_dict = {} -# sonos_dict["sonos"] = {"credentials": cred_dict, "data": data_dict} -# print("final dict for db :", sonos_dict) -# q.set_client_data("iot_speakers", sonos_dict, update_in_place=True) - - -# def create_grouplist_for_db(groups): -# groups_list = [] -# for i in range(len(groups)): -# groups_list.append({groups[i]["name"]: groups[i]["id"]}) -# return groups_list - - -# def create_playerlist_for_db(players): -# player_list = [] -# for i in range(len(players)): -# player_list.append({players[i]["name"]: players[i]["id"]}) -# return player_list - - -# # put this in a separate file -# def get_households(token): -# """ -# Returns the list of households of the user -# """ -# url = f"https://api.ws.sonos.com/control/api/v1/households" - -# payload = {} -# headers = {"Authorization": f"Bearer {token}"} - -# response = requests.request("GET", url, headers=headers, data=payload) - -# return response - - -# def get_groups(household_id, token): -# """ -# Returns the list of groups of the user -# """ -# url = f"https://api.ws.sonos.com/control/api/v1/households/{household_id}/groups" - -# payload = {} -# headers = {"Authorization": f"Bearer {token}"} - -# response = requests.request("GET", url, headers=headers, data=payload) - -# return response - - -# def create_token(code, sonos_encoded_credentials, host): -# """ -# Creates a token given a code -# """ -# url = f"https://api.sonos.com/login/v3/oauth/access?grant_type=authorization_code&code={code}&redirect_uri=http://{host}/connect_sonos.api" - -# payload = {} -# headers = { -# "Authorization": f"Basic {sonos_encoded_credentials}", -# "Cookie": "JSESSIONID=F710019AF0A3B7126A8702577C883B5F; AWSELB=69BFEFC914A689BF6DC8E4652748D7B501ED60290D5EA56F2E543ABD7CF357A5F65186AEBCFB059E28075D83A700FD504C030A53CC28683B515BE3DCA3CC587AFAF606E171; AWSELBCORS=69BFEFC914A689BF6DC8E4652748D7B501ED60290D5EA56F2E543ABD7CF357A5F65186AEBCFB059E28075D83A700FD504C030A53CC28683B515BE3DCA3CC587AFAF606E171", -# } - -# response = requests.request("POST", url, headers=headers, data=payload) - -# return response.json() - - -# def toggle_play_pause(group_id, token): -# """ -# Toggles the play/pause of a group -# """ -# url = ( -# f"https://api.ws.sonos.com/control/api/v1/groups/{group_id}/playback/playPause" -# ) - -# payload = {} -# headers = {"Content-Type": "application/json", "Authorization": f"Bearer {token}"} - -# response = requests.request("POST", url, headers=headers, data=payload) - -# return response - - -# def audio_clip(audioclip_url, player_id, token): -# """ -# Plays an audioclip from link to .mp3 file -# """ -# import requests -# import json - -# url = f"https://api.ws.sonos.com/control/api/v1/players/{player_id}/audioClip" - -# payload = json.dumps( -# { -# "name": "Embla", -# "appId": "com.acme.app", -# "streamUrl": f"{audioclip_url}", -# "volume": 50, -# "priority": "HIGH", -# "clipType": "CUSTOM", -# } -# ) -# headers = { -# "Content-Type": "application/json", -# "Authorization": f"Bearer {token}", -# } - -# response = requests.request("POST", url, headers=headers, data=payload) diff --git a/queries/iot_hue.py b/queries/iot_hue.py index 80d12a5a..59c373f2 100755 --- a/queries/iot_hue.py +++ b/queries/iot_hue.py @@ -26,15 +26,14 @@ # TODO: add "láttu", "hafðu", "litaðu", "kveiktu" functionality. # TODO: make the objects of sentences more modular, so that the same structure doesn't need to be written for each action # TODO: ditto the previous comment. make the initial non-terminals general and go into specifics at the terminal level instead. -# TODO: substituion klósett, baðherbergi hugmyndÆ senda lista i javascript og profa i röð -# TODO: Embla stores old javascript code cached which has caused errors +# TODO: substitution klósett -> baðherbergi, for common room names and alternative ways of saying # TODO: Cut down javascript sent to Embla # TODO: Two specified groups or lights. # TODO: No specified location # TODO: Fix scene issues # TODO: Turning on lights without using "turn on" -# TODO: Add functionality for robot-like commands "ljós í eldhúsinu", "rauður í eldhúsinu" -# TODO: Heldur að 'gerðu ljósið kaldara' sé senan 'köld' +# TODO: Add functionality for robot-like commands "ljós í eldhúsinu", "rautt í eldhúsinu" +# TODO: Mistakes 'gerðu ljósið kaldara' for the scene 'köld' from typing import Dict, List, Optional, cast, FrozenSet from typing_extensions import TypedDict @@ -146,9 +145,8 @@ def QIoTTurnOffLightsRest(node: Node, params: QueryStateDict, result: Result) -> def QIoTNewColor(node: Node, params: QueryStateDict, result: Result) -> None: result.action = "set_color" - print(result.color_name) color_hue = _COLORS.get(result.color_name, None) - print(color_hue) + if color_hue is not None: if "hue_obj" not in result: result["hue_obj"] = {"on": True, "xy": color_hue} @@ -230,7 +228,6 @@ def QIoTDarkest(node: Node, params: QueryStateDict, result: Result) -> None: def QIoTNewScene(node: Node, params: QueryStateDict, result: Result) -> None: result.action = "set_scene" scene_name = result.get("scene_name", None) - print("scene: " + scene_name) if scene_name is not None: if "hue_obj" not in result: result["hue_obj"] = {"on": True, "scene": scene_name} @@ -240,9 +237,9 @@ def QIoTNewScene(node: Node, params: QueryStateDict, result: Result) -> None: def QIoTColorName(node: Node, params: QueryStateDict, result: Result) -> None: - result["color_name"] = ( - node.first_child(lambda x: True).string_self().strip("'").split(":")[0] - ) + fc = node.first_child(lambda x: True) + if fc: + result["color_name"] = fc.string_self().strip("'").split(":")[0] def QIoTSceneName(node: Node, params: QueryStateDict, result: Result) -> None: @@ -311,55 +308,45 @@ def QIoTSpeakerHotwords(node: Node, params: QueryStateDict, result: Result) -> N def sentence(state: QueryStateDict, result: Result) -> None: """Called when sentence processing is complete""" q: Query = state["query"] - print("start of sentence") if result.get("abort"): - print("aborted") q.set_error("E_QUERY_NOT_UNDERSTOOD") return + + # Extract matched terminals in grammar (used like lemmas in this case) lemmas = set( i[0].root(state, result.params) for i in result.enum_descendants(lambda x: isinstance(x, TerminalNode)) ) if not lemmas.isdisjoint(_SPEAKER_WORDS): - print("matched with music word list") q.set_error("E_QUERY_NOT_UNDERSTOOD") return changing_color = result.get("changing_color", False) changing_scene = result.get("changing_scene", False) changing_brightness = result.get("changing_brightness", False) # changing_temp = result.get("changing_temp", False) - print( - "error?", - sum((changing_color, changing_scene, changing_brightness)) > 1, - ) if ( sum((changing_color, changing_scene, changing_brightness)) > 1 or "qtype" not in result ): - print("ERROR") + print("Multiple options error?") q.set_error("E_QUERY_NOT_UNDERSTOOD") return q.set_qtype(result.qtype) smartdevice_type = "iot" - client_id = str(q.client_id) - print("client_id:", client_id) - - # Fetch relevant data from the device_data table to perform an action on the lights - device_data = cast( - Optional[DeviceData], q.client_data(smartdevice_type).get("iot_lights") - ) - print("location :", q.location) - print("device data :", device_data) + cd = q.client_data(smartdevice_type) + device_data = None + if cd: + # Fetch relevant data from the device_data table to perform an action on the lights + device_data = cast(Optional[DeviceData], cd.get("iot_lights")) - selected_light: Optional[str] = None - print("selected light:", selected_light) hue_credentials: Optional[Dict[str, str]] = None if device_data is not None: dev = device_data assert dev is not None + # TODO: Better error checking light = dev.get("philips_hue") hue_credentials = light.get("credentials") bridge_ip = hue_credentials.get("ip_address") @@ -370,19 +357,10 @@ def sentence(state: QueryStateDict, result: Result) -> None: q.set_answer(*gen_answer(answer)) return - # Successfully matched a query type - print("bridge_ip: ", bridge_ip) - print("username: ", username) - print("selected light :", selected_light) - print("hue credentials :", hue_credentials) - try: - # kalla í javascripts stuff + # TODO: What if light and group is empty? light_or_group_name = result.get("light_name", result.get("group_name", "")) - color_name = result.get("color_name", "") - print("GROUP NAME:", light_or_group_name) - print("COLOR NAME:", color_name) - print(result.hue_obj) + q.set_answer( {"answer": "Skal gert."}, "Skal gert.", @@ -401,6 +379,3 @@ def sentence(state: QueryStateDict, result: Result) -> None: logging.warning("Exception while processing random query: {0}".format(e)) q.set_error("E_EXCEPTION: {0}".format(e)) raise - - -# f"var BRIDGE_IP = '192.168.1.68';var USERNAME = 'p3obluiXT13IbHMpp4X63ZvZnpNRdbqqMt723gy2';" diff --git a/queries/iot_speakers.py b/queries/iot_speakers.py index cc123906..1348839b 100644 --- a/queries/iot_speakers.py +++ b/queries/iot_speakers.py @@ -22,8 +22,27 @@ of random numbers, e.g. "Kastaðu tengingi", "Nefndu tölu milli 5 og 10", etc. """ +# TODO: add "láttu", "hafðu", "litaðu", "kveiktu" functionality. +# TODO: make the objects of sentences more modular, so that the same structure doesn't need to be written for each action +# TODO: ditto the previous comment. make the initial non-terminals general and go into specifics at the terminal level instead. +# TODO: substituion klósett, baðherbergi hugmyndÆ senda lista i javascript og profa i röð +# TODO: Embla stores old javascript code cached which has caused errors +# TODO: Cut down javascript sent to Embla +# TODO: Two specified groups or lights. +# TODO: No specified location +# TODO: Fix scene issues +from typing import Dict + +import logging +import random + +from query import Query, QueryStateDict +from queries import read_grammar_file +from queries.sonos import SonosClient +from tree import Result, Node + # Dictionary of radio stations and their stream urls -_RADIO_STREAMS = { +_RADIO_STREAMS: Dict[str, str] = { "Rás 1": "http://netradio.ruv.is/ras1.mp3", "Rás 2": "http://netradio.ruv.is/ras2.mp3", "Rondó": "http://netradio.ruv.is/rondo.mp3", @@ -49,41 +68,9 @@ } -# TODO: add "láttu", "hafðu", "litaðu", "kveiktu" functionality. -# TODO: make the objects of sentences more modular, so that the same structure doesn't need to be written for each action -# TODO: ditto the previous comment. make the initial non-terminals general and go into specifics at the terminal level instead. -# TODO: substituion klósett, baðherbergi hugmyndÆ senda lista i javascript og profa i röð -# TODO: Embla stores old javascript code cached which has caused errors -# TODO: Cut down javascript sent to Embla -# TODO: Two specified groups or lights. -# TODO: No specified location -# TODO: Fix scene issues - -from os import access -from typing import Dict, Mapping, Optional, cast -from typing_extensions import TypedDict - -import logging -import random -import json -import flask -from datetime import datetime, timedelta - -from reynir.lemmatize import simple_lemmatize - -from query import Query, QueryStateDict, AnswerTuple -from queries import gen_answer, read_jsfile, read_grammar_file -from queries.sonos import SonosClient -from tree import Result, Node, TerminalNode -from util import read_api_key - - _IoT_QTYPE = "IoTSpeakers" -TOPIC_LEMMAS = [ - "tónlist", - "spila", -] +TOPIC_LEMMAS = ["tónlist", "spila", "útvarp", "útvarpsstöð"] def help_text(lemma: str) -> str: @@ -91,7 +78,10 @@ def help_text(lemma: str) -> str: one of the above lemmas is found in it""" return "Ég skil þig ef þú segir til dæmis: {0}.".format( random.choice( - ("Hækkaðu í tónlistinni", "Kveiktu á tónlist", "Láttu vera tónlist") + ( + "Hækkaðu í tónlistinni", + "Kveiktu á tónlist", + ) ) ) @@ -103,10 +93,7 @@ def help_text(lemma: str) -> str: QUERY_NONTERMINALS = {"QIoTSpeaker", "QIoTSpeakerQuery"} # The context-free grammar for the queries recognized by this plug-in module - -GRAMMAR = read_grammar_file( - "iot_speakers", -) +GRAMMAR = read_grammar_file("iot_speakers") def QIoTSpeaker(node: Node, params: QueryStateDict, result: Result) -> None: @@ -297,35 +284,35 @@ def QIoTSpeakerUtvarpSudurland( def sentence(state: QueryStateDict, result: Result) -> None: """Called when sentence processing is complete""" - print("sentence") q: Query = state["query"] if "qkey" not in result: result.qkey = "turn_on" if result.qkey == "turn_on" and result.get("target") == "radio": result.qkey = "radio" if "qtype" in result: - print("IF QTYPE AND QKEY") try: q.set_qtype(result.qtype) - device_data = q.client_data("iot").get("iot_speakers") + cd = q.client_data("iot") + device_data = None + if cd: + device_data = cd.get("iot_speakers") if device_data is not None: - print("JUST BEFORE SONOS CLIENT") sonos_client = SonosClient( device_data, q.client_id, group_name=result.get("group_name") ) - print("JUST AFTER SONOS CLIENT") + # Map of query keys to handler functions and the corresponding answer string for Embla radio_url = _RADIO_STREAMS.get(result.get("station")) handler_map = { "turn_on": [ sonos_client.toggle_play, [], - "Ég kveikti á tónlist", + "Ég kveikti á tónlistinni", ], "turn_off": [ sonos_client.toggle_pause, [], - "Ég slökkti á tónlist", + "Ég slökkti á tónlistinni", ], "increase_volume": [ sonos_client.increase_volume, @@ -345,12 +332,12 @@ def sentence(state: QueryStateDict, result: Result) -> None: "next_song": [ sonos_client.next_song, [], - "Ég hötta á næsta tón", + "Ég skipti á næsta lag", ], "prev_song": [ sonos_client.prev_song, [], - "Ég hötta á fyrri tón", + "Ég hötta á fyrri tón", # TODO: wtf ], } handler, args, answer = handler_map.get(result.qkey) @@ -358,14 +345,12 @@ def sentence(state: QueryStateDict, result: Result) -> None: if response == "Group not found": text_ans = f"Herbergið '{result.group_name}' fannst ekki. Vinsamlegast athugaðu í Sonos appinu hvort nafnið sé rétt." else: - handler_answer = answer - text_ans = handler_answer - answer = ( + text_ans = answer + q.set_answer( dict(answer=text_ans), text_ans, text_ans.replace("Sonos", "Sónos"), ) - q.set_answer(*answer) return else: print("No device data found for this account") @@ -379,4 +364,4 @@ def sentence(state: QueryStateDict, result: Result) -> None: q.set_error("E_QUERY_NOT_UNDERSTOOD") return - # # TODO: Need to add check for if there are no registered devices to an account, probably when initilazing the querydata + # TODO: Need to add check for if there are no registered devices to an account, probably when initilazing the querydata diff --git a/queries/iot_spotify.py b/queries/iot_spotify.py index 9662b261..0a9662a6 100644 --- a/queries/iot_spotify.py +++ b/queries/iot_spotify.py @@ -27,11 +27,12 @@ """ +# TODO: Make grammar -from query import Query -from datetime import datetime, timedelta import random import re + +from query import Query from queries.spotify import SpotifyClient from queries import gen_answer @@ -40,7 +41,7 @@ def help_text(lemma: str) -> str: """Help text to return when query.py is unable to parse a query but one of the above lemmas is found in it""" return "Ég skil þig ef þú segir til dæmis: {0}.".format( - random.choice(("Spilaðu Þorparinn með Pálma Gunnarssyni")) + random.choice(("Spilaðu Þorparann með Pálma Gunnarssyni",)) ) @@ -51,27 +52,19 @@ def help_text(lemma: str) -> str: # r"^spilaðu ([\w|\s]+) á spotify$", # r"^spilaðu ([\w|\s]+) á spotify", r"^spilaðu plötuna ([\w|\s]+) með ([\w|\s]+)$", + r"^spilaðu lagið ([\w|\s]+) með ([\w|\s]+)$", r"^spilaðu ([\w|\s]+) með ([\w|\s]+)$", # r"^spilaðu plötuna ([\w|\s]+)$ með ([\w|\s]+)$ á spotify$", ] -def handle_plain_text(q) -> bool: +def handle_plain_text(q: Query) -> bool: """Handle a plain text query requesting Spotify to play a specific song by a specific artist.""" - # ql = q.query_lower.strip().rstrip("?") - print("handle_plain_text") ql = q.query_lower.strip().rstrip("?") - print("QL:", ql) - - pfx = None for rx in _SPOTIFY_REGEXES: - print(rx) - print("") m = re.search(rx, ql) - print(m) if m: - (print("MATCH!")) song_name = m.group(1) artist_name = m.group(2).strip() print("SONG NAME :", song_name) @@ -100,9 +93,8 @@ def handle_plain_text(q) -> bool: else: song_url = spotify_client.get_song_by_artist() response = spotify_client.play_song_on_device() - # response = None - print("RESPONSE FROM SPOTIFY:", response) - answer = "Ég spilaði lagið" + + answer = "Ég set það í gang." if response is None: q.set_url(song_url) q.set_answer({"answer": answer}, answer, "") @@ -112,30 +104,4 @@ def handle_plain_text(q) -> bool: answer = "Það vantar að tengja Spotify aðgang." q.set_answer(*gen_answer(answer)) return True - else: - return False - - # Caching (optional) - q.set_expires(datetime.utcnow() + timedelta(hours=24)) - - # Context (optional) - # q.set_context(dict(subject="Prufuviðfangsefni")) - - # Source (optional) - # q.set_source("Prufumódúll") - - # Beautify query for end user display (optional) - # q.set_beautified_query(ql.upper()) - - # Javascript command to execute client-side (optional) - # q.set_command("2 + 2") - - # URL to be opened by client (optional) - # q.set_url("https://miðeind.is") - - return True - return False - - -# def get_song_and_artist(q: Query) -> tuple: diff --git a/queries/js/Philips_Hue/fuse_search.js b/queries/js/Philips_Hue/fuse_search.js index 57f3dec9..a6fe0ad7 100644 --- a/queries/js/Philips_Hue/fuse_search.js +++ b/queries/js/Philips_Hue/fuse_search.js @@ -20,14 +20,12 @@ function philipsFuzzySearch(query, data) { let searchResult = fuse.search(query); let resultObject = {}; - console.log("result: ", searchResult); if (searchResult[0] === undefined) { - console.log("no match found"); return null; } // Structure the return object to be in the form of {result: (Object), score: (Number)} resultObject.result = searchResult[0].item; resultObject.score = searchResult[0].score; - console.log("resultObject :", resultObject); + return resultObject; } diff --git a/queries/js/Philips_Hue/groups.js b/queries/js/Philips_Hue/groups.js deleted file mode 100644 index e69de29b..00000000 diff --git a/queries/js/Philips_Hue/hub.js b/queries/js/Philips_Hue/hub.js index 1e4c8407..fd295607 100644 --- a/queries/js/Philips_Hue/hub.js +++ b/queries/js/Philips_Hue/hub.js @@ -1,45 +1,29 @@ "use strict"; async function findHub() { - // let hubArr = []; - // let hubObj = new Object(); - // hubObj.id = "ecb5fafffe1be1a4"; - // hubObj.internalipaddress = "192.168.1.68"; - // hubObj.port = "443"; - // console.log(hubObj); - // hubArr.push(hubObj); - // return hubArr[0]; return fetch(`https://discovery.meethue.com`) .then((resp) => resp.json()) .then((obj) => { - console.log(obj); return obj[0]; }) - .catch((err) => { - console.log("No smart device found!"); - }); + .catch((err) => {}); } async function createNewDeveloper(ipAddress) { - console.log("create new developer"); - const body = JSON.stringify({ - devicetype: "mideind_hue_communication#smartdevice", - }); return fetch(`http://${ipAddress}/api`, { method: "POST", - body: body, + body: JSON.stringify({ + devicetype: "Embla", + }), }) .then((resp) => resp.json()) .then((obj) => { return obj[0]; }) - .catch((err) => { - console.log(err); - }); + .catch((err) => {}); } async function storeDevice(data, requestURL) { - console.log("store device"); return fetch(`http://${requestURL}/register_query_data.api`, { method: "POST", body: JSON.stringify(data), @@ -51,23 +35,16 @@ async function storeDevice(data, requestURL) { .then((obj) => { return obj; }) - .catch((err) => { - console.log("Error while storing user"); - }); + .catch((err) => {}); } -// clientID = "82AD3C91-7DA2-4502-BB17-075CEC090B14", requestURL = "192.168.1.68") async function connectHub(clientID, requestURL) { - console.log("connect hub"); let deviceInfo = await findHub(); - console.log("device info: ", deviceInfo); - console.log("device_ip :", deviceInfo.internalipaddress); try { let username = await createNewDeveloper(deviceInfo.internalipaddress); - console.log("username: ", username); if (!username.success) { - return "Ýttu á 'Philips' takkann á tengiboxinu og reyndu aftur"; + return "Ýttu á 'Philips' takkann á miðstöðinni og reyndu aftur"; } const data = { @@ -85,16 +62,9 @@ async function connectHub(clientID, requestURL) { }, }; - const result = await storeDevice(data, requestURL); - console.log("result: ", result); - return "Tenging við snjalltæki tókst"; + await storeDevice(data, requestURL); + return "Tenging við Philips Hue miðstöðina tókst!"; } catch (error) { - console.log(error); - return "Ekki tókst að tengja snjalltæki"; + return "Ekki tókst að tengja Philips Hue miðstöðina."; } } - -async function syncConnectHub(clientID, requestURL) { - connectHub(clientID, requestURL); - return "Philips Hue miðstöðin hefur verið tengd"; -} diff --git a/queries/js/Philips_Hue/lights.js b/queries/js/Philips_Hue/lights.js index b4c5d4da..451dd0e4 100644 --- a/queries/js/Philips_Hue/lights.js +++ b/queries/js/Philips_Hue/lights.js @@ -1,82 +1,17 @@ "use strict"; -function changeBrightness() { - let sliderValue = document.getElementById("brightness_slider").value; - console.log(sliderValue); - - fetch(`http://${BRIDGE_IP}/api/${USERNAME}/lights/1/state`, { - method: "PUT", - body: JSON.stringify({ bri: Number(sliderValue) }), - }) - .then((resp) => resp.json()) - .then((obj) => { - console.log(obj); - }) - .catch((err) => { - console.log("an error occurred!"); - }); -} - -function changeColor() { - let xValue = Number(document.getElementById("color_x").value); - let yValue = Number(document.getElementById("color_y").value); - console.log(xValue, yValue); - - if ( - xValue === undefined || - yValue === undefined || - xValue > 1 || - xValue < 0 || - yValue > 1 || - yValue < 0 - ) { - document.getElementById("color_error").innerHTML = - "Please enter a value between 0 and 1."; - } else { - document.getElementById("color_error").innerHTML = ""; - let colorValue = [Number(xValue), Number(yValue)]; - console.log(colorValue); - fetch(`http://${BRIDGE_IP}/api/${USERNAME}/lights/1/state`, { - method: "PUT", - body: JSON.stringify({ xy: colorValue }), - }) - .then((resp) => resp.json()) - .then((obj) => { - console.log(obj); - }) - .catch((err) => { - console.log("an error occurred!"); - }); - } -} - async function getAllLights(hub_ip = BRIDGE_IP, username = USERNAME) { - return fetch(`http://${hub_ip}/api/${username}/lights`).then((resp) => - resp.json() - ); + return fetch(`http://${hub_ip}/api/${username}/lights`).then((resp) => resp.json()); } async function getAllGroups(hub_ip = BRIDGE_IP, username = USERNAME) { - return fetch(`http://${hub_ip}/api/${username}/groups`).then((resp) => - resp.json() - ); + return fetch(`http://${hub_ip}/api/${username}/groups`).then((resp) => resp.json()); } async function getAllScenes(hub_ip = BRIDGE_IP, username = USERNAME) { - return fetch(`http://${hub_ip}/api/${username}/scenes`).then((resp) => - resp.json() - ); + return fetch(`http://${hub_ip}/api/${username}/scenes`).then((resp) => resp.json()); } function getCurrentState(id) { - return fetch(`http://${BRIDGE_IP}/api/${USERNAME}/lights/${id}`).then( - (resp) => resp.json() - ); -} - -async function getAllLightsAndGroupsFromHTML() { - var lights = await getAllLights(); - var groups = await getAllGroups(); - console.log("lights:", lights); - console.log("groups:", groups); + return fetch(`http://${BRIDGE_IP}/api/${USERNAME}/lights/${id}`).then((resp) => resp.json()); } diff --git a/queries/js/Philips_Hue/scenes.js b/queries/js/Philips_Hue/scenes.js deleted file mode 100644 index 51774bf8..00000000 --- a/queries/js/Philips_Hue/scenes.js +++ /dev/null @@ -1,5 +0,0 @@ -function getAllScenes(hub_ip = BRIDGE_IP, username = USERNAME) { - return fetch(`http://${hub_ip}/api/${username}/scenes`).then((resp) => - resp.json() - ); -} diff --git a/queries/js/Philips_Hue/set_lights.js b/queries/js/Philips_Hue/set_lights.js index df60e383..ef5eeb21 100644 --- a/queries/js/Philips_Hue/set_lights.js +++ b/queries/js/Philips_Hue/set_lights.js @@ -1,95 +1,13 @@ "use strict"; -// Constants to be used when setting lights from HTML -// var BRIDGE_IP = "192.168.1.68"; -// var USERNAME = "BzdNyxr6mGSHVdQN86UeZP67qp5huJ2Q6TWyTzvz"; - -// TODO: Implement a hotfix for Ikea Tradfri bulbs, since it can only take one argument at a time - -/** Gets a target for the given query and sets the state of the target to the given state using a fetch request. - * @param {String} target - the target to find the target e.g. "eldhús" or "lampi" - * @param {String} state - the state to set the target to e.g. "{"on": true}" or "{"scene": "energize"}" - */ -function setLights(target, state) { - let parsedState = JSON.parse(state); - let promiseList = [getAllGroups(), getAllLights()]; - let sceneName; - if (parsedState.scene) { - sceneName = parsedState.scene; - promiseList.push(getAllScenes()); - } - // Get all lights and all groups from the API (and all scenes if "scene" was a paramater) - Promise.allSettled(promiseList).then((resolvedPromises) => { - let allGroups = resolvedPromises[0].value; - let allLights = resolvedPromises[1].value; - let allScenes; - try { - allScenes = resolvedPromises[2].value; - } catch (e) { - console.log("No scene in state"); - } - - // Get the target object for the given target - let targetObject = getTargetObject(target, allLights, allGroups); - if (targetObject === undefined) { - return "Ekki tókst að finna ljós"; - } - - // Check if state includes a scene or a brightness change - if (sceneName) { - let sceneID = getSceneID(parsedState.scene, allScenes); - if (sceneID === undefined) { - return "Ekki tókst að finna senu"; - } else { - parsedState.scene = sceneID; // Change the scene parameter to the scene ID - state = JSON.stringify(parsedState); - } - } else if (parsedState.bri_inc) { - state = JSON.stringify(parsedState); - } - - // Send data to API - let url = targetObject.url; - call_api(url, state); - let isTradfriBulb = check_if_if_ikea_bulb_in_group( - targetObject, - allLights - ); - if (sceneName && isTradfriBulb) { - const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); - sleep(450).then(() => { - call_api(url, state); - }); - } - }); -} -// fetch(`http://${BRIDGE_IP}/api/${USERNAME}/${url}`, { -// method: "PUT", -// body: state, -// }) -// .then((resp) => resp.json()) -// .then((obj) => { -// console.log(obj); -// }) -// .catch((err) => { -// console.log("an error occurred!"); -// }); -// }); -// } - function call_api(url, state) { - console.log("call api"); fetch(`http://${BRIDGE_IP}/api/${USERNAME}/${url}`, { method: "PUT", body: state, }) .then((resp) => resp.json()) - .then((obj) => { - console.log(obj); - }) - .catch((err) => { - console.log("an error occurred!"); - }); + .then((obj) => {}) + .catch((err) => {}); return; } @@ -141,57 +59,40 @@ function getTargetObject(target, allLights, allGroups) { */ function getSceneID(scene_name, allScenes) { let scenesResult = philipsFuzzySearch(scene_name, allScenes); - console.log("sceneResult :", scenesResult); if (scenesResult != null) { return scenesResult.result.ID; } else { return; } } -// TODO: Remove me -// /* Tester function for setting lights directly from HTML controls */ -// function setLightsFromHTML() { -// let target = document.getElementById("queryInput").value; -// let stateObject = new Object(); -// stateObject.bri_inc = Number(document.getElementById("brightnessInput").value); -// stateObject = JSON.stringify(stateObject); -// return setLights(target, stateObject); -// } -// /* Tester function for setting lights directly from HTML input fields */ -// function queryTestFromHTML() { -// let target = document.getElementById("queryInput").value; -// let bool = document.getElementById("boolInput").value; -// let scene = document.getElementById("sceneInput").value; -// console.log(target); -// if (scene === "") { -// setLights(target, `{"on": ${bool}}`); -// } else { -// setLights(target, `{"scene": "${scene}"}`); -// } -// } - -// TODO: Add docstring -function check_if_ikea_bulb_in_group(groupsObject, all_lights) { - for (let key in groupsObject.lights) { - let lightID = groupsObject.lights[key]; +/** + * Check whether any of the targeted lights are Ikea TRADFRI lights. + * Done in order to deal with a bug where the lights only accept + * one parameter at a time. + * @param {Object} targetObject Object containing all lights in target/query + * @param {Object} all_lights Object containing info for all connected lights + * @returns True if any of the query lights are Ikea TRADFRI lights, false otherwise + */ +function check_if_ikea_bulb_in_target(targetObject, all_lights) { + for (let key in targetObject.lights) { + let lightID = targetObject.lights[key]; let light = all_lights[lightID]; if ( light.manufacturername.includes("IKEA") || light.modelid.includes("TRADFRI") || light.manufacturername.includes("ikea") || light.manufacturername.includes("tradfri") - ); - - { + ) { return true; } } } /** Gets a target for the given query and sets the state of the target to the given state using a fetch request. - * @param {String} target - the target to find the target e.g. "eldhús" or "lampi" - * @param {String} state - the state to set the target to e.g. "{"on": true}" or "{"scene": "energize"}" + * @param {String} target - the target to find the target, e.g. "eldhús" or "lampi" + * @param {String} state - the state to set the target to e.g. {"on": true} or {"scene": "energize"} + * @return Basic string explaining what happened (in Icelandic). */ async function setLights(target, state) { let parsedState = JSON.parse(state); @@ -206,10 +107,8 @@ async function setLights(target, state) { let allGroups = resolvedPromises[0].value; let allLights = resolvedPromises[1].value; let allScenes; - try { + if (resolvedPromises.length > 2) { allScenes = resolvedPromises[2].value; - } catch (e) { - console.log("No scene in state"); } // Get the target object for the given target @@ -234,16 +133,16 @@ async function setLights(target, state) { let url = targetObject.url; call_api(url, state); - let isTradfriBulb = check_if_ikea_bulb_in_group( - targetObject, - allLights - ); + // Deal with Ikea TRADFRI bug + let isTradfriBulb = check_if_ikea_bulb_in_target(targetObject, allLights); if (sceneName && isTradfriBulb) { let sleep = (ms) => new Promise((r) => setTimeout(r, ms)); sleep(450).then(() => { call_api(url, state); }); } + + // Basic formatting of answers if (parsedState.scene) { return "Ég breytti um senu."; } @@ -265,45 +164,3 @@ async function setLights(target, state) { return "Stillingu hefur verið breytt."; }); } - -// /** Finds a matching light or group and returns an object with the ID, name and url for the target -// * @param {String} target - the target to find the target e.g. "eldhús" -// * @param {Object} allLights - an array of all lights from the API -// * @param {Object} allGroups - an array of all groups from the API -// */ -// function getTargetObjectOLD(target, allLights, allGroups) { -// let targetObject; -// let lightsResult = philipsFuzzySearch(target, allLights); -// let groupsResult = philipsFuzzySearch(target, allGroups); -// console.log("lightsResult: ", lightsResult); -// console.log("groupsResult: ", groupsResult); - -// if (lightsResult != null && groupsResult == null) { -// // Found a match for a single light -// targetObject = { -// id: lightsResult.result.ID, -// type: "light", -// url: `lights/${lightsResult.result.ID}/state`, -// }; -// } else if (groupsResult != null && lightsResult == null) { -// // Found a match for a light group -// targetObject = { -// id: groupsResult.result.ID, -// type: "group", -// url: `groups/${groupsResult.result.ID}/action`, -// }; -// } else if (groupsResult != null && lightsResult != null) { -// let lightsScore = lightsResult.score; -// let groupsScore = groupsResult.score; -// let selection = lightsScore > groupsScore ? lightsResult : groupsResult; -// console.log("selection :", selection); -// // Found a match for a light group and a light -// targetObject = { -// id: lightsResult.result.ID, -// type: "light", -// url: `lights/${lightsResult.result.ID}/state`, -// }; -// } -// console.log("targetObject: ", targetObject); -// return targetObject; -// } diff --git a/queries/js/Smart_Things/fuse_search_st.js b/queries/js/Smart_Things/fuse_search_st.js deleted file mode 100644 index 42c88333..00000000 --- a/queries/js/Smart_Things/fuse_search_st.js +++ /dev/null @@ -1,32 +0,0 @@ -/* -Fuzzy search function that returns an object in the form of {result: (Object), score: (Number)} -* @param {String} query - The search term -* @param {Object} data - The data to search -*/ -function smartThingsFuzzySearch(query, data) { - // Restructure data to be searchable by name - var newData = Object.keys(data).map(function (key) { - return { ID: key, info: data[key] }; - }); - // Fuzzy search for the query term (returns an array of objects) - var fuse = new Fuse(newData, { - keys: ["info", "info.name"], - includeScore: true, - shouldSort: true, - threshold: 0.5, - }); - let searchResult = fuse.search(query); - - let resultObject = new Object(); - console.log("result: ", searchResult); - if (searchResult[0] === undefined) { - console.log("no match found"); - return null; - } else { - // Structure the return object to be in the form of {result: (Object), score: (Number)} - resultObject.result = searchResult[0].item; - resultObject.score = searchResult[0].score; - console.log("resultObject :", resultObject); - return resultObject; - } -} diff --git a/queries/js/Smart_Things/st.html b/queries/js/Smart_Things/st.html deleted file mode 100644 index bffccf9e..00000000 --- a/queries/js/Smart_Things/st.html +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - JS Hue test - - - - -
    -

    SmartThings Test

    -
    - - -
    -
    - - -
    - - ui4 - string - -
    - - - diff --git a/queries/js/Smart_Things/st.js b/queries/js/Smart_Things/st.js deleted file mode 100644 index 2f8662fa..00000000 --- a/queries/js/Smart_Things/st.js +++ /dev/null @@ -1,169 +0,0 @@ -const AUTH_TOKEN = ""; - -function turnOnLight() { - var myHeaders = new Headers(); - myHeaders.append("Authorization", AUTH_TOKEN); - myHeaders.append("Content-Type", "application/json"); - - var raw = JSON.stringify({ - commands: [ - { - component: "main", - capability: "switch", - command: "on", - }, - ], - }); - - var requestOptions = { - method: "POST", - headers: myHeaders, - body: raw, - redirect: "follow", - }; - - fetch( - "https://api.smartthings.com/v1/devices/7d47b44f-057c-4320-9777-3d1eadca106e/commands", - requestOptions - ) - .then((response) => response.text()) - .then((result) => console.log(result)) - .catch((error) => console.log("error", error)); -} - -function turnOffLight() { - var myHeaders = new Headers(); - myHeaders.append("Authorization", AUTH_TOKEN); - myHeaders.append("Content-Type", "application/json"); - - var raw = JSON.stringify({ - commands: [ - { - component: "main", - capability: "switch", - command: "off", - }, - ], - }); - - var requestOptions = { - method: "POST", - headers: myHeaders, - body: raw, - redirect: "follow", - }; - - fetch( - "https://api.smartthings.com/v1/devices/7d47b44f-057c-4320-9777-3d1eadca106e/commands", - requestOptions - ) - .then((response) => response.text()) - .then((result) => console.log(result)) - .catch((error) => console.log("error", error)); -} - -function raiseBrightness() { - var myHeaders = new Headers(); - myHeaders.append("Authorization", AUTH_TOKEN); - myHeaders.append("Content-Type", "application/json"); - - var raw = JSON.stringify({ - commands: [ - { - component: "main", - capability: "switchLevel", - command: "setLevel", - arguments: "rate"[100], - }, - ], - }); - - var requestOptions = { - method: "POST", - headers: myHeaders, - body: raw, - redirect: "follow", - }; - - fetch( - "https://api.smartthings.com/v1/devices/7d47b44f-057c-4320-9777-3d1eadca106e/commands", - requestOptions - ) - .then((response) => response.text()) - .then((result) => console.log(result)) - .catch((error) => console.log("error", error)); -} - -function lowerBrightness() { - var myHeaders = new Headers(); - myHeaders.append("Authorization", AUTH_TOKEN); - myHeaders.append("Content-Type", "application/json"); - - var raw = JSON.stringify({ - commands: [ - { - component: "main", - capability: "switchLevel", - command: "setLevel", - arguments: [1], - }, - ], - }); - - var requestOptions = { - method: "POST", - headers: myHeaders, - body: raw, - redirect: "follow", - }; - - fetch( - "https://api.smartthings.com/v1/devices/7d47b44f-057c-4320-9777-3d1eadca106e/commands", - requestOptions - ) - .then((response) => response.text()) - .then((result) => console.log(result)) - .catch((error) => console.log("error", error)); -} - -function smartThingsWrapper( - query, - device, - capability, - command, - arguments = null -) { - targetObject = smartThingsFuzzySearch(query); - - var myHeaders = new Headers(); - myHeaders.append("Authorization", AUTH_TOKEN); - myHeaders.append("Content-Type", "application/json"); - - var raw = JSON.stringify({ - commands: [ - { - component: device, - capability: capability, - command: command, - if(arguments) { - arguments: [arguments]; - }, - }, - ], - }); - - var requestOptions = { - method: "POST", - headers: myHeaders, - body: raw, - redirect: "follow", - }; - - fetch( - `"https://api.smartthings.com/v1/devices/${device}/commands"`, - requestOptions - ) - .then((response) => response.text()) - .then((result) => console.log(result)) - .catch((error) => console.log("error", error)); -} diff --git a/queries/js/Smart_Things/st_connecthub.js b/queries/js/Smart_Things/st_connecthub.js deleted file mode 100644 index e73efd1b..00000000 --- a/queries/js/Smart_Things/st_connecthub.js +++ /dev/null @@ -1,48 +0,0 @@ -async function storeDevice(data, ipAddress) { - console.log("store device"); - return fetch(`http://${ipAddress}/register_query_data.api`, { - method: "POST", - body: JSON.stringify(data), - headers: { - "Content-Type": "application/json", - }, - }) - .then((resp) => resp.json()) - .then((obj) => { - return obj; - }) - .catch((err) => { - console.log("Error while storing user"); - }); -} - -// bearer token 64780d2b-b763-433d-95ca-3eaaf5e10642 -async function connectHub(clientID, ipAddress, bearerToken) { - console.log("connect hub"); - - try { - const data = { - client_id: clientID, - key: "smart_hubs", - data: { - hubs: { - selected_hub: "smart_things", - smart_things: { - bearer_token: bearerToken, - }, - }, - }, - }; - - const result = await storeDevice(data, ipAddress); - console.log("result: ", result); - return "Tenging við snjalltæki tókst"; - } catch (error) { - console.log(error); - return "Ekki tókst að tengja snjalltæki"; - } -} - -function syncConnectHub(cliendID, ipAdress) { - connectHub(cliendID, ipAdress); -} diff --git a/queries/js/Smart_Things/test.js b/queries/js/Smart_Things/test.js deleted file mode 100644 index a16d5bcb..00000000 --- a/queries/js/Smart_Things/test.js +++ /dev/null @@ -1,24426 +0,0 @@ -(window["webpackJsonp"] = window["webpackJsonp"] || []).push([ - ["chunk-vendors"], - { - "0029": function (t, e) { - t.exports = - "constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split( - "," - ); - }, - "012f": function (t, e, n) { - "use strict"; - var r = n("d13f"), - i = n("f2fe"), - o = n("bc25"), - a = n("560b"); - t.exports = function (t) { - r(r.S, t, { - from: function (t) { - var e, - n, - r, - s, - u = arguments[1]; - return ( - i(this), - (e = void 0 !== u), - e && i(u), - void 0 == t - ? new this() - : ((n = []), - e - ? ((r = 0), - (s = o(u, arguments[2], 2)), - a(t, !1, function (t) { - n.push(s(t, r++)); - })) - : a(t, !1, n.push, n), - new this(n)) - ); - }, - }); - }; - }, - "0185": function (t, e, n) { - var r = n("e5fa"); - t.exports = function (t) { - return Object(r(t)); - }; - }, - "01f9": function (t, e, n) { - "use strict"; - var r = n("2d00"), - i = n("5ca1"), - o = n("2aba"), - a = n("32e9"), - s = n("84f2"), - u = n("41a0"), - c = n("7f20"), - f = n("38fd"), - l = n("2b4c")("iterator"), - d = !([].keys && "next" in [].keys()), - p = "@@iterator", - h = "keys", - v = "values", - y = function () { - return this; - }; - t.exports = function (t, e, n, m, g, b, _) { - u(n, e, m); - var w, - x, - k, - S = function (t) { - if (!d && t in A) return A[t]; - switch (t) { - case h: - return function () { - return new n(this, t); - }; - case v: - return function () { - return new n(this, t); - }; - } - return function () { - return new n(this, t); - }; - }, - E = e + " Iterator", - O = g == v, - T = !1, - A = t.prototype, - C = A[l] || A[p] || (g && A[g]), - j = C || S(g), - I = g ? (O ? S("entries") : j) : void 0, - M = ("Array" == e && A.entries) || C; - if ( - (M && - ((k = f(M.call(new t()))), - k !== Object.prototype && - k.next && - (c(k, E, !0), - r || "function" == typeof k[l] || a(k, l, y))), - O && - C && - C.name !== v && - ((T = !0), - (j = function () { - return C.call(this); - })), - (r && !_) || (!d && !T && A[l]) || a(A, l, j), - (s[e] = j), - (s[E] = y), - g) - ) - if ( - ((w = { - values: O ? j : S(v), - keys: b ? j : S(h), - entries: I, - }), - _) - ) - for (x in w) x in A || o(A, x, w[x]); - else i(i.P + i.F * (d || T), e, w); - return w; - }; - }, - "0234": function (t, e, n) { - "use strict"; - function r(t) { - for (var e = 1; e < arguments.length; e++) { - var n = null != arguments[e] ? arguments[e] : {}, - r = Object.keys(n); - "function" === typeof Object.getOwnPropertySymbols && - (r = r.concat( - Object.getOwnPropertySymbols(n).filter(function ( - t - ) { - return Object.getOwnPropertyDescriptor(n, t) - .enumerable; - }) - )), - r.forEach(function (e) { - i(t, e, n[e]); - }); - } - return t; - } - function i(t, e, n) { - return ( - e in t - ? Object.defineProperty(t, e, { - value: n, - enumerable: !0, - configurable: !0, - writable: !0, - }) - : (t[e] = n), - t - ); - } - function o(t) { - return ( - (o = - "function" === typeof Symbol && - "symbol" === typeof Symbol.iterator - ? function (t) { - return typeof t; - } - : function (t) { - return t && - "function" === typeof Symbol && - t.constructor === Symbol && - t !== Symbol.prototype - ? "symbol" - : typeof t; - }), - o(t) - ); - } - Object.defineProperty(e, "__esModule", { value: !0 }), - (e.pushParams = c), - (e.popParams = f), - (e.withParams = h), - (e._setTarget = e.target = void 0); - var a = [], - s = null; - e.target = s; - var u = function (t) { - e.target = s = t; - }; - function c() { - null !== s && a.push(s), (e.target = s = {}); - } - function f() { - var t = s, - n = (e.target = s = a.pop() || null); - return ( - n && - (Array.isArray(n.$sub) || (n.$sub = []), - n.$sub.push(t)), - t - ); - } - function l(t) { - if ("object" !== o(t) || Array.isArray(t)) - throw new Error("params must be an object"); - e.target = s = r({}, s, t); - } - function d(t, e) { - return p(function (n) { - return function () { - n(t); - for ( - var r = arguments.length, i = new Array(r), o = 0; - o < r; - o++ - ) - i[o] = arguments[o]; - return e.apply(this, i); - }; - }); - } - function p(t) { - var e = t(l); - return function () { - c(); - try { - for ( - var t = arguments.length, n = new Array(t), r = 0; - r < t; - r++ - ) - n[r] = arguments[r]; - return e.apply(this, n); - } finally { - f(); - } - }; - } - function h(t, e) { - return "object" === o(t) && void 0 !== e ? d(t, e) : p(t); - } - e._setTarget = u; - }, - "02d7": function (t, e, n) { - var r = n("6f8a"), - i = n("b5aa"), - o = n("1b55")("species"); - t.exports = function (t) { - var e; - return ( - i(t) && - ((e = t.constructor), - "function" != typeof e || - (e !== Array && !i(e.prototype)) || - (e = void 0), - r(e) && ((e = e[o]), null === e && (e = void 0))), - void 0 === e ? Array : e - ); - }; - }, - "02f4": function (t, e, n) { - var r = n("4588"), - i = n("be13"); - t.exports = function (t) { - return function (e, n) { - var o, - a, - s = String(i(e)), - u = r(n), - c = s.length; - return u < 0 || u >= c - ? t - ? "" - : void 0 - : ((o = s.charCodeAt(u)), - o < 55296 || - o > 56319 || - u + 1 === c || - (a = s.charCodeAt(u + 1)) < 56320 || - a > 57343 - ? t - ? s.charAt(u) - : o - : t - ? s.slice(u, u + 2) - : a - 56320 + ((o - 55296) << 10) + 65536); - }; - }; - }, - "034c": function (t, e, n) { - var r = n("7d8a"), - i = n("adf3"); - t.exports = function (t) { - return function () { - if (r(this) != t) - throw TypeError(t + "#toJSON isn't generic"); - return i(this); - }; - }; - }, - "0390": function (t, e, n) { - "use strict"; - var r = n("02f4")(!0); - t.exports = function (t, e, n) { - return e + (n ? r(t, e).length : 1); - }; - }, - "061b": function (t, e, n) { - t.exports = n("7017"); - }, - "0780": function (t, e, n) { - "use strict"; - var r = n("3adc").f, - i = n("7108"), - o = n("3904"), - a = n("bc25"), - s = n("b0bc"), - u = n("560b"), - c = n("e4a9"), - f = n("245b"), - l = n("1be4"), - d = n("7d95"), - p = n("6277").fastKey, - h = n("1fca"), - v = d ? "_s" : "size", - y = function (t, e) { - var n, - r = p(e); - if ("F" !== r) return t._i[r]; - for (n = t._f; n; n = n.n) if (n.k == e) return n; - }; - t.exports = { - getConstructor: function (t, e, n, c) { - var f = t(function (t, r) { - s(t, f, e, "_i"), - (t._t = e), - (t._i = i(null)), - (t._f = void 0), - (t._l = void 0), - (t[v] = 0), - void 0 != r && u(r, n, t[c], t); - }); - return ( - o(f.prototype, { - clear: function () { - for ( - var t = h(this, e), n = t._i, r = t._f; - r; - r = r.n - ) - (r.r = !0), - r.p && (r.p = r.p.n = void 0), - delete n[r.i]; - (t._f = t._l = void 0), (t[v] = 0); - }, - delete: function (t) { - var n = h(this, e), - r = y(n, t); - if (r) { - var i = r.n, - o = r.p; - delete n._i[r.i], - (r.r = !0), - o && (o.n = i), - i && (i.p = o), - n._f == r && (n._f = i), - n._l == r && (n._l = o), - n[v]--; - } - return !!r; - }, - forEach: function (t) { - h(this, e); - var n, - r = a( - t, - arguments.length > 1 - ? arguments[1] - : void 0, - 3 - ); - while ((n = n ? n.n : this._f)) { - r(n.v, n.k, this); - while (n && n.r) n = n.p; - } - }, - has: function (t) { - return !!y(h(this, e), t); - }, - }), - d && - r(f.prototype, "size", { - get: function () { - return h(this, e)[v]; - }, - }), - f - ); - }, - def: function (t, e, n) { - var r, - i, - o = y(t, e); - return ( - o - ? (o.v = n) - : ((t._l = o = - { - i: (i = p(e, !0)), - k: e, - v: n, - p: (r = t._l), - n: void 0, - r: !1, - }), - t._f || (t._f = o), - r && (r.n = o), - t[v]++, - "F" !== i && (t._i[i] = o)), - t - ); - }, - getEntry: y, - setStrong: function (t, e, n) { - c( - t, - e, - function (t, n) { - (this._t = h(t, e)), - (this._k = n), - (this._l = void 0); - }, - function () { - var t = this, - e = t._k, - n = t._l; - while (n && n.r) n = n.p; - return t._t && (t._l = n = n ? n.n : t._t._f) - ? f( - 0, - "keys" == e - ? n.k - : "values" == e - ? n.v - : [n.k, n.v] - ) - : ((t._t = void 0), f(1)); - }, - n ? "entries" : "values", - !n, - !0 - ), - l(e); - }, - }; - }, - "07c8": function (t, e, n) { - var r = n("d13f"); - r(r.S, "Object", { setPrototypeOf: n("6494").set }); - }, - "097d": function (t, e, n) { - "use strict"; - var r = n("5ca1"), - i = n("8378"), - o = n("7726"), - a = n("ebd6"), - s = n("bcaa"); - r(r.P + r.R, "Promise", { - finally: function (t) { - var e = a(this, i.Promise || o.Promise), - n = "function" == typeof t; - return this.then( - n - ? function (n) { - return s(e, t()).then(function () { - return n; - }); - } - : t, - n - ? function (n) { - return s(e, t()).then(function () { - throw n; - }); - } - : t - ); - }, - }); - }, - "0a0a": function (t, e, n) { - var r = n("da3c"), - i = n("a7d3"), - o = n("b457"), - a = n("fda1"), - s = n("3adc").f; - t.exports = function (t) { - var e = i.Symbol || (i.Symbol = o ? {} : r.Symbol || {}); - "_" == t.charAt(0) || t in e || s(e, t, { value: a.f(t) }); - }; - }, - "0a49": function (t, e, n) { - var r = n("9b43"), - i = n("626a"), - o = n("4bf8"), - a = n("9def"), - s = n("cd1c"); - t.exports = function (t, e) { - var n = 1 == t, - u = 2 == t, - c = 3 == t, - f = 4 == t, - l = 6 == t, - d = 5 == t || l, - p = e || s; - return function (e, s, h) { - for ( - var v, - y, - m = o(e), - g = i(m), - b = r(s, h, 3), - _ = a(g.length), - w = 0, - x = n ? p(e, _) : u ? p(e, 0) : void 0; - _ > w; - w++ - ) - if ((d || w in g) && ((v = g[w]), (y = b(v, w, m)), t)) - if (n) x[w] = y; - else if (y) - switch (t) { - case 3: - return !0; - case 5: - return v; - case 6: - return w; - case 2: - x.push(v); - } - else if (f) return !1; - return l ? -1 : c || f ? f : x; - }; - }; - }, - "0bfb": function (t, e, n) { - "use strict"; - var r = n("cb7c"); - t.exports = function () { - var t = r(this), - e = ""; - return ( - t.global && (e += "g"), - t.ignoreCase && (e += "i"), - t.multiline && (e += "m"), - t.unicode && (e += "u"), - t.sticky && (e += "y"), - e - ); - }; - }, - "0d42": function (t, e, n) { - "use strict"; - var r = { - log: console.log.bind(console), - warn: console.warn.bind(console), - error: console.error.bind(console), - }, - i = "?"; - function o(t) { - var e = [], - n = h(t, "stack"); - return ( - n && - n.split("\n").forEach(function (t) { - var n = u(t) || f(t) || p(t); - n && (!n.func && n.line && (n.func = i), e.push(n)); - }), - { message: h(t, "message"), name: h(t, "name"), stack: e } - ); - } - var a = - /^\s*at (.*?) ?\(((?:file|https?|blob|chrome-extension|native|eval|webpack||\/).*?)(?::(\d+))?(?::(\d+))?\)?\s*$/i, - s = /\((\S*)(?::(\d+))(?::(\d+))\)/; - function u(t) { - var e = a.exec(t); - if (e) { - var n = e[2] && 0 === e[2].indexOf("native"), - r = e[2] && 0 === e[2].indexOf("eval"), - o = s.exec(e[2]); - return ( - r && o && ((e[2] = o[1]), (e[3] = o[2]), (e[4] = o[3])), - { - args: n ? [e[2]] : [], - column: e[4] ? +e[4] : void 0, - func: e[1] || i, - line: e[3] ? +e[3] : void 0, - url: n ? void 0 : e[2], - } - ); - } - } - var c = - /^\s*at (?:((?:\[object object\])?.+) )?\(?((?:file|ms-appx|https?|webpack|blob):.*?):(\d+)(?::(\d+))?\)?\s*$/i; - function f(t) { - var e = c.exec(t); - if (e) - return { - args: [], - column: e[4] ? +e[4] : void 0, - func: e[1] || i, - line: +e[3], - url: e[2], - }; - } - var l = - /^\s*(.*?)(?:\((.*?)\))?(?:^|@)((?:file|https?|blob|chrome|webpack|resource|capacitor|\[native).*?|[^@]*bundle)(?::(\d+))?(?::(\d+))?\s*$/i, - d = /(\S+) line (\d+)(?: > eval line \d+)* > eval/i; - function p(t) { - var e = l.exec(t); - if (e) { - var n = e[3] && e[3].indexOf(" > eval") > -1, - r = d.exec(e[3]); - return ( - n && - r && - ((e[3] = r[1]), (e[4] = r[2]), (e[5] = void 0)), - { - args: e[2] ? e[2].split(",") : [], - column: e[5] ? +e[5] : void 0, - func: e[1] || i, - line: e[4] ? +e[4] : void 0, - url: e[3], - } - ); - } - } - function h(t, e) { - if ("object" === typeof t && t && e in t) { - var n = t[e]; - return "string" === typeof n ? n : void 0; - } - } - var v = 1e3, - y = 60 * v, - m = 60 * y, - g = 24 * m, - b = 365 * g, - _ = 1024; - function w(t, e, n) { - var r, - i, - o = !n || void 0 === n.leading || n.leading, - a = !n || void 0 === n.trailing || n.trailing, - s = !1; - return { - throttled: function () { - for (var n = [], u = 0; u < arguments.length; u++) - n[u] = arguments[u]; - s - ? (r = n) - : (o ? t.apply(void 0, n) : (r = n), - (s = !0), - (i = setTimeout(function () { - a && r && t.apply(void 0, r), - (s = !1), - (r = void 0); - }, e))); - }, - cancel: function () { - clearTimeout(i), (s = !1), (r = void 0); - }, - }; - } - function x(t) { - for (var e = [], n = 1; n < arguments.length; n++) - e[n - 1] = arguments[n]; - return ( - e.forEach(function (e) { - for (var n in e) - Object.prototype.hasOwnProperty.call(e, n) && - (t[n] = e[n]); - }), - t - ); - } - function k(t) { - return x({}, t); - } - function S(t) { - return t - ? ( - parseInt(t, 10) ^ - ((16 * Math.random()) >> (parseInt(t, 10) / 4)) - ).toString(16) - : "" - .concat(1e7, "-") - .concat(1e3, "-") - .concat(4e3, "-") - .concat(8e3, "-") - .concat(1e11) - .replace(/[018]/g, S); - } - function E(t) { - return 0 !== t && 100 * Math.random() <= t; - } - function O(t, e) { - return +t.toFixed(e); - } - function T() {} - function A(t, e, n) { - if (null === t || void 0 === t) return JSON.stringify(t); - var r = [!1, void 0]; - C(t) && ((r = [!0, t.toJSON]), delete t.toJSON); - var i, - o, - a = [!1, void 0]; - "object" === typeof t && - ((i = Object.getPrototypeOf(t)), - C(i) && ((a = [!0, i.toJSON]), delete i.toJSON)); - try { - o = JSON.stringify(t, e, n); - } catch (s) { - o = ""; - } finally { - r[0] && (t.toJSON = r[1]), a[0] && (i.toJSON = a[1]); - } - return o; - } - function C(t) { - return ( - "object" === typeof t && - null !== t && - Object.prototype.hasOwnProperty.call(t, "toJSON") - ); - } - function j(t, e) { - return -1 !== t.indexOf(e); - } - function I(t, e) { - for (var n = 0; n < t.length; n += 1) { - var r = t[n]; - if (e(r, n, t)) return r; - } - } - function M(t, e) { - for (var n = t.length - 1; n >= 0; n -= 1) { - var r = t[n]; - if (e(r, n, t)) return r; - } - } - function L(t) { - return P(t) && t >= 0 && t <= 100; - } - function P(t) { - return "number" === typeof t; - } - function N(t) { - return Object.keys(t).map(function (e) { - return t[e]; - }); - } - function R(t, e) { - return Object.keys(t).some(function (n) { - return t[n] === e; - }); - } - function $(t) { - return Object.keys(t).map(function (e) { - return [e, t[e]]; - }); - } - function D(t) { - return 0 === Object.keys(t).length; - } - function F(t, e) { - for (var n = {}, r = 0, i = Object.keys(t); r < i.length; r++) { - var o = i[r]; - n[o] = e(t[o]); - } - return n; - } - function z() { - if ("object" === typeof globalThis) return globalThis; - Object.defineProperty(Object.prototype, "_dd_temp_", { - get: function () { - return this; - }, - configurable: !0, - }); - var t = _dd_temp_; - return ( - delete Object.prototype._dd_temp_, - "object" !== typeof t && - (t = - "object" === typeof self - ? self - : "object" === typeof window - ? window - : {}), - t - ); - } - function U() { - return B(window.location); - } - function B(t) { - if (t.origin) return t.origin; - var e = t.host.replace(/(:80|:443)$/, ""); - return "".concat(t.protocol, "//").concat(e); - } - function V(t, e) { - var n = new RegExp("(?:^|;)\\s*".concat(e, "\\s*=\\s*([^;]+)")), - r = n.exec(t); - return r ? r[1] : void 0; - } - function H(t, e, n) { - void 0 === n && (n = ""); - var r = t.charCodeAt(e - 1), - i = r >= 55296 && r <= 56319, - o = i ? e + 1 : e; - return t.length <= o ? t : "".concat(t.slice(0, o)).concat(n); - } - function q(t, e, n, r) { - return W(t, [e], n, r); - } - function W(t, e, n, r) { - var i = void 0 === r ? {} : r, - o = i.once, - a = i.capture, - s = i.passive, - u = Tt( - o - ? function (t) { - f(), n(t); - } - : n - ), - c = s ? { capture: a, passive: s } : a; - e.forEach(function (e) { - return t.addEventListener(e, u, c); - }); - var f = function () { - return e.forEach(function (e) { - return t.removeEventListener(e, u, c); - }); - }; - return { stop: f }; - } - function G(t, e) { - if ( - document.readyState === t || - "complete" === document.readyState - ) - e(); - else { - var n = "complete" === t ? "load" : "DOMContentLoaded"; - q(window, n, e, { once: !0 }); - } - } - function Z(t) { - return null === t - ? "null" - : Array.isArray(t) - ? "array" - : typeof t; - } - function J() { - if ("undefined" !== typeof WeakSet) { - var t = new WeakSet(); - return { - hasAlreadyBeenSeen: function (e) { - var n = t.has(e); - return n || t.add(e), n; - }, - }; - } - var e = []; - return { - hasAlreadyBeenSeen: function (t) { - var n = e.indexOf(t) >= 0; - return n || e.push(t), n; - }, - }; - } - function K(t, e, n) { - if ((void 0 === n && (n = J()), void 0 === e)) return t; - if ("object" !== typeof e || null === e) return e; - if (e instanceof Date) return new Date(e.getTime()); - if (e instanceof RegExp) { - var r = - e.flags || - [ - e.global ? "g" : "", - e.ignoreCase ? "i" : "", - e.multiline ? "m" : "", - e.sticky ? "y" : "", - e.unicode ? "u" : "", - ].join(""); - return new RegExp(e.source, r); - } - if (!n.hasAlreadyBeenSeen(e)) { - if (Array.isArray(e)) { - for ( - var i = Array.isArray(t) ? t : [], o = 0; - o < e.length; - ++o - ) - i[o] = K(i[o], e[o], n); - return i; - } - var a = "object" === Z(t) ? t : {}; - for (var s in e) - Object.prototype.hasOwnProperty.call(e, s) && - (a[s] = K(a[s], e[s], n)); - return a; - } - } - function X(t) { - return K(void 0, t); - } - function Y() { - for (var t, e = [], n = 0; n < arguments.length; n++) - e[n] = arguments[n]; - for (var r = 0, i = e; r < i.length; r++) { - var o = i[r]; - void 0 !== o && null !== o && (t = K(t, o)); - } - return t; - } - function Q(t, e) { - if (window.requestIdleCallback) { - var n = window.requestIdleCallback(Tt(t), e); - return function () { - return window.cancelIdleCallback(n); - }; - } - var r = window.requestAnimationFrame(Tt(t)); - return function () { - return window.cancelAnimationFrame(r); - }; - } - var tt = { - AGENT: "agent", - CONSOLE: "console", - CUSTOM: "custom", - LOGGER: "logger", - NETWORK: "network", - SOURCE: "source", - REPORT: "report", - }; - function et(t, e, n, r) { - return t && (void 0 !== t.message || e instanceof Error) - ? { - message: t.message || "Empty message", - stack: nt(t), - handlingStack: r, - type: t.name, - } - : { - message: "".concat(n, " ").concat(A(e)), - stack: "No stack, consider using an instance of Error", - handlingStack: r, - type: t && t.name, - }; - } - function nt(t) { - var e = rt(t); - return ( - t.stack.forEach(function (t) { - var n = "?" === t.func ? "" : t.func, - r = - t.args && t.args.length > 0 - ? "(".concat(t.args.join(", "), ")") - : "", - i = t.line ? ":".concat(t.line) : "", - o = t.line && t.column ? ":".concat(t.column) : ""; - e += "\n at " - .concat(n) - .concat(r, " @ ") - .concat(t.url) - .concat(i) - .concat(o); - }), - e - ); - } - function rt(t) { - return "".concat(t.name || "Error", ": ").concat(t.message); - } - function it() { - var t, - e = 2, - n = new Error(); - if (!n.stack) - try { - throw n; - } catch (r) { - T(); - } - return ( - At(function () { - var r = o(n); - (r.stack = r.stack.slice(e)), (t = nt(r)); - }), - t - ); - } - var ot, - at, - st = (function () { - function t(t) { - (this.onFirstSubscribe = t), (this.observers = []); - } - return ( - (t.prototype.subscribe = function (t) { - var e = this; - return ( - !this.observers.length && - this.onFirstSubscribe && - (this.onLastUnsubscribe = - this.onFirstSubscribe() || void 0), - this.observers.push(t), - { - unsubscribe: function () { - (e.observers = e.observers.filter( - function (e) { - return t !== e; - } - )), - !e.observers.length && - e.onLastUnsubscribe && - e.onLastUnsubscribe(); - }, - } - ); - }), - (t.prototype.notify = function (t) { - this.observers.forEach(function (e) { - return e(t); - }); - }), - t - ); - })(); - function ut() { - for (var t = [], e = 0; e < arguments.length; e++) - t[e] = arguments[e]; - var n = new st(function () { - var e = t.map(function (t) { - return t.subscribe(function (t) { - return n.notify(t); - }); - }); - return function () { - return e.forEach(function (t) { - return t.unsubscribe(); - }); - }; - }); - return n; - } - function ct(t) { - return { relative: t, timeStamp: ft(t) }; - } - function ft(t) { - var e = Date.now() - performance.now(); - return e > wt() ? Math.round(e + t) : bt(t); - } - function lt() { - return Math.round(Date.now() - (wt() + performance.now())); - } - function dt(t) { - return P(t) ? O(1e6 * t, 0) : t; - } - function pt() { - return Date.now(); - } - function ht() { - return performance.now(); - } - function vt() { - return { relative: ht(), timeStamp: pt() }; - } - function yt() { - return { relative: 0, timeStamp: wt() }; - } - function mt(t, e) { - return e - t; - } - function gt(t) { - return t - wt(); - } - function bt(t) { - return Math.round(wt() + t); - } - function _t(t) { - return t < b; - } - function wt() { - return ( - void 0 === ot && (ot = performance.timing.navigationStart), - ot - ); - } - function xt(t) { - Array.isArray(t) && - (at || (at = new Set(t)), - t - .filter(function (t) { - return "string" === typeof t; - }) - .forEach(function (t) { - at.add(t); - })); - } - function kt(t) { - return !!at && at.has(t); - } - var St, - Et = { - maxMessagesPerPage: 0, - sentMessageCount: 0, - telemetryEnabled: !1, - }; - function Ot(t) { - var e, - n, - r = new st(), - i = new st(); - function o(t) { - return Y({ date: pt() }, void 0 !== e ? e() : {}, t); - } - function a(t) { - return Y( - { - type: "telemetry", - date: pt(), - service: "browser-sdk", - version: "4.8.1", - source: "browser", - _dd: { format_version: 2 }, - telemetry: t, - }, - void 0 !== n ? n() : {} - ); - } - return ( - (Et.telemetryEnabled = E(t.telemetrySampleRate)), - (St = function (t) { - r.notify(o(t)), - kt("telemetry") && - Et.telemetryEnabled && - i.notify(a(t)); - }), - x(Et, { - maxMessagesPerPage: - t.maxInternalMonitoringMessagesPerPage, - sentMessageCount: 0, - }), - { - setExternalContextProvider: function (t) { - e = t; - }, - monitoringMessageObservable: r, - setTelemetryContextProvider: function (t) { - n = t; - }, - telemetryEventObservable: i, - } - ); - } - function Tt(t) { - return function () { - return At(t, this, arguments); - }; - } - function At(t, e, n) { - try { - return t.apply(e, n); - } catch (r) { - Pt(r); - try { - jt(r); - } catch (r) { - Pt(r); - } - } - } - function Ct(t, e) { - Nt(t, e), It(x({ message: t, status: "debug" }, e)); - } - function jt(t) { - It(x({ status: "error" }, Mt(t))); - } - function It(t) { - St && - Et.sentMessageCount < Et.maxMessagesPerPage && - ((Et.sentMessageCount += 1), St(t)); - } - function Mt(t) { - if (t instanceof Error) { - var e = o(t); - return { - error: { kind: e.name, stack: nt(e) }, - message: e.message, - }; - } - return { - error: { stack: "Not an instance of error" }, - message: "Uncaught ".concat(A(t)), - }; - } - function Lt(t) { - Et.debugMode = t; - } - function Pt(t) { - Et.debugMode && r.error("[INTERNAL ERROR]", t); - } - function Nt(t, e) { - Et.debugMode && r.log("[MONITORING MESSAGE]", t, e); - } - function Rt(t, e) { - return function () { - for (var n = [], i = 0; i < arguments.length; i++) - n[i] = arguments[i]; - try { - return t.apply(void 0, n); - } catch (o) { - r.error(e, o); - } - }; - } - function $t(t) { - var e = x( - { - version: "4.8.1", - onReady: function (t) { - t(); - }, - }, - t - ); - return ( - Object.defineProperty(e, "_setDebug", { - get: function () { - return Lt; - }, - enumerable: !1, - }), - e - ); - } - function Dt(t, e, n) { - var r = t[e]; - (t[e] = n), - r && - r.q && - r.q.forEach(function (t) { - return Rt(t, "onReady callback threw an error:")(); - }); - } - function Ft() { - var t = {}; - return { - get: function () { - return t; - }, - add: function (e, n) { - t[e] = n; - }, - remove: function (e) { - delete t[e]; - }, - set: function (e) { - t = e; - }, - }; - } - var zt = 500, - Ut = (function () { - function t() { - this.buffer = []; - } - return ( - (t.prototype.add = function (t) { - var e = this.buffer.push(t); - e > zt && this.buffer.splice(0, 1); - }), - (t.prototype.drain = function () { - this.buffer.forEach(function (t) { - return t(); - }), - (this.buffer.length = 0); - }), - t - ); - })(); - function Bt() { - var t = Ht(); - if (t) - return { - getAllowedWebViewHosts: function () { - return JSON.parse(t.getAllowedWebViewHosts()); - }, - send: function (e, n) { - t.send(JSON.stringify({ eventType: e, event: n })); - }, - }; - } - function Vt(t) { - var e; - void 0 === t && - (t = - null === (e = z().location) || void 0 === e - ? void 0 - : e.hostname); - var n = Bt(); - return ( - !!n && - n.getAllowedWebViewHosts().some(function (e) { - var n = e.replace(/\./g, "\\."), - r = new RegExp("^(.+\\.)*".concat(n, "$")); - return r.test(t); - }) - ); - } - function Ht() { - return z().DatadogEventBridge; - } - var qt, - Wt, - Gt = v; - function Zt(t, e, n, r) { - var i = new Date(); - i.setTime(i.getTime() + n); - var o = "expires=".concat(i.toUTCString()), - a = r && r.crossSite ? "none" : "strict", - s = r && r.domain ? ";domain=".concat(r.domain) : "", - u = r && r.secure ? ";secure" : ""; - document.cookie = "" - .concat(t, "=") - .concat(e, ";") - .concat(o, ";path=/;samesite=") - .concat(a) - .concat(s) - .concat(u); - } - function Jt(t) { - return V(document.cookie, t); - } - function Kt(t, e) { - Zt(t, "", 0, e); - } - function Xt(t) { - if (void 0 === document.cookie || null === document.cookie) - return !1; - try { - var e = "dd_cookie_test_".concat(S()), - n = "test"; - Zt(e, n, v, t); - var i = Jt(e) === n; - return Kt(e, t), i; - } catch (o) { - return r.error(o), !1; - } - } - function Yt() { - if (void 0 === qt) { - var t = "dd_site_test_".concat(S()), - e = "test", - n = window.location.hostname.split("."), - r = n.pop(); - while (n.length && !Jt(t)) - (r = "".concat(n.pop(), ".").concat(r)), - Zt(t, e, v, { domain: r }); - Kt(t, { domain: r }), (qt = r); - } - return qt; - } - function Qt(t) { - return re(t, U()).href; - } - function te(t) { - try { - return !!re(t); - } catch (e) { - return !1; - } - } - function ee(t) { - return B(re(t)); - } - function ne(t) { - var e = re(t).pathname; - return "/" === e[0] ? e : "/".concat(e); - } - function re(t, e) { - if (ie()) return void 0 !== e ? new URL(t, e) : new URL(t); - if (void 0 === e && !/:/.test(t)) - throw new Error("Invalid URL: '".concat(t, "'")); - var n = document, - r = n.createElement("a"); - if (void 0 !== e) { - n = document.implementation.createHTMLDocument(""); - var i = n.createElement("base"); - (i.href = e), n.head.appendChild(i), n.body.appendChild(r); - } - return (r.href = t), r; - } - function ie() { - if (void 0 !== Wt) return Wt; - try { - var t = new URL("http://test/path"); - return (Wt = "http://test/path" === t.href), Wt; - } catch (e) { - Wt = !1; - } - return Wt; - } - var oe = { - logs: "logs", - rum: "rum", - sessionReplay: "session-replay", - }, - ae = { logs: "logs", rum: "rum", sessionReplay: "replay" }, - se = "datadoghq.com"; - function ue(t, e, n, r) { - var i = t.site, - o = void 0 === i ? se : i, - a = t.clientToken, - s = o.split("."), - u = s.pop(), - c = "" - .concat(oe[e], ".browser-intake-") - .concat(s.join("-"), ".") - .concat(u), - f = "https://".concat(c, "/api/v2/").concat(ae[e]), - l = t.proxyUrl && Qt(t.proxyUrl); - return { - build: function () { - var t = - "ddsource=".concat(r || "browser") + - "&ddtags=".concat( - encodeURIComponent( - ["sdk_version:".concat("4.8.1")] - .concat(n) - .join(",") - ) - ) + - "&dd-api-key=".concat(a) + - "&dd-evp-origin-version=".concat( - encodeURIComponent("4.8.1") - ) + - "&dd-evp-origin=browser" + - "&dd-request-id=".concat(S()); - "rum" === e && (t += "&batch_time=".concat(pt())); - var i = "".concat(f, "?").concat(t); - return l - ? "" - .concat(l, "?ddforward=") - .concat(encodeURIComponent(i)) - : i; - }, - buildIntakeUrl: function () { - return l ? "".concat(l, "?ddforward") : f; - }, - }; - } - var ce = 200; - function fe(t) { - var e = t.env, - n = t.service, - r = t.version, - i = t.datacenter, - o = []; - return ( - e && o.push(de("env", e)), - n && o.push(de("service", n)), - r && o.push(de("version", r)), - i && o.push(de("datacenter", i)), - o - ); - } - var le = /[^a-z0-9_:./-]/; - function de(t, e) { - var n = ce - t.length - 1; - (e.length > n || le.test(e)) && - r.warn( - "".concat( - t, - " value doesn't meet tag requirements and will be sanitized" - ) - ); - var i = e.replace(/,/g, "_"); - return "".concat(t, ":").concat(i); - } - function pe(t) { - var e = fe(t), - n = he(t, e), - r = N(n).map(function (t) { - return t.buildIntakeUrl(); - }), - i = ve(t, r, e); - return x( - { - isIntakeUrl: function (t) { - return r.some(function (e) { - return 0 === t.indexOf(e); - }); - }, - replica: i, - }, - n - ); - } - function he(t, e) { - var n = { - logsEndpointBuilder: ue(t, "logs", e), - rumEndpointBuilder: ue(t, "rum", e), - sessionReplayEndpointBuilder: ue(t, "sessionReplay", e), - }; - return t.internalMonitoringApiKey - ? x(n, { - internalMonitoringEndpointBuilder: ue( - x({}, t, { - clientToken: t.internalMonitoringApiKey, - }), - "logs", - e, - "browser-agent-internal-monitoring" - ), - }) - : n; - } - function ve(t, e, n) { - if (t.replica) { - var r = x({}, t, { - site: se, - clientToken: t.replica.clientToken, - }), - i = { - logsEndpointBuilder: ue(r, "logs", n), - rumEndpointBuilder: ue(r, "rum", n), - internalMonitoringEndpointBuilder: ue( - r, - "logs", - n, - "browser-agent-internal-monitoring" - ), - }; - return ( - e.push.apply( - e, - N(i).map(function (t) { - return t.buildIntakeUrl(); - }) - ), - x({ applicationId: t.replica.applicationId }, i) - ); - } - } - var ye = { - ALLOW: "allow", - MASK: "mask", - MASK_USER_INPUT: "mask-user-input", - }; - function me(t) { - var e, n; - if (t && t.clientToken) - if (void 0 === t.sampleRate || L(t.sampleRate)) { - if ( - void 0 === t.telemetrySampleRate || - L(t.telemetrySampleRate) - ) - return ( - xt(t.enableExperimentalFeatures), - x( - { - beforeSend: - t.beforeSend && - Rt( - t.beforeSend, - "beforeSend threw an error:" - ), - cookieOptions: ge(t), - sampleRate: - null !== (e = t.sampleRate) && - void 0 !== e - ? e - : 100, - telemetrySampleRate: - null !== - (n = t.telemetrySampleRate) && - void 0 !== n - ? n - : 20, - service: t.service, - silentMultipleInit: - !!t.silentMultipleInit, - batchBytesLimit: 16 * _, - eventRateLimiterThreshold: 3e3, - maxInternalMonitoringMessagesPerPage: 15, - flushTimeout: 30 * v, - maxBatchSize: 50, - maxMessageSize: 256 * _, - }, - pe(t) - ) - ); - r.error( - "Telemetry Sample Rate should be a number between 0 and 100" - ); - } else - r.error( - "Sample Rate should be a number between 0 and 100" - ); - else - r.error( - "Client Token is not configured, we will not send any data." - ); - } - function ge(t) { - var e = {}; - return ( - (e.secure = be(t)), - (e.crossSite = !!t.useCrossSiteSessionCookie), - t.trackSessionAcrossSubdomains && (e.domain = Yt()), - e - ); - } - function be(t) { - return ( - !!t.useSecureSessionCookie || !!t.useCrossSiteSessionCookie - ); - } - var _e = "datadog-synthetics-public-id", - we = "datadog-synthetics-result-id", - xe = "datadog-synthetics-injects-rum"; - function ke() { - var t = window._DATADOG_SYNTHETICS_PUBLIC_ID || Jt(_e), - e = window._DATADOG_SYNTHETICS_RESULT_ID || Jt(we); - if ("string" === typeof t && "string" === typeof e) - return { test_id: t, result_id: e, injected: Se() }; - } - function Se() { - return Boolean( - window._DATADOG_SYNTHETICS_INJECTS_RUM || Jt(xe) - ); - } - function Ee(t) { - var e, n; - if (t.applicationId) - if ( - void 0 === t.replaySampleRate || - L(t.replaySampleRate) - ) { - if (void 0 !== t.allowedTracingOrigins) { - if (!Array.isArray(t.allowedTracingOrigins)) - return void r.error( - "Allowed Tracing Origins should be an array" - ); - if ( - 0 !== t.allowedTracingOrigins.length && - void 0 === t.service - ) - return void r.error( - "Service need to be configured when tracing is enabled" - ); - } - var i = me(t); - if (i) - return x( - { - applicationId: t.applicationId, - version: t.version, - actionNameAttribute: t.actionNameAttribute, - replaySampleRate: - null !== (e = t.replaySampleRate) && - void 0 !== e - ? e - : 100, - allowedTracingOrigins: - null !== - (n = t.allowedTracingOrigins) && - void 0 !== n - ? n - : [], - trackInteractions: !!t.trackInteractions, - trackViewsManually: !!t.trackViewsManually, - defaultPrivacyLevel: R( - ye, - t.defaultPrivacyLevel - ) - ? t.defaultPrivacyLevel - : ye.MASK_USER_INPUT, - }, - i - ); - } else - r.error( - "Replay Sample Rate should be a number between 0 and 100" - ); - else - r.error( - "Application ID is not configured, no RUM data will be collected." - ); - } - function Oe(t, e, n) { - var i = void 0 === n ? {} : n, - o = i.ignoreInitIfSyntheticsWillInjectRum, - a = void 0 === o || o, - s = !1, - u = Ft(), - c = {}, - f = function () {}, - l = function () {}, - d = new Ut(), - p = function (t, e) { - void 0 === e && (e = pt()), - d.add(function () { - return p(t, e); - }); - }, - h = function (t, e) { - void 0 === e && (e = vt()), - d.add(function () { - return h(t, e); - }); - }, - v = function (t, e) { - void 0 === e && (e = m()), - d.add(function () { - return v(t, e); - }); - }, - y = function (t, e) { - void 0 === e && (e = m()), - d.add(function () { - return y(t, e); - }); - }; - function m() { - return X({ context: u.get(), user: c }); - } - function g(t) { - if (!a || !Se()) { - if (Vt()) t = O(t); - else if (!S(t)) return; - if (E(t)) { - var e = Ee(t); - if (e) { - if (e.trackViewsManually) { - var n = d; - (d = new Ut()), - (h = function (t) { - b(e, t); - }), - n.drain(); - } else b(e); - (l = function () { - return X(t); - }), - (s = !0); - } - } - } - } - function b(n, r) { - var i = t( - n, - function () { - return { - user: c, - context: u.get(), - hasReplay: !!e.isRecording() || void 0, - }; - }, - e, - r - ); - (h = i.startView), - (v = i.addAction), - (y = i.addError), - (p = i.addTiming), - (f = i.getInternalContext), - d.drain(), - e.onRumStart(i.lifeCycle, n, i.session, i.viewContexts); - } - var _ = Tt(function (t) { - var e = "object" === typeof t ? t : { name: t }; - h(e); - }), - w = $t({ - init: Tt(g), - addRumGlobalContext: Tt(u.add), - removeRumGlobalContext: Tt(u.remove), - getRumGlobalContext: Tt(u.get), - setRumGlobalContext: Tt(u.set), - getInternalContext: Tt(function (t) { - return f(t); - }), - getInitConfiguration: Tt(function () { - return l(); - }), - addAction: Tt(function (t, e) { - v({ - name: t, - context: X(e), - startClocks: vt(), - type: "custom", - }); - }), - addError: function (t, e) { - var n = it(); - At(function () { - y({ - error: t, - handlingStack: n, - context: X(e), - startClocks: vt(), - }); - }); - }, - addTiming: Tt(function (t, e) { - p(t, e); - }), - setUser: Tt(function (t) { - var e = k(t); - e ? (c = e) : r.error("Unsupported user:", t); - }), - removeUser: Tt(function () { - c = {}; - }), - startView: _, - startSessionReplayRecording: Tt(e.start), - stopSessionReplayRecording: Tt(e.stop), - }); - return w; - function k(t) { - if ("object" === typeof t && t) { - var e = X(t); - return ( - "id" in e && (e.id = String(e.id)), - "name" in e && (e.name = String(e.name)), - "email" in e && (e.email = String(e.email)), - e - ); - } - } - function S(t) { - return Xt(ge(t)) - ? !T() || - (r.error( - "Execution is not allowed in the current context." - ), - !1) - : (r.warn( - "Cookies are not authorized, we will not send any data." - ), - !1); - } - function E(t) { - return ( - !s || - (t.silentMultipleInit || - r.error("DD_RUM is already initialized."), - !1) - ); - } - function O(t) { - return x({}, t, { - applicationId: "00000000-aaaa-0000-aaaa-000000000000", - clientToken: "empty", - sampleRate: 100, - }); - } - function T() { - return "file:" === window.location.protocol; - } - } - var Te = /[^\u0000-\u007F]/, - Ae = (function () { - function t(t, e, n, r, i, o) { - void 0 === o && (o = T), - (this.request = t), - (this.maxSize = e), - (this.bytesLimit = n), - (this.maxMessageSize = r), - (this.flushTimeout = i), - (this.beforeUnloadCallback = o), - (this.pushOnlyBuffer = []), - (this.upsertBuffer = {}), - (this.bufferBytesSize = 0), - (this.bufferMessageCount = 0), - this.flushOnVisibilityHidden(), - this.flushPeriodically(); - } - return ( - (t.prototype.add = function (t) { - this.addOrUpdate(t); - }), - (t.prototype.upsert = function (t, e) { - this.addOrUpdate(t, e); - }), - (t.prototype.flush = function (t) { - if (0 !== this.bufferMessageCount) { - var e = this.pushOnlyBuffer.concat( - N(this.upsertBuffer) - ); - this.request.send( - e.join("\n"), - this.bufferBytesSize, - t - ), - (this.pushOnlyBuffer = []), - (this.upsertBuffer = {}), - (this.bufferBytesSize = 0), - (this.bufferMessageCount = 0); - } - }), - (t.prototype.sizeInBytes = function (t) { - return Te.test(t) - ? void 0 !== window.TextEncoder - ? new TextEncoder().encode(t).length - : new Blob([t]).size - : t.length; - }), - (t.prototype.addOrUpdate = function (t, e) { - var n = this.process(t), - i = n.processedMessage, - o = n.messageBytesSize; - o >= this.maxMessageSize - ? r.warn( - "Discarded a message whose size was bigger than the maximum allowed size ".concat( - this.maxMessageSize, - "KB." - ) - ) - : (this.hasMessageFor(e) && this.remove(e), - this.willReachedBytesLimitWith(o) && - this.flush("willReachedBytesLimitWith"), - this.push(i, o, e), - this.isFull() && this.flush("isFull")); - }), - (t.prototype.process = function (t) { - var e = A(t), - n = this.sizeInBytes(e); - return { processedMessage: e, messageBytesSize: n }; - }), - (t.prototype.push = function (t, e, n) { - this.bufferMessageCount > 0 && - (this.bufferBytesSize += 1), - void 0 !== n - ? (this.upsertBuffer[n] = t) - : this.pushOnlyBuffer.push(t), - (this.bufferBytesSize += e), - (this.bufferMessageCount += 1); - }), - (t.prototype.remove = function (t) { - var e = this.upsertBuffer[t]; - delete this.upsertBuffer[t]; - var n = this.sizeInBytes(e); - (this.bufferBytesSize -= n), - (this.bufferMessageCount -= 1), - this.bufferMessageCount > 0 && - (this.bufferBytesSize -= 1); - }), - (t.prototype.hasMessageFor = function (t) { - return ( - void 0 !== t && void 0 !== this.upsertBuffer[t] - ); - }), - (t.prototype.willReachedBytesLimitWith = function (t) { - return ( - this.bufferBytesSize + t + 1 >= this.bytesLimit - ); - }), - (t.prototype.isFull = function () { - return ( - this.bufferMessageCount === this.maxSize || - this.bufferBytesSize >= this.bytesLimit - ); - }), - (t.prototype.flushPeriodically = function () { - var t = this; - setTimeout( - Tt(function () { - t.flush("flushPeriodically"), - t.flushPeriodically(); - }), - this.flushTimeout - ); - }), - (t.prototype.flushOnVisibilityHidden = function () { - var t = this; - navigator.sendBeacon && - (q( - window, - "beforeunload", - this.beforeUnloadCallback - ), - q(document, "visibilitychange", function () { - "hidden" === document.visibilityState && - t.flush("visibilitychange"); - }), - q(window, "beforeunload", function () { - return t.flush("beforeunload"); - })); - }), - t - ); - })(), - Ce = !1, - je = (function () { - function t(t, e) { - (this.endpointBuilder = t), (this.bytesLimit = e); - } - return ( - (t.prototype.send = function (t, e, n) { - var r = this.endpointBuilder.build(), - i = - !!navigator.sendBeacon && - e < this.bytesLimit; - if (i) - try { - var o = navigator.sendBeacon(r, t); - if (o) return; - } catch (u) { - Me(u); - } - var a = function (t) { - var o = - null === t || void 0 === t - ? void 0 - : t.currentTarget; - (o.status >= 200 && o.status < 300) || - Ce || - ((Ce = !0), - Ct("XHR fallback failed", { - on_line: navigator.onLine, - size: e, - url: r, - try_beacon: i, - flush_reason: n, - event: { - is_trusted: t.isTrusted, - total: t.total, - loaded: t.loaded, - }, - request: { - status: o.status, - ready_state: o.readyState, - response_text: - o.responseText.slice( - 0, - 512 - ), - }, - })); - }, - s = new XMLHttpRequest(); - s.addEventListener( - "loadend", - Tt(function (t) { - return a(t); - }) - ), - s.open("POST", r, !0), - s.send(t); - }), - t - ); - })(), - Ie = !1; - function Me(t) { - Ie || ((Ie = !0), jt(t)); - } - function Le(t, e, n) { - var r, - i = o(e); - function o(e) { - return new Ae( - new je(e, t.batchBytesLimit), - t.maxBatchSize, - t.batchBytesLimit, - t.maxMessageSize, - t.flushTimeout - ); - } - return ( - n && (r = o(n)), - { - add: function (t) { - i.add(t), r && r.add(t); - }, - } - ); - } - function Pe() { - var t = Ne(), - e = new st(function () { - if (t) { - var n = new t( - Tt(function () { - return e.notify(); - }) - ); - return ( - n.observe(document, { - attributes: !0, - characterData: !0, - childList: !0, - subtree: !0, - }), - function () { - return n.disconnect(); - } - ); - } - }); - return e; - } - function Ne() { - var t, - e = window; - if (e.Zone) { - var n = e.Zone.__symbol__("MutationObserver"); - t = e[n]; - } - return t || (t = e.MutationObserver), t; - } - var Re = "initial_document", - $e = [ - [ - "document", - function (t) { - return Re === t; - }, - ], - [ - "xhr", - function (t) { - return "xmlhttprequest" === t; - }, - ], - [ - "fetch", - function (t) { - return "fetch" === t; - }, - ], - [ - "beacon", - function (t) { - return "beacon" === t; - }, - ], - [ - "css", - function (t, e) { - return /\.css$/i.test(e); - }, - ], - [ - "js", - function (t, e) { - return /\.js$/i.test(e); - }, - ], - [ - "image", - function (t, e) { - return ( - j(["image", "img", "icon"], t) || - null !== - /\.(gif|jpg|jpeg|tiff|png|svg|ico)$/i.exec( - e - ) - ); - }, - ], - [ - "font", - function (t, e) { - return null !== /\.(woff|eot|woff2|ttf)$/i.exec(e); - }, - ], - [ - "media", - function (t, e) { - return ( - j(["audio", "video"], t) || - null !== /\.(mp3|mp4)$/i.exec(e) - ); - }, - ], - ]; - function De(t) { - var e = t.name; - if (!te(e)) - return ( - Ct('Failed to construct URL for "'.concat(t.name, '"')), - "other" - ); - for (var n = ne(e), r = 0, i = $e; r < i.length; r++) { - var o = i[r], - a = o[0], - s = o[1]; - if (s(t.initiatorType, n)) return a; - } - return "other"; - } - function Fe() { - for (var t = [], e = 0; e < arguments.length; e++) - t[e] = arguments[e]; - for (var n = 1; n < t.length; n += 1) - if (t[n - 1] > t[n]) return !1; - return !0; - } - function ze(t) { - return ( - "xmlhttprequest" === t.initiatorType || - "fetch" === t.initiatorType - ); - } - function Ue(t) { - var e = t.duration, - n = t.startTime, - r = t.responseEnd; - return dt(0 === e && n < r ? mt(n, r) : e); - } - function Be(t) { - var e = Ve(t); - if (e) { - var n = e.startTime, - r = e.fetchStart, - i = e.redirectStart, - o = e.redirectEnd, - a = e.domainLookupStart, - s = e.domainLookupEnd, - u = e.connectStart, - c = e.secureConnectionStart, - f = e.connectEnd, - l = e.requestStart, - d = e.responseStart, - p = e.responseEnd, - h = { download: qe(n, d, p), first_byte: qe(n, l, d) }; - return ( - f !== r && - ((h.connect = qe(n, u, f)), - Fe(u, c, f) && (h.ssl = qe(n, c, f))), - s !== r && (h.dns = qe(n, a, s)), - He(t) && (h.redirect = qe(n, i, o)), - h - ); - } - } - function Ve(t) { - if ( - Fe( - t.startTime, - t.fetchStart, - t.domainLookupStart, - t.domainLookupEnd, - t.connectStart, - t.connectEnd, - t.requestStart, - t.responseStart, - t.responseEnd - ) - ) { - if (!He(t)) return t; - var e = t.redirectStart, - n = t.redirectEnd; - if ( - (e < t.startTime && (e = t.startTime), - n < t.startTime && (n = t.fetchStart), - Fe(t.startTime, e, n, t.fetchStart)) - ) - return x({}, t, { redirectEnd: n, redirectStart: e }); - } - } - function He(t) { - return t.fetchStart !== t.startTime; - } - function qe(t, e, n) { - return { duration: dt(mt(e, n)), start: dt(mt(t, e)) }; - } - function We(t) { - if (t.startTime < t.responseStart) return t.decodedBodySize; - } - function Ge(t, e) { - return e && !t.isIntakeUrl(e); - } - var Ze = 2 * y; - function Je(t) { - var e = Ke(t) || Xe(t); - if (e && !(e.traceTime <= Date.now() - Ze)) return e.traceId; - } - function Ke(t) { - var e = t.querySelector("meta[name=dd-trace-id]"), - n = t.querySelector("meta[name=dd-trace-time]"); - return Ye(e && e.content, n && n.content); - } - function Xe(t) { - var e = Qe(t); - if (e) return Ye(V(e, "trace-id"), V(e, "trace-time")); - } - function Ye(t, e) { - var n = e && Number(e); - if (t && n) return { traceId: t, traceTime: n }; - } - function Qe(t) { - for (var e = 0; e < t.childNodes.length; e += 1) { - var n = tn(t.childNodes[e]); - if (n) return n; - } - if (t.body) - for (e = t.body.childNodes.length - 1; e >= 0; e -= 1) { - var r = t.body.childNodes[e]; - n = tn(r); - if (n) return n; - if (!nn(r)) break; - } - } - function tn(t) { - if (t && en(t)) { - var e = /^\s*DATADOG;(.*?)\s*$/.exec(t.data); - if (e) return e[1]; - } - } - function en(t) { - return "#comment" === t.nodeName; - } - function nn(t) { - return "#text" === t.nodeName; - } - function rn() { - return ( - void 0 !== window.performance && "getEntries" in performance - ); - } - function on(t) { - return ( - window.PerformanceObserver && - void 0 !== PerformanceObserver.supportedEntryTypes && - PerformanceObserver.supportedEntryTypes.includes(t) - ); - } - function an() { - return "function" === typeof PerformanceEntry; - } - function sn(t, e) { - if ( - (un(function (n) { - dn(t, e, [n]); - }), - rn()) - ) { - var n = performance.getEntries(); - setTimeout( - Tt(function () { - return dn(t, e, n); - }) - ); - } - if (window.PerformanceObserver) { - var r = Tt(function (n) { - return dn(t, e, n.getEntries()); - }), - i = ["resource", "navigation", "longtask", "paint"], - o = [ - "largest-contentful-paint", - "first-input", - "layout-shift", - ]; - try { - o.forEach(function (t) { - var e = new PerformanceObserver(r); - e.observe({ type: t, buffered: !0 }); - }); - } catch (s) { - i.push.apply(i, o); - } - var a = new PerformanceObserver(r); - a.observe({ entryTypes: i }), - rn() && - "addEventListener" in performance && - performance.addEventListener( - "resourcetimingbufferfull", - function () { - performance.clearResourceTimings(); - } - ); - } - on("navigation") || - cn(function (n) { - dn(t, e, [n]); - }), - on("first-input") || - fn(function (n) { - dn(t, e, [n]); - }); - } - function un(t) { - G("interactive", function () { - var e, - n = { - entryType: "resource", - initiatorType: Re, - traceId: Je(document), - }; - if ( - on("navigation") && - performance.getEntriesByType("navigation").length > 0 - ) { - var r = performance.getEntriesByType("navigation")[0]; - e = x(r.toJSON(), n); - } else { - var i = ln(); - e = x( - i, - { - decodedBodySize: 0, - duration: i.responseEnd, - name: window.location.href, - startTime: 0, - }, - n - ); - } - t(e); - }); - } - function cn(t) { - function e() { - t(x(ln(), { entryType: "navigation" })); - } - G("complete", function () { - setTimeout(Tt(e)); - }); - } - function fn(t) { - var e = Date.now(), - n = !1, - r = W( - window, - [ - "click", - "mousedown", - "keydown", - "touchstart", - "pointerdown", - ], - function (t) { - if (t.cancelable) { - var e = { - entryType: "first-input", - processingStart: ht(), - startTime: t.timeStamp, - }; - "pointerdown" === t.type ? i(e) : o(e); - } - }, - { passive: !0, capture: !0 } - ).stop; - function i(t) { - W( - window, - ["pointerup", "pointercancel"], - function (e) { - "pointerup" === e.type && o(t); - }, - { once: !0 } - ); - } - function o(i) { - if (!n) { - (n = !0), r(); - var o = i.processingStart - i.startTime; - o >= 0 && o < Date.now() - e && t(i); - } - } - } - function ln() { - var t = {}, - e = performance.timing; - for (var n in e) - if (P(e[n])) { - var r = n, - i = e[r]; - t[r] = 0 === i ? 0 : gt(i); - } - return t; - } - function dn(t, e, n) { - var r = n.filter(function (t) { - return ( - "resource" === t.entryType || - "navigation" === t.entryType || - "paint" === t.entryType || - "longtask" === t.entryType || - "largest-contentful-paint" === t.entryType || - "first-input" === t.entryType || - "layout-shift" === t.entryType - ); - }), - i = r.filter(function (t) { - return !pn(t) && !hn(e, t); - }); - i.length && t.notify(0, i); - } - function pn(t) { - return "navigation" === t.entryType && t.loadEventEnd <= 0; - } - function hn(t, e) { - return "resource" === e.entryType && !Ge(t, e.name); - } - function vn(t, e, n) { - var r = 0, - i = !1; - return { - isLimitReached: function () { - if ( - (0 === r && - setTimeout(function () { - r = 0; - }, y), - (r += 1), - r <= e || i) - ) - return (i = !1), !1; - if (r === e + 1) { - i = !0; - try { - n({ - message: "Reached max number of " - .concat(t, "s by minute: ") - .concat(e), - source: tt.AGENT, - startClocks: vt(), - }); - } finally { - i = !1; - } - } - return !0; - }, - }; - } - function yn(t, e, n) { - var r = X(t), - i = n(r); - return ( - e.forEach(function (e) { - var n = mn(t, e), - i = mn(r, e), - o = Z(n), - a = Z(i); - a === o - ? gn(t, e, i) - : "object" !== o || - ("undefined" !== a && "null" !== a) || - gn(t, e, {}); - }), - i - ); - } - function mn(t, e) { - for (var n = t, r = 0, i = e.split("."); r < i.length; r++) { - var o = i[r]; - if (!bn(n, o)) return; - n = n[o]; - } - return n; - } - function gn(t, e, n) { - for (var r = t, i = e.split("."), o = 0; o < i.length; o += 1) { - var a = i[o]; - if (!bn(r, a)) return; - o !== i.length - 1 ? (r = r[a]) : (r[a] = n); - } - } - function bn(t, e) { - return "object" === typeof t && null !== t && e in t; - } - function _n() { - var t, - e = - null === (t = window.Cypress) || void 0 === t - ? void 0 - : t.env("traceId"); - if ("string" === typeof e) return { test_execution_id: e }; - } - var wn = [ - "view.url", - "view.referrer", - "action.target.name", - "error.message", - "error.stack", - "error.resource.url", - "resource.url", - ], - xn = wn.concat(["context"]); - function kn(t, e, n, r, i, o, a) { - var s, - u = function (t) { - e.notify(12, { error: t }); - }, - c = - ((s = {}), - (s["error"] = vn( - "error", - t.eventRateLimiterThreshold, - u - )), - (s["action"] = vn( - "action", - t.eventRateLimiterThreshold, - u - )), - s), - f = ke(), - l = _n(); - e.subscribe(10, function (s) { - var u = s.startTime, - d = s.rawRumEvent, - p = s.domainContext, - h = s.savedCommonContext, - v = s.customerContext, - y = r.findView(u), - m = i.findUrl(u), - g = n.findTrackedSession( - "view" !== d.type ? u : void 0 - ); - if (g && y && m) { - var b = h || a(), - _ = { - _dd: { - format_version: 2, - drift: lt(), - session: { plan: g.hasReplayPlan ? 2 : 1 }, - browser_sdk_version: Vt() - ? "4.8.1" - : void 0, - }, - application: { id: t.applicationId }, - date: pt(), - service: t.service, - source: "browser", - session: { - id: g.id, - type: f - ? "synthetics" - : l - ? "ci_test" - : "user", - }, - synthetics: f, - ci_test: l, - }, - w = o.findActionId(u); - kt("sub-apps") - ? (_.version = t.version) - : (delete y.service, delete y.version); - var x = - En(d) && w - ? Y(_, m, y, { action: { id: w } }, d) - : Y(_, m, y, d); - (x.context = Y(b.context, v)), - "has_replay" in x.session || - (x.session.has_replay = b.hasReplay), - D(b.user) || (x.usr = b.user), - Sn(x, t.beforeSend, p, c) && - (D(x.context) && delete x.context, - e.notify(11, x)); - } - }); - } - function Sn(t, e, n, i) { - var o; - if (e) { - var a = yn(t, "view" === t.type ? wn : xn, function (t) { - return e(t, n); - }); - if (!1 === a && "view" !== t.type) return !1; - !1 === a && - r.warn("Can't dismiss view events using beforeSend!"); - } - var s = - null === (o = i[t.type]) || void 0 === o - ? void 0 - : o.isLimitReached(); - return !s; - } - function En(t) { - return ( - -1 !== ["error", "resource", "long_task"].indexOf(t.type) - ); - } - var On = 500, - Tn = 2500, - An = []; - function Cn() { - document.hasFocus() && jn(); - var t = Mn(jn).stop, - e = Ln(In).stop; - return { - isInForegroundAt: Pn, - selectInForegroundPeriodsFor: Nn, - stop: function () { - (An = []), t(), e(); - }, - }; - } - function jn() { - if (!(An.length > Tn)) { - var t = An[An.length - 1], - e = ht(); - (void 0 !== t && void 0 === t.end) || An.push({ start: e }); - } - } - function In() { - if (0 !== An.length) { - var t = An[An.length - 1], - e = ht(); - void 0 === t.end && (t.end = e); - } - } - function Mn(t) { - return q(window, "focus", function (e) { - e.isTrusted && t(); - }); - } - function Ln(t) { - return q(window, "blur", function (e) { - e.isTrusted && t(); - }); - } - function Pn(t) { - for (var e = An.length - 1; e >= 0; e--) { - var n = An[e]; - if (void 0 !== n.end && t > n.end) break; - if (t > n.start && (void 0 === n.end || t < n.end)) - return !0; - } - return !1; - } - function Nn(t, e) { - for ( - var n = t + e, - r = [], - i = Math.max(0, An.length - On), - o = An.length - 1; - o >= i; - o-- - ) { - var a = An[o]; - if (void 0 !== a.end && t > a.end) break; - if (!(n < a.start)) { - var s = t > a.start ? t : a.start, - u = mt(t, s), - c = void 0 === a.end || n < a.end ? n : a.end, - f = mt(s, c); - r.unshift({ start: dt(u), duration: dt(f) }); - } - } - return r; - } - function Rn(t, e, n, r, i) { - return { - get: function (o) { - var a = n.findView(o), - s = i.findUrl(o), - u = e.findTrackedSession(o); - if (u && a && s) { - var c = r.findActionId(o); - return { - application_id: t, - session_id: u.id, - user_action: c ? { id: c } : void 0, - view: x({}, a.view, s.view), - }; - } - }, - }; - } - var $n = (function () { - function t() { - this.callbacks = {}; - } - return ( - (t.prototype.notify = function (t, e) { - var n = this.callbacks[t]; - n && - n.forEach(function (t) { - return t(e); - }); - }), - (t.prototype.subscribe = function (t, e) { - var n = this; - return ( - this.callbacks[t] || (this.callbacks[t] = []), - this.callbacks[t].push(e), - { - unsubscribe: function () { - n.callbacks[t] = n.callbacks[t].filter( - function (t) { - return e !== t; - } - ); - }, - } - ); - }), - t - ); - })(); - function Dn() { - return Boolean(document.documentMode); - } - function Fn() { - return ( - !!window.chrome || - /HeadlessChrome/.test(window.navigator.userAgent) - ); - } - var zn, - Un = /^([a-z]+)=([a-z0-9-]+)$/, - Bn = "&", - Vn = "_dd_s", - Hn = 10, - qn = 100, - Wn = []; - function Gn(t, e) { - var n; - if ((void 0 === e && (e = 0), zn || (zn = t), t === zn)) - if (e >= qn) Kn(); - else { - var r, - i = tr(); - if (Zn()) { - if (i.lock) return void Jn(t, e); - if ( - ((r = S()), - (i.lock = r), - Yn(i, t.options), - (i = tr()), - i.lock !== r) - ) - return void Jn(t, e); - } - var o = t.process(i); - if (Zn() && ((i = tr()), i.lock !== r)) Jn(t, e); - else { - if ( - (o && Xn(o, t.options), Zn() && (!o || !nr(o))) - ) { - if (((i = tr()), i.lock !== r)) - return void Jn(t, e); - delete i.lock, Yn(i, t.options), (o = i); - } - null === (n = t.after) || - void 0 === n || - n.call(t, o || i), - Kn(); - } - } - else Wn.push(t); - } - function Zn() { - return Fn(); - } - function Jn(t, e) { - setTimeout( - Tt(function () { - Gn(t, e + 1); - }), - Hn - ); - } - function Kn() { - zn = void 0; - var t = Wn.shift(); - t && Gn(t); - } - function Xn(t, e) { - nr(t) - ? rr(e) - : ((t.expire = String(Date.now() + ir)), Yn(t, e)); - } - function Yn(t, e) { - Zt(Vn, Qn(t), ir, e); - } - function Qn(t) { - return $(t) - .map(function (t) { - var e = t[0], - n = t[1]; - return "".concat(e, "=").concat(n); - }) - .join(Bn); - } - function tr() { - var t = Jt(Vn), - e = {}; - return ( - er(t) && - t.split(Bn).forEach(function (t) { - var n = Un.exec(t); - if (null !== n) { - var r = n[1], - i = n[2]; - e[r] = i; - } - }), - e - ); - } - function er(t) { - return void 0 !== t && (-1 !== t.indexOf(Bn) || Un.test(t)); - } - function nr(t) { - return D(t); - } - function rr(t) { - Zt(Vn, "", 0, t); - } - var ir = 15 * y, - or = 4 * m; - function ar(t, e, n) { - var r = new st(), - i = new st(), - o = setInterval(Tt(c), Gt), - a = y(); - function s() { - var e; - Gn({ - options: t, - process: function (t) { - var n = f(t); - return (e = l(n)), n; - }, - after: function (t) { - e && !d() && v(t), (a = t); - }, - }); - } - function u() { - Gn({ - options: t, - process: function (t) { - return d() ? f(t) : void 0; - }, - }); - } - function c() { - Gn({ - options: t, - process: function (t) { - return m(t) ? void 0 : {}; - }, - after: f, - }); - } - function f(t) { - return m(t) || (t = {}), d() && (p(t) ? h() : (a = t)), t; - } - function l(t) { - var r = n(t[e]), - i = r.trackingType, - o = r.isTracked; - return ( - (t[e] = i), - o && - !t.id && - ((t.id = S()), (t.created = String(Date.now()))), - o - ); - } - function d() { - return void 0 !== a[e]; - } - function p(t) { - return a.id !== t.id || a[e] !== t[e]; - } - function h() { - (a = {}), i.notify(); - } - function v(t) { - (a = t), r.notify(); - } - function y() { - var t = tr(); - return m(t) ? t : {}; - } - function m(t) { - return ( - (void 0 === t.created || - Date.now() - Number(t.created) < or) && - (void 0 === t.expire || Date.now() < Number(t.expire)) - ); - } - return { - expandOrRenewSession: w(Tt(s), Gt).throttled, - expandSession: u, - getSession: function () { - return a; - }, - renewObservable: r, - expireObservable: i, - stop: function () { - clearInterval(o); - }, - }; - } - var sr, - ur = 1 / 0, - cr = y, - fr = (function () { - function t(t) { - var e = this; - (this.expireDelay = t), - (this.entries = []), - (this.clearOldContextsInterval = setInterval( - function () { - return e.clearOldContexts(); - }, - cr - )); - } - return ( - (t.prototype.add = function (t, e) { - var n = this, - r = { - context: t, - startTime: e, - endTime: ur, - remove: function () { - var t = n.entries.indexOf(r); - t >= 0 && n.entries.splice(t, 1); - }, - close: function (t) { - r.endTime = t; - }, - }; - return this.entries.unshift(r), r; - }), - (t.prototype.find = function (t) { - void 0 === t && (t = ur); - for ( - var e = 0, n = this.entries; - e < n.length; - e++ - ) { - var r = n[e]; - if (r.startTime <= t) { - if (t <= r.endTime) return r.context; - break; - } - } - }), - (t.prototype.closeActive = function (t) { - var e = this.entries[0]; - e && e.endTime === ur && e.close(t); - }), - (t.prototype.findAll = function (t) { - return ( - void 0 === t && (t = ur), - this.entries - .filter(function (e) { - return ( - e.startTime <= t && t <= e.endTime - ); - }) - .map(function (t) { - return t.context; - }) - ); - }), - (t.prototype.reset = function () { - this.entries = []; - }), - (t.prototype.stop = function () { - clearInterval(this.clearOldContextsInterval); - }), - (t.prototype.clearOldContexts = function () { - var t = ht() - this.expireDelay; - while ( - this.entries.length > 0 && - this.entries[this.entries.length - 1].endTime < - t - ) - this.entries.pop(); - }), - t - ); - })(), - lr = or; - function dr(t) { - var e = new fr(lr); - function n(t) { - return { - service: t.service, - version: t.version, - view: { id: t.id, name: t.name }, - }; - } - return ( - t.subscribe(2, function (t) { - e.add(n(t), t.startClocks.relative); - }), - t.subscribe(4, function (t) { - var n = t.endClocks; - e.closeActive(n.relative); - }), - t.subscribe(8, function () { - e.reset(); - }), - { - findView: function (t) { - return e.find(t); - }, - stop: function () { - e.stop(); - }, - } - ); - } - function pr(t, e, n) { - var r = t[e], - i = n(r), - o = function () { - return i.apply(this, arguments); - }; - return ( - (t[e] = o), - { - stop: function () { - t[e] === o ? (t[e] = r) : (i = r); - }, - } - ); - } - function hr(t, e, n) { - var r = n.before, - i = n.after; - return pr(t, e, function (t) { - return function () { - var e, - n = arguments; - return ( - r && At(r, this, n), - "function" === typeof t && (e = t.apply(this, n)), - i && At(i, this, n), - e - ); - }; - }); - } - function vr(t, e, n) { - var r = Object.getOwnPropertyDescriptor(t, e); - if (!r || !r.set || !r.configurable) return { stop: T }; - var i = function (t, e) { - setTimeout( - Tt(function () { - n(t, e); - }), - 0 - ); - }, - o = function (t) { - r.set.call(this, t), i(this, t); - }; - return ( - Object.defineProperty(t, e, { set: o }), - { - stop: function () { - var n; - (null === - (n = Object.getOwnPropertyDescriptor(t, e)) || - void 0 === n - ? void 0 - : n.set) === o - ? Object.defineProperty(t, e, r) - : (i = T); - }, - } - ); - } - var yr, - mr = new WeakMap(); - function gr() { - return sr || (sr = br()), sr; - } - function br() { - var t = new st(function () { - var e = hr(XMLHttpRequest.prototype, "open", { - before: _r, - }).stop, - n = hr(XMLHttpRequest.prototype, "send", { - before: function () { - wr.call(this, t); - }, - }).stop, - r = hr(XMLHttpRequest.prototype, "abort", { - before: xr, - }).stop; - return function () { - e(), n(), r(); - }; - }); - return t; - } - function _r(t, e) { - mr.set(this, { - state: "open", - method: t, - url: Qt(e.toString()), - }); - } - function wr(t) { - var e = this, - n = mr.get(this); - if (n) { - var r = n; - (r.state = "start"), - (r.startTime = ht()), - (r.startClocks = vt()), - (r.isAborted = !1), - (r.xhr = this); - var i = !1, - o = hr(this, "onreadystatechange", { - before: function () { - this.readyState === XMLHttpRequest.DONE && a(); - }, - }).stop, - a = Tt(function () { - if ( - (e.removeEventListener("loadend", a), o(), !i) - ) { - i = !0; - var s = n; - (s.state = "complete"), - (s.duration = mt( - r.startClocks.timeStamp, - pt() - )), - (s.status = e.status), - t.notify(k(s)); - } - }); - this.addEventListener("loadend", a), t.notify(r); - } - } - function xr() { - var t = mr.get(this); - t && (t.isAborted = !0); - } - function kr() { - return yr || (yr = Sr()), yr; - } - function Sr() { - var t = new st(function () { - if (window.fetch) { - var e = pr(window, "fetch", function (e) { - return function (n, r) { - var i, - o = At(Er, null, [t, n, r]); - return ( - o - ? ((i = e.call(this, o.input, o.init)), - At(Or, null, [t, i, o])) - : (i = e.call(this, n, r)), - i - ); - }; - }).stop; - return e; - } - }); - return t; - } - function Er(t, e, n) { - var r = - (n && n.method) || - ("object" === typeof e && e.method) || - "GET", - i = Qt(("object" === typeof e && e.url) || e), - o = vt(), - a = { - state: "start", - init: n, - input: e, - method: r, - startClocks: o, - url: i, - }; - return t.notify(a), a; - } - function Or(t, e, n) { - var r = function (e) { - var r = n; - (r.state = "complete"), - (r.duration = mt(r.startClocks.timeStamp, pt())), - "stack" in e || e instanceof Error - ? ((r.status = 0), - (r.isAborted = - e instanceof DOMException && - e.code === DOMException.ABORT_ERR), - (r.error = e), - t.notify(r)) - : "status" in e && - ((r.response = e), - (r.responseType = e.type), - (r.status = e.status), - (r.isAborted = !1), - t.notify(r)); - }; - e.then(Tt(r), Tt(r)); - } - function Tr(t) { - 0 !== t.status || - t.isAborted || - ((t.traceId = void 0), (t.spanId = void 0)); - } - function Ar(t, e) { - return { - clearTracingIfNeeded: Tr, - traceFetch: function (n) { - return Cr(t, n, e, function (t) { - var e; - if ( - n.input instanceof Request && - !(null === (e = n.init) || void 0 === e - ? void 0 - : e.headers) - ) - (n.input = new Request(n.input)), - Object.keys(t).forEach(function (e) { - n.input.headers.append(e, t[e]); - }); - else { - n.init = k(n.init); - var r = []; - n.init.headers instanceof Headers - ? n.init.headers.forEach(function (t, e) { - r.push([e, t]); - }) - : Array.isArray(n.init.headers) - ? n.init.headers.forEach(function (t) { - r.push(t); - }) - : n.init.headers && - Object.keys(n.init.headers).forEach( - function (t) { - r.push([t, n.init.headers[t]]); - } - ), - (n.init.headers = r.concat($(t))); - } - }); - }, - traceXhr: function (n, r) { - return Cr(t, n, e, function (t) { - Object.keys(t).forEach(function (e) { - r.setRequestHeader(e, t[e]); - }); - }); - }, - }; - } - function Cr(t, e, n, r) { - Ir() && - jr(t, e.url) && - n.findTrackedSession() && - ((e.traceId = new Pr()), - (e.spanId = new Pr()), - r(Lr(e.traceId, e.spanId))); - } - function jr(t, e) { - for ( - var n = ee(e), r = 0, i = t.allowedTracingOrigins; - r < i.length; - r++ - ) { - var o = i[r]; - if (n === o || (o instanceof RegExp && o.test(n))) - return !0; - } - return !1; - } - function Ir() { - return void 0 !== Mr(); - } - function Mr() { - return window.crypto || window.msCrypto; - } - function Lr(t, e) { - return { - "x-datadog-origin": "rum", - "x-datadog-parent-id": e.toDecimalString(), - "x-datadog-sampled": "1", - "x-datadog-sampling-priority": "1", - "x-datadog-trace-id": t.toDecimalString(), - }; - } - var Pr = (function () { - function t() { - (this.buffer = new Uint8Array(8)), - Mr().getRandomValues(this.buffer), - (this.buffer[0] = 127 & this.buffer[0]); - } - return ( - (t.prototype.toString = function (t) { - var e = this.readInt32(0), - n = this.readInt32(4), - r = ""; - do { - var i = (e % t) * 4294967296 + n; - (e = Math.floor(e / t)), - (n = Math.floor(i / t)), - (r = (i % t).toString(t) + r); - } while (e || n); - return r; - }), - (t.prototype.toDecimalString = function () { - return this.toString(10); - }), - (t.prototype.readInt32 = function (t) { - return ( - 16777216 * this.buffer[t] + - (this.buffer[t + 1] << 16) + - (this.buffer[t + 2] << 8) + - this.buffer[t + 3] - ); - }), - t - ); - })(), - Nr = 1; - function Rr(t, e, n) { - var r = Ar(e, n); - $r(t, e, r), Dr(t, e, r); - } - function $r(t, e, n) { - var r = gr().subscribe(function (r) { - var i = r; - if (Ge(e, i.url)) - switch (i.state) { - case "start": - n.traceXhr(i, i.xhr), - (i.requestIndex = Fr()), - t.notify(5, { - requestIndex: i.requestIndex, - }); - break; - case "complete": - n.clearTracingIfNeeded(i), - t.notify(6, { - duration: i.duration, - method: i.method, - requestIndex: i.requestIndex, - spanId: i.spanId, - startClocks: i.startClocks, - status: i.status, - traceId: i.traceId, - type: "xhr", - url: i.url, - xhr: i.xhr, - }); - break; - } - }); - return { - stop: function () { - return r.unsubscribe(); - }, - }; - } - function Dr(t, e, n) { - var r = kr().subscribe(function (r) { - var i = r; - if (Ge(e, i.url)) - switch (i.state) { - case "start": - n.traceFetch(i), - (i.requestIndex = Fr()), - t.notify(5, { - requestIndex: i.requestIndex, - }); - break; - case "complete": - n.clearTracingIfNeeded(i), - t.notify(6, { - duration: i.duration, - method: i.method, - requestIndex: i.requestIndex, - responseType: i.responseType, - spanId: i.spanId, - startClocks: i.startClocks, - status: i.status, - traceId: i.traceId, - type: "fetch", - url: i.url, - response: i.response, - init: i.init, - input: i.input, - }); - break; - } - }); - return { - stop: function () { - return r.unsubscribe(); - }, - }; - } - function Fr() { - var t = Nr; - return (Nr += 1), t; - } - function zr(t, e) { - void 0 === e && (e = T); - var n = { - errorCount: 0, - longTaskCount: 0, - resourceCount: 0, - userActionCount: 0, - }, - r = t.subscribe(11, function (t) { - var r = t.type; - switch (r) { - case "error": - (n.errorCount += 1), e(n); - break; - case "action": - (n.userActionCount += 1), e(n); - break; - case "long_task": - (n.longTaskCount += 1), e(n); - break; - case "resource": - (n.resourceCount += 1), e(n); - break; - } - }); - return { - stop: function () { - r.unsubscribe(); - }, - eventCounts: n, - }; - } - var Ur = 100, - Br = 100; - function Vr(t, e, n, r) { - var i = qr(t, e); - return Hr(i, n, r); - } - function Hr(t, e, n) { - var r, - i = !1, - o = setTimeout( - Tt(function () { - return c({ hadActivity: !1 }); - }), - Ur - ), - a = - n && - setTimeout( - Tt(function () { - return c({ hadActivity: !0, end: pt() }); - }), - n - ), - s = t.subscribe(function (t) { - var e = t.isBusy; - clearTimeout(o), clearTimeout(r); - var n = pt(); - e || - (r = setTimeout( - Tt(function () { - return c({ hadActivity: !0, end: n }); - }), - Br - )); - }), - u = function () { - (i = !0), - clearTimeout(o), - clearTimeout(r), - clearTimeout(a), - s.unsubscribe(); - }; - function c(t) { - i || (u(), e(t)); - } - return { stop: u }; - } - function qr(t, e) { - var n = new st(function () { - var n, - i = [], - o = 0; - return ( - i.push( - e.subscribe(function () { - return r(o); - }), - t.subscribe(0, function (t) { - t.some(function (t) { - var e = t.entryType; - return "resource" === e; - }) && r(o); - }), - t.subscribe(5, function (t) { - void 0 === n && (n = t.requestIndex), r(++o); - }), - t.subscribe(6, function (t) { - void 0 === n || t.requestIndex < n || r(--o); - }) - ), - function () { - return i.forEach(function (t) { - return t.unsubscribe(); - }); - } - ); - }); - function r(t) { - n.notify({ isBusy: t > 0 }); - } - return n; - } - var Wr = "data-dd-action-name"; - function Gr(t, e) { - return ( - Zr(t, Wr) || - (e && Zr(t, e)) || - ti(t, e, Xr) || - ti(t, e, Yr) || - "" - ); - } - function Zr(t, e) { - var n; - if (si()) n = t.closest("[".concat(e, "]")); - else { - var r = t; - while (r) { - if (r.hasAttribute(e)) { - n = r; - break; - } - r = r.parentElement; - } - } - if (n) { - var i = n.getAttribute(e); - return ni(ei(i.trim())); - } - } - var Jr, - Kr, - Xr = [ - function (t, e) { - if (ai()) { - if ( - "labels" in t && - t.labels && - t.labels.length > 0 - ) - return ii(t.labels[0], e); - } else if (t.id) { - var n = - t.ownerDocument && - t.ownerDocument.querySelector( - 'label[for="'.concat( - t.id.replace('"', '\\"'), - '"]' - ) - ); - return n && ii(n, e); - } - }, - function (t) { - if ("INPUT" === t.nodeName) { - var e = t, - n = e.getAttribute("type"); - if ( - "button" === n || - "submit" === n || - "reset" === n - ) - return e.value; - } - }, - function (t, e) { - if ( - "BUTTON" === t.nodeName || - "LABEL" === t.nodeName || - "button" === t.getAttribute("role") - ) - return ii(t, e); - }, - function (t) { - return t.getAttribute("aria-label"); - }, - function (t, e) { - var n = t.getAttribute("aria-labelledby"); - if (n) - return n - .split(/\s+/) - .map(function (e) { - return ri(t, e); - }) - .filter(function (t) { - return Boolean(t); - }) - .map(function (t) { - return ii(t, e); - }) - .join(" "); - }, - function (t) { - return t.getAttribute("alt"); - }, - function (t) { - return t.getAttribute("name"); - }, - function (t) { - return t.getAttribute("title"); - }, - function (t) { - return t.getAttribute("placeholder"); - }, - function (t, e) { - if ("options" in t && t.options.length > 0) - return ii(t.options[0], e); - }, - ], - Yr = [ - function (t, e) { - return ii(t, e); - }, - ], - Qr = 10; - function ti(t, e, n) { - var r = t, - i = 0; - while ( - i <= Qr && - r && - "BODY" !== r.nodeName && - "HTML" !== r.nodeName && - "HEAD" !== r.nodeName - ) { - for (var o = 0, a = n; o < a.length; o++) { - var s = a[o], - u = s(r, e); - if ("string" === typeof u) { - var c = u.trim(); - if (c) return ni(ei(c)); - } - } - if ("FORM" === r.nodeName) break; - (r = r.parentElement), (i += 1); - } - } - function ei(t) { - return t.replace(/\s+/g, " "); - } - function ni(t) { - return t.length > 100 ? "".concat(H(t, 100), " [...]") : t; - } - function ri(t, e) { - return t.ownerDocument - ? t.ownerDocument.getElementById(e) - : null; - } - function ii(t, e) { - if (!t.isContentEditable) { - if ("innerText" in t) { - var n = t.innerText, - r = function (e) { - for ( - var r = t.querySelectorAll(e), i = 0; - i < r.length; - i += 1 - ) { - var o = r[i]; - if ("innerText" in o) { - var a = o.innerText; - a && - a.trim().length > 0 && - (n = n.replace(a, "")); - } - } - }; - return ( - oi() || r("script, style"), - r("[".concat(Wr, "]")), - e && r("[".concat(e, "]")), - n - ); - } - return t.textContent; - } - } - function oi() { - return !Dn(); - } - function ai() { - return ( - void 0 === Jr && - (Jr = "labels" in HTMLInputElement.prototype), - Jr - ); - } - function si() { - return ( - void 0 === Kr && (Kr = "closest" in HTMLElement.prototype), - Kr - ); - } - var ui = 10 * v, - ci = 5 * y; - function fi(t, e, n) { - var r = n.actionNameAttribute, - i = new fr(ci); - t.subscribe(8, function () { - i.reset(); - }); - var o = li(function (n) { - if (kt("frustration-signals") || !i.find()) { - var o = Gr(n.target, r); - if (o) - var a = di( - t, - e, - "click", - o, - n, - function (t) { - s.close(gt(t)); - }, - function () { - s.remove(); - } - ), - s = i.add(a, a.startClocks.relative); - } - }).stop, - a = { - findActionId: function (t) { - var e; - return kt("frustration-signals") - ? i.findAll(t).map(function (t) { - return t.id; - }) - : null === (e = i.find(t)) || void 0 === e - ? void 0 - : e.id; - }, - }; - return { - stop: function () { - i.findAll().forEach(function (t) { - return t.discard(); - }), - o(); - }, - actionContexts: a, - }; - } - function li(t) { - return q( - window, - "click", - function (e) { - e.target instanceof Element && t(e); - }, - { capture: !0 } - ); - } - function di(t, e, n, r, i, o, a) { - var s, - u = S(), - c = vt(), - f = zr(t), - l = Vr( - t, - e, - function (t) { - t.hadActivity && c.timeStamp <= t.end - ? d(t.end) - : p(); - }, - ui - ).stop; - function d(e) { - h(); - var a = f.eventCounts; - t.notify(1, { - counts: { - errorCount: a.errorCount, - longTaskCount: a.longTaskCount, - resourceCount: a.resourceCount, - }, - duration: mt(c.timeStamp, e), - id: u, - name: r, - startClocks: c, - type: n, - event: i, - }), - o(e); - } - function p() { - h(), a(); - } - function h() { - l(), f.stop(), s && s.unsubscribe(); - } - return ( - kt("frustration-signals") || (s = t.subscribe(2, p)), - { discard: p, id: u, startClocks: c } - ); - } - function pi(t, e, n, r) { - t.subscribe(1, function (e) { - return t.notify(10, hi(e, r)); - }); - var i = { findActionId: T }; - return ( - n.trackInteractions && (i = fi(t, e, n).actionContexts), - { - addAction: function (e, n) { - t.notify( - 10, - x({ savedCommonContext: n }, hi(e, r)) - ); - }, - actionContexts: i, - } - ); - } - function hi(t, e) { - var n = vi(t) - ? { - action: { - error: { count: t.counts.errorCount }, - id: t.id, - loading_time: dt(t.duration), - long_task: { count: t.counts.longTaskCount }, - resource: { count: t.counts.resourceCount }, - }, - } - : void 0, - r = vi(t) ? void 0 : t.context, - i = Y( - { - action: { - id: S(), - target: { name: t.name }, - type: t.type, - }, - date: t.startClocks.timeStamp, - type: "action", - }, - n - ), - o = e.isInForegroundAt(t.startClocks.relative); - return ( - void 0 !== o && (i.view = { in_foreground: o }), - { - customerContext: r, - rawRumEvent: i, - startTime: t.startClocks.relative, - domainContext: vi(t) ? { event: t.event } : {}, - } - ); - } - function vi(t) { - return "custom" !== t.type; - } - var yi = - /^(?:[Uu]ncaught (?:exception: )?)?(?:((?:Eval|Internal|Range|Reference|Syntax|Type|URI|)Error): )?(.*)$/; - function mi(t) { - var e = gi(t).stop, - n = bi(t).stop; - return { - stop: function () { - e(), n(); - }, - }; - } - function gi(t) { - return hr(window, "onerror", { - before: function (e, n, r, i, a) { - var s; - if (a) (s = o(a)), t(s, a); - else { - var u, - c = { url: n, column: i, line: r }, - f = e; - if ("[object String]" === {}.toString.call(e)) { - var l = yi.exec(f); - l && ((u = l[1]), (f = l[2])); - } - (s = { - name: u, - message: "string" === typeof f ? f : void 0, - stack: [c], - }), - t(s, e); - } - }, - }); - } - function bi(t) { - return hr(window, "onunhandledrejection", { - before: function (e) { - var n = e.reason || "Empty reason", - r = o(n); - t(r, n); - }, - }); - } - function _i(t) { - return mi(function (e, n) { - var r = et(e, n, "Uncaught"), - i = r.stack, - o = r.message, - a = r.type; - t.notify({ - message: o, - stack: i, - type: a, - source: tt.SOURCE, - startClocks: vt(), - originalError: n, - handling: "unhandled", - }); - }); - } - var wi = { - log: "log", - debug: "debug", - info: "info", - warn: "warn", - error: "error", - }, - xi = {}; - function ki(t) { - var e = t.map(function (t) { - return xi[t] || (xi[t] = Si(t)), xi[t]; - }); - return ut.apply(void 0, e); - } - function Si(t) { - var e = new st(function () { - var n = console[t]; - return ( - (console[t] = function () { - for (var r = [], i = 0; i < arguments.length; i++) - r[i] = arguments[i]; - n.apply(console, r); - var o = it(); - At(function () { - e.notify(Ei(r, t, o)); - }); - }), - function () { - console[t] = n; - } - ); - }); - return e; - } - function Ei(t, e, n) { - var r, - i = t - .map(function (t) { - return Oi(t); - }) - .join(" "); - if (e === wi.error) { - var a = I(t, function (t) { - return t instanceof Error; - }); - (r = a ? nt(o(a)) : void 0), - (i = "console error: ".concat(i)); - } - return { api: e, message: i, stack: r, handlingStack: n }; - } - function Oi(t) { - return "string" === typeof t - ? t - : t instanceof Error - ? rt(o(t)) - : A(t, void 0, 2); - } - function Ti(t) { - var e = ki([wi.error]).subscribe(function (e) { - return t.notify({ - startClocks: vt(), - message: e.message, - stack: e.stack, - source: tt.CONSOLE, - handling: "handled", - handlingStack: e.handlingStack, - }); - }); - return { - stop: function () { - e.unsubscribe(); - }, - }; - } - var Ai, - Ci, - ji = { - intervention: "intervention", - deprecation: "deprecation", - cspViolation: "csp_violation", - }; - function Ii(t) { - var e = []; - j(t, ji.cspViolation) && e.push(Li()); - var n = t.filter(function (t) { - return t !== ji.cspViolation; - }); - return n.length && e.push(Mi(n)), ut.apply(void 0, e); - } - function Mi(t) { - var e = new st(function () { - if (window.ReportingObserver) { - var n = Tt(function (t) { - return t.forEach(function (t) { - e.notify(Pi(t)); - }); - }), - r = new window.ReportingObserver(n, { - types: t, - buffered: !0, - }); - return ( - r.observe(), - function () { - r.disconnect(); - } - ); - } - }); - return e; - } - function Li() { - var t = new st(function () { - var e = Tt(function (e) { - t.notify(Ni(e)); - }), - n = q(document, "securitypolicyviolation", e).stop; - return n; - }); - return t; - } - function Pi(t) { - var e = t.type, - n = t.body; - return { - type: e, - subtype: n.id, - message: "".concat(e, ": ").concat(n.message), - stack: Ri( - n.id, - n.message, - n.sourceFile, - n.lineNumber, - n.columnNumber - ), - }; - } - function Ni(t) { - var e = ji.cspViolation, - n = "'" - .concat(t.blockedURI, "' blocked by '") - .concat(t.effectiveDirective, "' directive"); - return { - type: ji.cspViolation, - subtype: t.effectiveDirective, - message: "".concat(e, ": ").concat(n), - stack: Ri( - t.effectiveDirective, - "" - .concat(n, ' of the policy "') - .concat(H(t.originalPolicy, 100, "..."), '"'), - t.sourceFile, - t.lineNumber, - t.columnNumber - ), - }; - } - function Ri(t, e, n, r, i) { - return ( - n && - nt({ - name: t, - message: e, - stack: [{ func: "?", url: n, line: r, column: i }], - }) - ); - } - function $i(t) { - var e = Ii([ji.cspViolation, ji.intervention]).subscribe( - function (e) { - return t.notify({ - startClocks: vt(), - message: e.message, - stack: e.stack, - type: e.subtype, - source: tt.REPORT, - handling: "unhandled", - }); - } - ); - return { - stop: function () { - e.unsubscribe(); - }, - }; - } - function Di(t, e) { - var n = new st(); - return ( - Ti(n), - _i(n), - $i(n), - n.subscribe(function (e) { - return t.notify(12, { error: e }); - }), - Fi(t, e) - ); - } - function Fi(t, e) { - return ( - t.subscribe(12, function (n) { - var r = n.error, - i = n.customerContext, - o = n.savedCommonContext; - t.notify( - 10, - x( - { customerContext: i, savedCommonContext: o }, - Ui(r, e) - ) - ); - }), - { - addError: function (e, n) { - var r = e.error, - i = e.handlingStack, - o = e.startClocks, - a = e.context, - s = zi(r, i, o); - t.notify(12, { - customerContext: a, - savedCommonContext: n, - error: s, - }); - }, - } - ); - } - function zi(t, e, n) { - var r = t instanceof Error ? o(t) : void 0; - return x( - { - startClocks: n, - source: tt.CUSTOM, - originalError: t, - handling: "handled", - }, - et(r, t, "Provided", e) - ); - } - function Ui(t, e) { - var n = { - date: t.startClocks.timeStamp, - error: { - id: S(), - message: t.message, - source: t.source, - stack: t.stack, - handling_stack: t.handlingStack, - type: t.type, - handling: t.handling, - source_type: "browser", - }, - type: "error", - }, - r = e.isInForegroundAt(t.startClocks.relative); - return ( - void 0 !== r && (n.view = { in_foreground: r }), - { - rawRumEvent: n, - startTime: t.startClocks.relative, - domainContext: { error: t.originalError }, - } - ); - } - function Bi(t, e) { - t.subscribe(0, function (n) { - for (var r = 0, i = n; r < i.length; r++) { - var o = i[r]; - if ("longtask" !== o.entryType) break; - var a = e.findTrackedSession(o.startTime); - if (!a || a.hasLitePlan) break; - var s = ct(o.startTime), - u = { - date: s.timeStamp, - long_task: { - id: S(), - duration: dt(o.duration), - }, - type: "long_task", - }; - t.notify(10, { - rawRumEvent: u, - startTime: s.relative, - domainContext: { performanceEntry: o.toJSON() }, - }); - } - }); - } - function Vi(t) { - if (performance && "getEntriesByName" in performance) { - var e = performance.getEntriesByName(t.url, "resource"); - if (e.length && "toJSON" in e[0]) { - var n = e - .map(function (t) { - return t.toJSON(); - }) - .filter(Ve) - .filter(function (e) { - return Wi( - e, - t.startClocks.relative, - qi({ - startTime: t.startClocks.relative, - duration: t.duration, - }) - ); - }); - return 1 === n.length - ? n[0] - : 2 === n.length && Hi(n) - ? n[1] - : void 0; - } - } - } - function Hi(t) { - return qi(t[0]) <= t[1].startTime; - } - function qi(t) { - return t.startTime + t.duration; - } - function Wi(t, e, n) { - var r = 1; - return t.startTime >= e - r && qi(t) <= n + r; - } - function Gi(t) { - t.subscribe(6, function (e) { - t.notify(10, Zi(e)); - }), - t.subscribe(0, function (e) { - for (var n = 0, r = e; n < r.length; n++) { - var i = r[n]; - "resource" !== i.entryType || - ze(i) || - t.notify(10, Ji(i)); - } - }); - } - function Zi(t) { - var e = "xhr" === t.type ? "xhr" : "fetch", - n = Vi(t), - r = n ? ct(n.startTime) : t.startClocks, - i = n ? Ki(n) : void 0, - o = Xi(t), - a = Y( - { - date: r.timeStamp, - resource: { - id: S(), - type: e, - duration: dt(t.duration), - method: t.method, - status_code: t.status, - url: t.url, - }, - type: "resource", - }, - o, - i - ); - return { - startTime: r.relative, - rawRumEvent: a, - domainContext: { - performanceEntry: n && Qi(n), - xhr: t.xhr, - response: t.response, - requestInput: t.input, - requestInit: t.init, - error: t.error, - }, - }; - } - function Ji(t) { - var e = De(t), - n = Ki(t), - r = Yi(t), - i = ct(t.startTime), - o = Y( - { - date: i.timeStamp, - resource: { id: S(), type: e, url: t.name }, - type: "resource", - }, - r, - n - ); - return { - startTime: i.relative, - rawRumEvent: o, - domainContext: { performanceEntry: Qi(t) }, - }; - } - function Ki(t) { - return { resource: x({ duration: Ue(t), size: We(t) }, Be(t)) }; - } - function Xi(t) { - var e = t.traceId && t.spanId; - if (e) - return { - _dd: { - span_id: t.spanId.toDecimalString(), - trace_id: t.traceId.toDecimalString(), - }, - }; - } - function Yi(t) { - return t.traceId ? { _dd: { trace_id: t.traceId } } : void 0; - } - function Qi(t) { - return an() && t instanceof PerformanceEntry && t.toJSON(), t; - } - function to(t) { - return ( - void 0 === t && (t = window), - Ai || - ("hidden" === document.visibilityState - ? (Ai = { timeStamp: 0 }) - : ((Ai = { timeStamp: 1 / 0 }), - (Ci = W( - t, - ["pagehide", "visibilitychange"], - function (t) { - ("pagehide" !== t.type && - "hidden" !== - document.visibilityState) || - ((Ai.timeStamp = t.timeStamp), Ci()); - }, - { capture: !0 } - ).stop))), - Ai - ); - } - var eo = 10 * y; - function no(t, e) { - var n = {}; - function r(t) { - x(n, t), e(n); - } - var i = ro(t, r).stop, - o = io(t, function (t) { - return r({ firstContentfulPaint: t }); - }).stop, - a = oo(t, window, function (t) { - r({ largestContentfulPaint: t }); - }).stop, - s = ao(t, function (t) { - var e = t.firstInputDelay, - n = t.firstInputTime; - r({ firstInputDelay: e, firstInputTime: n }); - }).stop; - return { - stop: function () { - i(), o(), a(), s(); - }, - }; - } - function ro(t, e) { - var n = t.subscribe(0, function (t) { - for (var n = 0, r = t; n < r.length; n++) { - var i = r[n]; - "navigation" === i.entryType && - e({ - domComplete: i.domComplete, - domContentLoaded: i.domContentLoadedEventEnd, - domInteractive: i.domInteractive, - loadEvent: i.loadEventEnd, - }); - } - }).unsubscribe; - return { stop: n }; - } - function io(t, e) { - var n = to(), - r = t.subscribe(0, function (t) { - var r = I(t, function (t) { - return ( - "paint" === t.entryType && - "first-contentful-paint" === t.name && - t.startTime < n.timeStamp && - t.startTime < eo - ); - }); - r && e(r.startTime); - }).unsubscribe; - return { stop: r }; - } - function oo(t, e, n) { - var r = to(), - i = 1 / 0, - o = W( - e, - ["pointerdown", "keydown"], - function (t) { - i = t.timeStamp; - }, - { capture: !0, once: !0 } - ).stop, - a = t.subscribe(0, function (t) { - var e = M(t, function (t) { - return ( - "largest-contentful-paint" === t.entryType && - t.startTime < i && - t.startTime < r.timeStamp && - t.startTime < eo - ); - }); - e && n(e.startTime); - }).unsubscribe; - return { - stop: function () { - o(), a(); - }, - }; - } - function ao(t, e) { - var n = to(), - r = t.subscribe(0, function (t) { - var r = I(t, function (t) { - return ( - "first-input" === t.entryType && - t.startTime < n.timeStamp - ); - }); - if (r) { - var i = mt(r.startTime, r.processingStart); - e({ - firstInputDelay: i >= 0 ? i : 0, - firstInputTime: r.startTime, - }); - } - }).unsubscribe; - return { stop: r }; - } - function so(t, e, n, r, i) { - var o, - a = { - eventCounts: { - errorCount: 0, - longTaskCount: 0, - resourceCount: 0, - userActionCount: 0, - }, - }, - s = zr(t, function (t) { - (a.eventCounts = t), n(); - }).stop, - u = uo(t, e, r, i, function (t) { - (a.loadingTime = t), n(); - }), - c = u.stop, - f = u.setLoadEvent; - return ( - lo() - ? ((a.cumulativeLayoutShift = 0), - (o = co(t, function (t) { - (a.cumulativeLayoutShift = t), n(); - }).stop)) - : (o = T), - { - stop: function () { - s(), c(), o(); - }, - setLoadEvent: f, - viewMetrics: a, - } - ); - } - function uo(t, e, n, r, i) { - var o = "initial_load" === n, - a = !0, - s = []; - function u() { - !a && !o && s.length > 0 && i(Math.max.apply(Math, s)); - } - var c = Vr(t, e, function (t) { - a && - ((a = !1), - t.hadActivity && s.push(mt(r.timeStamp, t.end)), - u()); - }).stop; - return { - stop: c, - setLoadEvent: function (t) { - o && ((o = !1), s.push(t), u()); - }, - }; - } - function co(t, e) { - var n = 0, - r = fo(), - i = t.subscribe(0, function (t) { - for (var i = 0, o = t; i < o.length; i++) { - var a = o[i]; - "layout-shift" !== a.entryType || - a.hadRecentInput || - (r.update(a), - r.value() > n && ((n = r.value()), e(O(n, 4)))); - } - }).unsubscribe; - return { stop: i }; - } - function fo() { - var t, - e, - n = 0; - return { - update: function (r) { - var i = - void 0 === t || - r.startTime - e >= v || - r.startTime - t >= 5 * v; - i - ? ((t = e = r.startTime), (n = r.value)) - : ((n += r.value), (e = r.startTime)); - }, - value: function () { - return n; - }, - }; - } - function lo() { - return on("layout-shift"); - } - var po = 3e3, - ho = 5 * y; - function vo(t, e, n, r, i, o) { - var a, - s = d(o), - u = s.stop, - c = s.initialView, - f = c, - l = h().stop; - function d(r) { - var i = yo(e, n, t, "initial_load", yt(), r), - o = no(e, function (t) { - i.updateTimings(t), i.scheduleUpdate(); - }).stop; - return { initialView: i, stop: o }; - } - function p(r, i) { - return yo(e, n, t, "route_change", r, i); - } - function h() { - e.subscribe(8, function () { - f.end(), - (f = p(void 0, { - name: f.name, - service: f.service, - version: f.version, - })); - }), - e.subscribe(9, function () { - f.end(), f.triggerUpdate(); - }); - var t = window.setInterval( - Tt(function () { - f.triggerUpdate(); - }), - ho - ); - return { - stop: function () { - clearInterval(t); - }, - }; - } - function v(t) { - return t.subscribe(function (t) { - var e = t.oldLocation, - n = t.newLocation; - if (go(e, n)) - return f.end(), f.triggerUpdate(), void (f = p()); - }); - } - return ( - i && (a = v(r)), - { - addTiming: function (t, e) { - void 0 === e && (e = pt()), - f.addTiming(t, e), - f.scheduleUpdate(); - }, - startView: function (t, e) { - f.end(e), f.triggerUpdate(), (f = p(e, t)); - }, - stop: function () { - null === a || void 0 === a || a.unsubscribe(), - u(), - l(), - f.end(); - }, - } - ); - } - function yo(t, e, n, r, i, o) { - void 0 === i && (i = vt()); - var a, - s, - u, - c, - f = S(), - l = {}, - d = {}, - p = 0, - h = k(n); - o && ((s = o.name), (u = o.service), (c = o.version)), - t.notify(2, { - id: f, - name: s, - startClocks: i, - service: u, - version: c, - }); - var v = w(Tt(O), po, { leading: !1 }), - y = v.throttled, - m = v.cancel, - g = so(t, e, y, r, i), - b = g.setLoadEvent, - _ = g.stop, - E = g.viewMetrics; - function O() { - p += 1; - var e = void 0 === a ? pt() : a.timeStamp; - t.notify( - 3, - x( - { - customTimings: d, - documentVersion: p, - id: f, - name: s, - service: u, - version: c, - loadingType: r, - location: h, - startClocks: i, - timings: l, - duration: mt(i.timeStamp, e), - isActive: void 0 === a, - }, - E - ) - ); - } - return ( - O(), - { - name: s, - service: u, - version: c, - scheduleUpdate: y, - end: function (e) { - void 0 === e && (e = vt()), - (a = e), - _(), - t.notify(4, { endClocks: a }); - }, - triggerUpdate: function () { - m(), O(); - }, - updateTimings: function (t) { - (l = t), void 0 !== t.loadEvent && b(t.loadEvent); - }, - addTiming: function (t, e) { - var n = _t(e) ? e : mt(i.timeStamp, e); - d[mo(t)] = n; - }, - } - ); - } - function mo(t) { - var e = t.replace(/[^a-zA-Z0-9-_.@$]/g, "_"); - return ( - e !== t && - r.warn( - "Invalid timing name: " - .concat(t, ", sanitized to: ") - .concat(e) - ), - e - ); - } - function go(t, e) { - return ( - t.pathname !== e.pathname || - (!bo(e.hash) && _o(e.hash) !== _o(t.hash)) - ); - } - function bo(t) { - var e = t.substr(1); - return !!document.getElementById(e); - } - function _o(t) { - var e = t.indexOf("?"); - return e < 0 ? t : t.slice(0, e); - } - function wo(t, e, n, r, i, o, a, s) { - return ( - t.subscribe(3, function (e) { - return t.notify(10, xo(e, o, a)); - }), - vo(n, t, r, i, !e.trackViewsManually, s) - ); - } - function xo(t, e, n) { - var r = n.getReplayStats(t.id), - i = { - _dd: { - document_version: t.documentVersion, - replay_stats: r, - }, - date: t.startClocks.timeStamp, - type: "view", - view: { - action: { count: t.eventCounts.userActionCount }, - cumulative_layout_shift: t.cumulativeLayoutShift, - dom_complete: dt(t.timings.domComplete), - dom_content_loaded: dt(t.timings.domContentLoaded), - dom_interactive: dt(t.timings.domInteractive), - error: { count: t.eventCounts.errorCount }, - first_contentful_paint: dt( - t.timings.firstContentfulPaint - ), - first_input_delay: dt(t.timings.firstInputDelay), - first_input_time: dt(t.timings.firstInputTime), - is_active: t.isActive, - name: t.name, - largest_contentful_paint: dt( - t.timings.largestContentfulPaint - ), - load_event: dt(t.timings.loadEvent), - loading_time: ko(dt(t.loadingTime)), - loading_type: t.loadingType, - long_task: { count: t.eventCounts.longTaskCount }, - resource: { count: t.eventCounts.resourceCount }, - time_spent: dt(t.duration), - in_foreground_periods: - e.selectInForegroundPeriodsFor( - t.startClocks.relative, - t.duration - ), - }, - session: { has_replay: !!r || void 0 }, - }; - return ( - D(t.customTimings) || - (i.view.custom_timings = F(t.customTimings, dt)), - { - rawRumEvent: i, - startTime: t.startClocks.relative, - domainContext: { location: t.location }, - } - ); - } - function ko(t) { - return P(t) && t < 0 ? void 0 : t; - } - var So = "_dd", - Eo = "_dd_r", - Oo = "_dd_l", - To = "rum", - Ao = "logs"; - function Co(t) { - var e = Jt(Vn), - n = Jt(So), - r = Jt(Eo), - i = Jt(Oo); - if (!e) { - var o = {}; - n && (o.id = n), - i && /^[01]$/.test(i) && (o[Ao] = i), - r && /^[012]$/.test(r) && (o[To] = r), - Xn(o, t); - } - } - var jo = y, - Io = or, - Mo = []; - function Lo(t, e, n) { - Co(t); - var r = ar(t, e, n); - Mo.push(function () { - return r.stop(); - }); - var i = new fr(Io); - function o() { - return { - id: r.getSession().id, - trackingType: r.getSession()[e], - }; - } - return ( - Mo.push(function () { - return i.stop(); - }), - r.renewObservable.subscribe(function () { - i.add(o(), ht()); - }), - r.expireObservable.subscribe(function () { - i.closeActive(ht()); - }), - r.expandOrRenewSession(), - i.add(o(), yt().relative), - Po(function () { - return r.expandOrRenewSession(); - }), - No(function () { - return r.expandSession(); - }), - { - findActiveSession: function (t) { - return i.find(t); - }, - renewObservable: r.renewObservable, - expireObservable: r.expireObservable, - } - ); - } - function Po(t) { - var e = W( - window, - ["click", "touchstart", "keydown", "scroll"], - t, - { capture: !0, passive: !0 } - ).stop; - Mo.push(e); - } - function No(t) { - var e = Tt(function () { - "visible" === document.visibilityState && t(); - }), - n = q(document, "visibilitychange", e).stop; - Mo.push(n); - var r = setInterval(e, jo); - Mo.push(function () { - clearInterval(r); - }); - } - var Ro = "rum"; - function $o(t, e) { - var n = Lo(t.cookieOptions, Ro, function (e) { - return Fo(t, e); - }); - return ( - n.expireObservable.subscribe(function () { - e.notify(7); - }), - n.renewObservable.subscribe(function () { - e.notify(8); - }), - { - findTrackedSession: function (t) { - var e = n.findActiveSession(t); - if (e && Uo(e.trackingType)) - return { - id: e.id, - hasReplayPlan: "1" === e.trackingType, - hasLitePlan: "2" === e.trackingType, - }; - }, - } - ); - } - function Do() { - var t = { - id: "00000000-aaaa-0000-aaaa-000000000000", - hasReplayPlan: !0, - hasLitePlan: !1, - }; - return { - findTrackedSession: function () { - return t; - }, - }; - } - function Fo(t, e) { - var n; - return ( - (n = zo(e) - ? e - : E(t.sampleRate) - ? E(t.replaySampleRate) - ? "1" - : "2" - : "0"), - { trackingType: n, isTracked: Uo(n) } - ); - } - function zo(t) { - return "0" === t || "1" === t || "2" === t; - } - function Uo(t) { - return "2" === t || "1" === t; - } - function Bo(t, e, n) { - var r = Vo(t, e); - e.subscribe(11, function (t) { - "view" === t.type ? r.upsert(t, t.view.id) : r.add(t); - }), - n.subscribe(function (t) { - return r.add(t); - }); - } - function Vo(t, e) { - var n, - r = o(t.rumEndpointBuilder, function () { - return e.notify(9); - }), - i = t.replica; - function o(e, n) { - return new Ae( - new je(e, t.batchBytesLimit), - t.maxBatchSize, - t.batchBytesLimit, - t.maxMessageSize, - t.flushTimeout, - n - ); - } - function a(t) { - return Y(t, { application: { id: i.applicationId } }); - } - return ( - void 0 !== i && (n = o(i.rumEndpointBuilder)), - { - add: function (t) { - r.add(t), n && n.add(a(t)); - }, - upsert: function (t, e) { - r.upsert(t, e), n && n.upsert(a(t), e); - }, - } - ); - } - function Ho(t) { - var e = Bt(); - t.subscribe(11, function (t) { - e.send("rum", t); - }); - } - var qo = or; - function Wo(t, e, n) { - var r, - i = new fr(qo); - t.subscribe(4, function (t) { - var e = t.endClocks; - i.closeActive(e.relative); - }), - t.subscribe(2, function (t) { - var e = t.startClocks, - o = n.href; - i.add( - a({ url: o, referrer: r || document.referrer }), - e.relative - ), - (r = o); - }); - var o = e.subscribe(function (t) { - var e = t.newLocation, - n = i.find(); - if (n) { - var r = ht(); - i.closeActive(r), - i.add( - a({ url: e.href, referrer: n.view.referrer }), - r - ); - } - }); - function a(t) { - var e = t.url, - n = t.referrer; - return { view: { url: e, referrer: n } }; - } - return { - findUrl: function (t) { - return i.find(t); - }, - stop: function () { - o.unsubscribe(), i.stop(); - }, - }; - } - function Go(t) { - var e = k(t), - n = new st(function () { - var t = Zo(r).stop, - e = Jo(r).stop; - return function () { - t(), e(); - }; - }); - function r() { - if (e.href !== t.href) { - var r = k(t); - n.notify({ newLocation: r, oldLocation: e }), (e = r); - } - } - return n; - } - function Zo(t) { - var e = hr(history, "pushState", { after: t }).stop, - n = hr(history, "replaceState", { after: t }).stop, - r = q(window, "popstate", t).stop; - return { - stop: function () { - e(), n(), r(); - }, - }; - } - function Jo(t) { - return q(window, "hashchange", t); - } - function Ko(t, e, n, r) { - var i = new $n(), - o = Xo(t); - o.setExternalContextProvider(function () { - var e; - return Y( - { - application_id: t.applicationId, - session: { - id: - null === (e = a.findTrackedSession()) || - void 0 === e - ? void 0 - : e.id, - }, - }, - f.findView(), - { view: { name: null } } - ); - }), - o.setTelemetryContextProvider(function () { - var e, n; - return { - application: { id: t.applicationId }, - session: { - id: - null === (e = a.findTrackedSession()) || - void 0 === e - ? void 0 - : e.id, - }, - view: { - id: - null === (n = f.findView()) || void 0 === n - ? void 0 - : n.view.id, - }, - action: { id: p.findActionId() }, - }; - }), - Vt() ? Ho(i) : Bo(t, i, o.telemetryEventObservable); - var a = Vt() ? Do() : $o(t, i), - s = Pe(), - u = Go(location), - c = Yo(i, t, location, a, u, s, e), - f = c.viewContexts, - l = c.foregroundContexts, - d = c.urlContexts, - p = c.actionContexts, - h = c.addAction; - Bi(i, a), Gi(i); - var v = wo(i, t, location, s, u, l, n, r), - y = v.addTiming, - m = v.startView, - g = Di(i, l).addError; - Rr(i, t, a), sn(i, t); - var b = Rn(t.applicationId, a, f, p, d); - return { - addAction: h, - addError: g, - addTiming: y, - startView: m, - lifeCycle: i, - viewContexts: f, - session: a, - getInternalContext: b.get, - }; - } - function Xo(t) { - var e, - n = Ot(t); - if (Vt()) { - var r = Bt(); - n.monitoringMessageObservable.subscribe(function (t) { - return r.send("internal_log", t); - }), - n.telemetryEventObservable.subscribe(function (t) { - return r.send("internal_telemetry", t); - }); - } else if (t.internalMonitoringEndpointBuilder) { - var i = Le( - t, - t.internalMonitoringEndpointBuilder, - null === (e = t.replica) || void 0 === e - ? void 0 - : e.internalMonitoringEndpointBuilder - ); - n.monitoringMessageObservable.subscribe(function (t) { - return i.add(t); - }); - } - return n; - } - function Yo(t, e, n, r, i, o, a) { - var s = dr(t), - u = Wo(t, i, n), - c = Cn(), - f = pi(t, o, e, c), - l = f.addAction, - d = f.actionContexts; - return ( - kn(e, t, r, s, u, d, a), - { - viewContexts: s, - foregroundContexts: c, - urlContexts: u, - addAction: l, - actionContexts: d, - stop: function () { - s.stop(), c.stop(); - }, - } - ); - } - var Qo = { - FullSnapshot: 2, - IncrementalSnapshot: 3, - Meta: 4, - Focus: 6, - ViewEnd: 7, - VisualViewport: 8, - }, - ta = { - IGNORE: "ignore", - HIDDEN: "hidden", - ALLOW: ye.ALLOW, - MASK: ye.MASK, - MASK_USER_INPUT: ye.MASK_USER_INPUT, - }, - ea = "data-dd-privacy", - na = "allow", - ra = "mask", - ia = "mask-user-input", - oa = "hidden", - aa = "dd-privacy-allow", - sa = "dd-privacy-mask", - ua = "dd-privacy-mask-user-input", - ca = "dd-privacy-hidden", - fa = "***", - la = - "data:image/gif;base64,R0lGODlhAQABAIAAAMLCwgAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==", - da = { - INPUT: !0, - OUTPUT: !0, - TEXTAREA: !0, - SELECT: !0, - OPTION: !0, - DATALIST: !0, - OPTGROUP: !0, - }, - pa = 1e5, - ha = "x"; - function va(t, e) { - var n = t.parentNode ? va(t.parentNode, e) : e, - r = ma(t); - return ya(r, n); - } - function ya(t, e) { - switch (e) { - case ta.HIDDEN: - case ta.IGNORE: - return e; - } - switch (t) { - case ta.ALLOW: - case ta.MASK: - case ta.MASK_USER_INPUT: - case ta.HIDDEN: - case ta.IGNORE: - return t; - default: - return e; - } - } - function ma(t) { - if (ba(t)) { - var e = t.getAttribute(ea); - if ("BASE" === t.tagName) return ta.ALLOW; - if ("INPUT" === t.tagName) { - var n = t; - if ( - "password" === n.type || - "email" === n.type || - "tel" === n.type - ) - return ta.MASK; - if ("hidden" === n.type) return ta.MASK; - var r = n.getAttribute("autocomplete"); - if (r && 0 === r.indexOf("cc-")) return ta.MASK; - } - return e === oa || t.classList.contains(ca) - ? ta.HIDDEN - : e === ra || t.classList.contains(sa) - ? ta.MASK - : e === ia || t.classList.contains(ua) - ? ta.MASK_USER_INPUT - : e === na || t.classList.contains(aa) - ? ta.ALLOW - : Da(t) - ? ta.IGNORE - : void 0; - } - } - function ga(t, e) { - switch (e) { - case ta.MASK: - case ta.HIDDEN: - case ta.IGNORE: - return !0; - case ta.MASK_USER_INPUT: - return _a(t) ? wa(t.parentNode) : wa(t); - default: - return !1; - } - } - function ba(t) { - return t.nodeType === t.ELEMENT_NODE; - } - function _a(t) { - return t.nodeType === t.TEXT_NODE; - } - function wa(t) { - if (!t || t.nodeType !== t.ELEMENT_NODE) return !1; - var e = t; - if ("INPUT" === e.tagName) - switch (e.type) { - case "button": - case "color": - case "reset": - case "submit": - return !1; - } - return !!da[e.tagName]; - } - var xa = function (t) { - return t.replace(/\S/g, ha); - }; - function ka(t, e, n) { - var r, - i = - null === (r = t.parentElement) || void 0 === r - ? void 0 - : r.tagName, - o = t.textContent || ""; - if (!e || o.trim()) { - var a = n, - s = "STYLE" === i || void 0, - u = "SCRIPT" === i; - if (u) o = fa; - else if (a === ta.HIDDEN) o = fa; - else if (ga(t, a) && !s) - if ( - "DATALIST" === i || - "SELECT" === i || - "OPTGROUP" === i - ) { - if (!o.trim()) return; - } else o = "OPTION" === i ? fa : xa(o); - return o; - } - } - var Sa = new WeakMap(); - function Ea(t) { - return Sa.has(t); - } - function Oa(t) { - var e = t; - while (e) { - if (!Ea(e)) return !1; - e = e.parentNode; - } - return !0; - } - function Ta(t) { - return Sa.get(t); - } - function Aa(t, e) { - Sa.set(t, e); - } - function Ca(t, e) { - var n = t.tagName, - r = t.value; - if (ga(t, e)) { - var i = t.type; - if ( - "INPUT" === n && - ("button" === i || "submit" === i || "reset" === i) - ) - return r; - if (!r || "OPTION" === n) return; - return fa; - } - return "OPTION" === n || "SELECT" === n - ? t.value - : "INPUT" === n || "TEXTAREA" === n - ? r - : void 0; - } - function ja(t) { - return Boolean(t.changedTouches); - } - function Ia(t, e) { - Array.prototype.forEach.call(t, e); - } - function Ma(t, e) { - return La(t, { document: t, parentNodePrivacyLevel: e }); - } - function La(t, e) { - var n = Pa(t, e); - if (!n) return null; - var r = Ta(t) || Ha(), - i = n; - return ( - (i.id = r), - Aa(t, r), - e.serializedNodeIds && e.serializedNodeIds.add(r), - i - ); - } - function Pa(t, e) { - switch (t.nodeType) { - case t.DOCUMENT_NODE: - return Na(t, e); - case t.DOCUMENT_TYPE_NODE: - return Ra(t); - case t.ELEMENT_NODE: - return $a(t, e); - case t.TEXT_NODE: - return Fa(t, e); - case t.CDATA_SECTION_NODE: - return za(); - } - } - function Na(t, e) { - return { type: 0, childNodes: Ua(t, e) }; - } - function Ra(t) { - return { - type: 1, - name: t.name, - publicId: t.publicId, - systemId: t.systemId, - }; - } - function $a(t, e) { - var n, - r = Wa(t.tagName), - i = Ka(t) || void 0, - o = ya(ma(t), e.parentNodePrivacyLevel); - if (o === ta.HIDDEN) { - var a = t.getBoundingClientRect(), - s = a.width, - u = a.height; - return { - type: 2, - tagName: r, - attributes: - ((n = { - rr_width: "".concat(s, "px"), - rr_height: "".concat(u, "px"), - }), - (n[ea] = oa), - n), - childNodes: [], - isSVG: i, - }; - } - if (o !== ta.IGNORE) { - var c = Xa(t, o), - f = []; - if (t.childNodes.length) { - var l = void 0; - (l = - e.parentNodePrivacyLevel === o && - e.ignoreWhiteSpace === ("head" === r) - ? e - : x({}, e, { - parentNodePrivacyLevel: o, - ignoreWhiteSpace: "head" === r, - })), - (f = Ua(t, l)); - } - return { - type: 2, - tagName: r, - attributes: c, - childNodes: f, - isSVG: i, - }; - } - } - function Da(t) { - if ("SCRIPT" === t.nodeName) return !0; - if ("LINK" === t.nodeName) { - var e = i("rel"); - return ( - ("preload" === e && "script" === i("as")) || - "shortcut icon" === e || - "icon" === e - ); - } - if ("META" === t.nodeName) { - var n = i("name"), - r = ((e = i("rel")), i("property")); - return ( - /^msapplication-tile(image|color)$/.test(n) || - "application-name" === n || - "icon" === e || - "apple-touch-icon" === e || - "shortcut icon" === e || - "keywords" === n || - "description" === n || - /^(og|twitter|fb):/.test(r) || - /^(og|twitter):/.test(n) || - "pinterest" === n || - "robots" === n || - "googlebot" === n || - "bingbot" === n || - t.hasAttribute("http-equiv") || - "author" === n || - "generator" === n || - "framework" === n || - "publisher" === n || - "progid" === n || - /^article:/.test(r) || - /^product:/.test(r) || - "google-site-verification" === n || - "yandex-verification" === n || - "csrf-token" === n || - "p:domain_verify" === n || - "verify-v1" === n || - "verification" === n || - "shopify-checkout-api-token" === n - ); - } - function i(e) { - return (t.getAttribute(e) || "").toLowerCase(); - } - return !1; - } - function Fa(t, e) { - var n, - r = - null === (n = t.parentElement) || void 0 === n - ? void 0 - : n.tagName, - i = ka( - t, - e.ignoreWhiteSpace || !1, - e.parentNodePrivacyLevel - ); - if (i) - return { - type: 3, - textContent: i, - isStyle: "STYLE" === r || void 0, - }; - } - function za() { - return { type: 4, textContent: "" }; - } - function Ua(t, e) { - var n = []; - return ( - Ia(t.childNodes, function (t) { - var r = La(t, e); - r && n.push(r); - }), - n - ); - } - function Ba(t, e, n) { - if (e === ta.HIDDEN) return null; - var r = t.getAttribute(n); - if (e === ta.MASK) { - var i = t.tagName; - switch (n) { - case "title": - case "alt": - return fa; - } - if ( - ("IMG" === i || "SOURCE" === i) && - ("src" === n || "srcset" === n) - ) - return la; - if ("A" === i && "href" === n) return fa; - if (r && 0 === n.indexOf("data-") && n !== ea) return fa; - } - return r && - "string" === typeof r && - r.length > pa && - "data:" === r.slice(0, 5) - ? "data:truncated" - : r; - } - var Va = 1; - function Ha() { - return Va++; - } - var qa = /[^a-z1-6-_]/; - function Wa(t) { - var e = t.toLowerCase().trim(); - return qa.test(e) ? "div" : e; - } - function Ga(t) { - try { - var e = t.rules || t.cssRules; - return e ? Array.from(e).map(Za).join("") : null; - } catch (n) { - return null; - } - } - function Za(t) { - return Ja(t) ? Ga(t.styleSheet) || "" : t.cssText; - } - function Ja(t) { - return "styleSheet" in t; - } - function Ka(t) { - return "svg" === t.tagName || t instanceof SVGElement; - } - function Xa(t, e) { - if (e === ta.HIDDEN) return {}; - for ( - var n = {}, r = Wa(t.tagName), i = t.ownerDocument, o = 0; - o < t.attributes.length; - o += 1 - ) { - var a = t.attributes.item(o), - s = a.name, - u = Ba(t, e, s); - null !== u && (n[s] = u); - } - if ( - t.value && - ("textarea" === r || - "select" === r || - "option" === r || - "input" === r) - ) { - var c = Ca(t, e); - void 0 !== c && (n.value = c); - } - if ("option" === r && e === ta.ALLOW) { - var f = t; - f.selected && (n.selected = f.selected); - } - if ("link" === r) { - var l = Array.from(i.styleSheets).find(function (e) { - return e.href === t.href; - }), - d = Ga(l); - d && l && (delete n.rel, delete n.href, (n._cssText = d)); - } - if ( - "style" === r && - t.sheet && - !(t.innerText || t.textContent || "").trim().length - ) { - d = Ga(t.sheet); - d && (n._cssText = d); - } - var p = t; - if ( - ("input" !== r || - ("radio" !== p.type && "checkbox" !== p.type) || - (e === ta.ALLOW - ? (n.checked = !!p.checked) - : ga(p, e) && (n.checked = fa)), - "audio" === r || "video" === r) - ) { - var h = t; - n.rr_mediaState = h.paused ? "paused" : "played"; - } - return ( - t.scrollLeft && - (n.rr_scrollLeft = Math.round(t.scrollLeft)), - t.scrollTop && (n.rr_scrollTop = Math.round(t.scrollTop)), - n - ); - } - var Ya = { - Mutation: 0, - MouseMove: 1, - MouseInteraction: 2, - Scroll: 3, - ViewportResize: 4, - Input: 5, - TouchMove: 6, - MediaInteraction: 7, - StyleSheetRule: 8, - }, - Qa = { - MouseUp: 0, - MouseDown: 1, - Click: 2, - ContextMenu: 3, - DblClick: 4, - Focus: 5, - Blur: 6, - TouchStart: 7, - TouchEnd: 9, - }, - ts = { Play: 0, Pause: 1 }, - es = 100; - function ns(t) { - var e = T, - n = []; - function r() { - e(), t(n), (n = []); - } - return { - addMutations: function (t) { - 0 === n.length && (e = Q(r, { timeout: es })), - n.push.apply(n, t); - }, - flush: r, - stop: function () { - e(); - }, - }; - } - function rs(t, e, n) { - var r = Ne(); - if (!r) return { stop: T }; - var i = ns(function (t) { - os(t.concat(o.takeRecords()), e, n); - }), - o = new r(Tt(i.addMutations)); - return ( - o.observe(document, { - attributeOldValue: !0, - attributes: !0, - characterData: !0, - characterDataOldValue: !0, - childList: !0, - subtree: !0, - }), - t.onFlush(i.flush), - { - stop: function () { - o.disconnect(), i.stop(); - }, - } - ); - } - var is = (function () { - function t() {} - return ( - (t.prototype.flush = function () { - var t; - null === (t = this.flushListener) || - void 0 === t || - t.call(this); - }), - (t.prototype.onFlush = function (t) { - this.flushListener = t; - }), - t - ); - })(); - function os(t, e, n) { - var r = t.filter(function (t) { - return ( - document.contains(t.target) && - Oa(t.target) && - va(t.target, n) !== ta.HIDDEN - ); - }), - i = as( - r.filter(function (t) { - return "childList" === t.type; - }), - n - ), - o = i.adds, - a = i.removes, - s = i.hasBeenSerialized, - u = ss( - r.filter(function (t) { - return "characterData" === t.type && !s(t.target); - }), - n - ), - c = us( - r.filter(function (t) { - return "attributes" === t.type && !s(t.target); - }), - n - ); - (u.length || c.length || a.length || o.length) && - e({ adds: o, removes: a, texts: u, attributes: c }); - } - function as(t, e) { - for ( - var n = new Set(), - r = new Map(), - i = function (t) { - Ia(t.addedNodes, function (t) { - n.add(t); - }), - Ia(t.removedNodes, function (e) { - n.has(e) || r.set(e, t.target), n.delete(e); - }); - }, - o = 0, - a = t; - o < a.length; - o++ - ) { - var s = a[o]; - i(s); - } - var u = Array.from(n); - cs(u); - for ( - var c = new Set(), f = [], l = 0, d = u; - l < d.length; - l++ - ) { - var p = d[l]; - if (!m(p)) { - var h = va(p.parentNode, e); - if (h !== ta.HIDDEN && h !== ta.IGNORE) { - var v = La(p, { - document: document, - serializedNodeIds: c, - parentNodePrivacyLevel: h, - }); - v && - f.push({ - nextId: g(p), - parentId: Ta(p.parentNode), - node: v, - }); - } - } - } - var y = []; - return ( - r.forEach(function (t, e) { - Ea(e) && y.push({ parentId: Ta(t), id: Ta(e) }); - }), - { adds: f, removes: y, hasBeenSerialized: m } - ); - function m(t) { - return Ea(t) && c.has(Ta(t)); - } - function g(t) { - var e = t.nextSibling; - while (e) { - if (Ea(e)) return Ta(e); - e = e.nextSibling; - } - return null; - } - } - function ss(t, e) { - for ( - var n, - r = [], - i = new Set(), - o = t.filter(function (t) { - return !i.has(t.target) && (i.add(t.target), !0); - }), - a = 0, - s = o; - a < s.length; - a++ - ) { - var u = s[a], - c = u.target.textContent; - if (c !== u.oldValue) { - var f = va(u.target.parentNode, e); - f !== ta.HIDDEN && - f !== ta.IGNORE && - r.push({ - id: Ta(u.target), - value: - null !== (n = ka(u.target, !1, f)) && - void 0 !== n - ? n - : null, - }); - } - } - return r; - } - function us(t, e) { - for ( - var n = [], - r = new Map(), - i = t.filter(function (t) { - var e = r.get(t.target); - return ( - !(null === e || void 0 === e - ? void 0 - : e.has(t.attributeName)) && - (e - ? e.add(t.attributeName) - : r.set( - t.target, - new Set([t.attributeName]) - ), - !0) - ); - }), - o = new Map(), - a = 0, - s = i; - a < s.length; - a++ - ) { - var u = s[a], - c = u.target.getAttribute(u.attributeName); - if (c !== u.oldValue) { - var f = va(u.target, e), - l = Ba(u.target, f, u.attributeName), - d = void 0; - if ("value" === u.attributeName) { - var p = Ca(u.target, f); - if (void 0 === p) continue; - d = p; - } else d = "string" === typeof l ? l : null; - var h = o.get(u.target); - h || - ((h = { id: Ta(u.target), attributes: {} }), - n.push(h), - o.set(u.target, h)), - (h.attributes[u.attributeName] = d); - } - } - return n; - } - function cs(t) { - t.sort(function (t, e) { - var n = t.compareDocumentPosition(e); - return n & Node.DOCUMENT_POSITION_CONTAINED_BY - ? -1 - : n & Node.DOCUMENT_POSITION_CONTAINS || - n & Node.DOCUMENT_POSITION_FOLLOWING - ? 1 - : n & Node.DOCUMENT_POSITION_PRECEDING - ? -1 - : 0; - }); - } - var fs = 25; - function ls() { - var t = window.visualViewport; - return ( - Math.abs(t.pageTop - t.offsetTop - window.scrollY) > fs || - Math.abs(t.pageLeft - t.offsetLeft - window.scrollX) > fs - ); - } - var ds, - ps = function (t, e) { - var n = window.visualViewport, - r = { - layoutViewportX: t, - layoutViewportY: e, - visualViewportX: t, - visualViewportY: e, - }; - return n - ? (ls() - ? ((r.layoutViewportX = Math.round( - t + n.offsetLeft - )), - (r.layoutViewportY = Math.round( - e + n.offsetTop - ))) - : ((r.visualViewportX = Math.round( - t - n.offsetLeft - )), - (r.visualViewportY = Math.round( - e - n.offsetTop - ))), - r) - : r; - }, - hs = function () { - var t = window.visualViewport; - return { - scale: t.scale, - offsetLeft: t.offsetLeft, - offsetTop: t.offsetTop, - pageLeft: t.pageLeft, - pageTop: t.pageTop, - height: t.height, - width: t.width, - }; - }; - function vs() { - var t = window.visualViewport; - return t ? t.width * t.scale : window.innerWidth || 0; - } - function ys() { - var t = window.visualViewport; - return t ? t.height * t.scale : window.innerHeight || 0; - } - function ms() { - var t = window.visualViewport; - return t - ? t.pageLeft - t.offsetLeft - : void 0 !== window.scrollX - ? window.scrollX - : window.pageXOffset || 0; - } - function gs() { - var t = window.visualViewport; - return t - ? t.pageTop - t.offsetTop - : void 0 !== window.scrollY - ? window.scrollY - : window.pageYOffset || 0; - } - var bs = 50, - _s = 100, - ws = 200; - function xs(t) { - var e = ks( - t.mutationController, - t.mutationCb, - t.defaultPrivacyLevel - ), - n = Ss(t.mousemoveCb), - r = Os(t.mouseInteractionCb, t.defaultPrivacyLevel), - i = Ts(t.scrollCb, t.defaultPrivacyLevel), - o = As(t.viewportResizeCb), - a = Cs(t.inputCb, t.defaultPrivacyLevel), - s = Is(t.mediaInteractionCb, t.defaultPrivacyLevel), - u = js(t.styleSheetRuleCb), - c = Ms(t.focusCb), - f = Ls(t.visualViewportResizeCb); - return function () { - e(), n(), r(), i(), o(), a(), s(), u(), c(), f(); - }; - } - function ks(t, e, n) { - return rs(t, e, n).stop; - } - function Ss(t) { - var e = w( - Tt(function (e) { - var n = e.target; - if (Ea(n)) { - var r = ja(e) ? e.changedTouches[0] : e, - i = r.clientX, - o = r.clientY, - a = { id: Ta(n), timeOffset: 0, x: i, y: o }; - if (window.visualViewport) { - var s = ps(i, o), - u = s.visualViewportX, - c = s.visualViewportY; - (a.x = u), (a.y = c); - } - t([a], ja(e) ? Ya.TouchMove : Ya.MouseMove); - } - }), - bs, - { trailing: !1 } - ).throttled; - return W(document, ["mousemove", "touchmove"], e, { - capture: !0, - passive: !0, - }).stop; - } - var Es = - ((ds = {}), - (ds["mouseup"] = Qa.MouseUp), - (ds["mousedown"] = Qa.MouseDown), - (ds["click"] = Qa.Click), - (ds["contextmenu"] = Qa.ContextMenu), - (ds["dblclick"] = Qa.DblClick), - (ds["focus"] = Qa.Focus), - (ds["blur"] = Qa.Blur), - (ds["touchstart"] = Qa.TouchStart), - (ds["touchend"] = Qa.TouchEnd), - ds); - function Os(t, e) { - var n = function (n) { - var r = n.target; - if (va(r, e) !== ta.HIDDEN && Ea(r)) { - var i = ja(n) ? n.changedTouches[0] : n, - o = i.clientX, - a = i.clientY, - s = { id: Ta(r), type: Es[n.type], x: o, y: a }; - if (window.visualViewport) { - var u = ps(o, a), - c = u.visualViewportX, - f = u.visualViewportY; - (s.x = c), (s.y = f); - } - t(s); - } - }; - return W(document, Object.keys(Es), n, { - capture: !0, - passive: !0, - }).stop; - } - function Ts(t, e) { - var n = w( - Tt(function (n) { - var r = n.target; - if (r && va(r, e) !== ta.HIDDEN && Ea(r)) { - var i = Ta(r); - r === document - ? t({ id: i, x: ms(), y: gs() }) - : t({ id: i, x: r.scrollLeft, y: r.scrollTop }); - } - }), - _s - ).throttled; - return q(document, "scroll", n, { capture: !0, passive: !0 }) - .stop; - } - function As(t) { - var e = w( - Tt(function () { - var e = ys(), - n = vs(); - t({ height: Number(e), width: Number(n) }); - }), - 200 - ).throttled; - return q(window, "resize", e, { capture: !0, passive: !0 }) - .stop; - } - function Cs(t, e) { - var n = new WeakMap(); - function r(t) { - var n = va(t, e); - if (n !== ta.HIDDEN) { - var r, - o = t.type; - if ("radio" === o || "checkbox" === o) { - if (ga(t, n)) return; - r = { isChecked: t.checked }; - } else { - var a = Ca(t, n); - if (void 0 === a) return; - r = { text: a }; - } - i(t, r); - var s = t.name; - "radio" === o && - s && - t.checked && - Ia( - document.querySelectorAll( - 'input[type="radio"][name="'.concat(s, '"]') - ), - function (e) { - e !== t && i(e, { isChecked: !1 }); - } - ); - } - } - function i(e, r) { - if (Ea(e)) { - var i = n.get(e); - (i && - i.text === r.text && - i.isChecked === r.isChecked) || - (n.set(e, r), t(x({ id: Ta(e) }, r))); - } - } - var o = W( - document, - ["input", "change"], - function (t) { - (t.target instanceof HTMLInputElement || - t.target instanceof HTMLTextAreaElement || - t.target instanceof HTMLSelectElement) && - r(t.target); - }, - { capture: !0, passive: !0 } - ).stop, - a = [ - vr(HTMLInputElement.prototype, "value", r), - vr(HTMLInputElement.prototype, "checked", r), - vr(HTMLSelectElement.prototype, "value", r), - vr(HTMLTextAreaElement.prototype, "value", r), - vr(HTMLSelectElement.prototype, "selectedIndex", r), - ]; - return function () { - a.forEach(function (t) { - return t.stop(); - }), - o(); - }; - } - function js(t) { - var e = hr(CSSStyleSheet.prototype, "insertRule", { - before: function (e, n) { - Ea(this.ownerNode) && - t({ - id: Ta(this.ownerNode), - adds: [{ rule: e, index: n }], - }); - }, - }).stop, - n = hr(CSSStyleSheet.prototype, "deleteRule", { - before: function (e) { - Ea(this.ownerNode) && - t({ - id: Ta(this.ownerNode), - removes: [{ index: e }], - }); - }, - }).stop; - return function () { - e(), n(); - }; - } - function Is(t, e) { - var n = function (n) { - var r = n.target; - r && - va(r, e) !== ta.HIDDEN && - Ea(r) && - t({ - id: Ta(r), - type: "play" === n.type ? ts.Play : ts.Pause, - }); - }; - return W(document, ["play", "pause"], n, { - capture: !0, - passive: !0, - }).stop; - } - function Ms(t) { - return W(window, ["focus", "blur"], function () { - t({ has_focus: document.hasFocus() }); - }).stop; - } - function Ls(t) { - if (!window.visualViewport) return T; - var e = w( - Tt(function () { - t(hs()); - }), - ws, - { trailing: !1 } - ), - n = e.throttled, - r = e.cancel, - i = W(window.visualViewport, ["resize", "scroll"], n, { - capture: !0, - passive: !0, - }).stop; - return function () { - i(), r(); - }; - } - function Ps(t) { - var e = t.emit; - if (!e) throw new Error("emit function is required"); - var n = new is(), - r = function (r) { - void 0 === r && (r = pt()), - n.flush(), - e({ - data: { - height: ys(), - href: window.location.href, - width: vs(), - }, - type: Qo.Meta, - timestamp: r, - }), - e({ - data: { has_focus: document.hasFocus() }, - type: Qo.Focus, - timestamp: r, - }), - e({ - data: { - node: Ma(document, t.defaultPrivacyLevel), - initialOffset: { left: ms(), top: gs() }, - }, - type: Qo.FullSnapshot, - timestamp: r, - }), - window.visualViewport && - e({ - data: hs(), - type: Qo.VisualViewport, - timestamp: r, - }); - }; - r(); - var i = xs({ - mutationController: n, - defaultPrivacyLevel: t.defaultPrivacyLevel, - inputCb: function (t) { - return e(Ns(Ya.Input, t)); - }, - mediaInteractionCb: function (t) { - return e(Ns(Ya.MediaInteraction, t)); - }, - mouseInteractionCb: function (t) { - return e(Ns(Ya.MouseInteraction, t)); - }, - mousemoveCb: function (t, n) { - return e(Ns(n, { positions: t })); - }, - mutationCb: function (t) { - return e(Ns(Ya.Mutation, t)); - }, - scrollCb: function (t) { - return e(Ns(Ya.Scroll, t)); - }, - styleSheetRuleCb: function (t) { - return e(Ns(Ya.StyleSheetRule, t)); - }, - viewportResizeCb: function (t) { - return e(Ns(Ya.ViewportResize, t)); - }, - focusCb: function (t) { - return e({ data: t, type: Qo.Focus, timestamp: pt() }); - }, - visualViewportResizeCb: function (t) { - e({ - data: t, - type: Qo.VisualViewport, - timestamp: pt(), - }); - }, - }); - return { - stop: i, - takeFullSnapshot: r, - flushMutations: function () { - return n.flush(); - }, - }; - } - function Ns(t, e) { - return { - data: x({ source: t }, e), - type: Qo.IncrementalSnapshot, - timestamp: pt(), - }; - } - var Rs = 6e4; - function $s(t, e, n, r, i) { - var o = new FormData(); - o.append( - "segment", - new Blob([e], { type: "application/octet-stream" }), - "".concat(n.session.id, "-").concat(n.start) - ), - Ds(n, function (t, e) { - return o.append(t, e); - }), - o.append("raw_segment_size", r.toString()); - var a = new je(t, Rs); - a.send(o, e.byteLength, i); - } - function Ds(t, e, n) { - void 0 === n && (n = ""), - $(t).forEach(function (t) { - var r = t[0], - i = t[1]; - "object" === typeof i && null !== i - ? Ds(i, e, "".concat(n).concat(r, ".")) - : e("".concat(n).concat(r), String(i)); - }); - } - var Fs, - zs = 10; - function Us(t) { - return Ws(t).segments_count; - } - function Bs(t) { - Ws(t).segments_count += 1; - } - function Vs(t) { - Ws(t).records_count += 1; - } - function Hs(t, e) { - Ws(t).segments_total_raw_size += e; - } - function qs(t) { - return null === Fs || void 0 === Fs ? void 0 : Fs.get(t); - } - function Ws(t) { - var e; - return ( - Fs || (Fs = new Map()), - Fs.has(t) - ? (e = Fs.get(t)) - : ((e = { - records_count: 0, - segments_count: 0, - segments_total_raw_size: 0, - }), - Fs.set(t, e), - Fs.size > zs && Gs()), - e - ); - } - function Gs() { - if (Fs) - if (Fs.keys) Fs.delete(Fs.keys().next().value); - else { - var t = !0; - Fs.forEach(function (e, n) { - t && (Fs.delete(n), (t = !1)); - }); - } - } - var Zs, - Js = 0, - Ks = (function () { - function t(t, e, n, r, i, o) { - var a = this; - (this.worker = t), - (this.isFlushed = !1), - (this.id = Js++); - var s = e.view.id; - (this.metadata = x( - { - start: r.timestamp, - end: r.timestamp, - creation_reason: n, - records_count: 1, - has_full_snapshot: r.type === Qo.FullSnapshot, - index_in_view: Us(s), - }, - e - )), - Bs(s), - Vs(s); - var u = Tt(function (e) { - var n = e.data; - "errored" !== n.type && - "initialized" !== n.type && - (n.id === a.id - ? (Hs(s, n.additionalRawSize), - "flushed" === n.type - ? (o(n.result, n.rawSize), - t.removeEventListener("message", u)) - : i(n.compressedSize)) - : n.id > a.id && - (t.removeEventListener("message", u), - Ct( - "Segment did not receive a 'flush' response before being replaced." - ))); - }); - t.addEventListener("message", u), - this.worker.postMessage({ - data: '{"records":['.concat(JSON.stringify(r)), - id: this.id, - action: "write", - }); - } - return ( - (t.prototype.addRecord = function (t) { - var e; - (this.metadata.end = t.timestamp), - (this.metadata.records_count += 1), - Vs(this.metadata.view.id), - (e = this.metadata).has_full_snapshot || - (e.has_full_snapshot = - t.type === Qo.FullSnapshot), - this.worker.postMessage({ - data: ",".concat(JSON.stringify(t)), - id: this.id, - action: "write", - }); - }), - (t.prototype.flush = function (t) { - this.worker.postMessage({ - data: "],".concat( - JSON.stringify(this.metadata).slice(1), - "\n" - ), - id: this.id, - action: "flush", - }), - (this.isFlushed = !0), - (this.flushReason = t); - }), - t - ); - })(), - Xs = 3e4, - Ys = Rs; - function Qs(t, e, n, r, i, o) { - return tu( - t, - function () { - return eu(e, n, r); - }, - i, - o - ); - } - function tu(t, e, n, r, i) { - void 0 === i && (i = window); - var o = { status: 0, nextSegmentCreationReason: "init" }, - a = t.subscribe(2, function () { - c("view_change"); - }).unsubscribe, - s = t.subscribe(9, function () { - c("before_unload"); - }).unsubscribe, - u = q( - i, - "visibilitychange", - function () { - "hidden" === document.visibilityState && - c("visibility_hidden"); - }, - { capture: !0 } - ).stop; - function c(t) { - 1 === o.status && - (o.segment.flush(t || "sdk_stopped"), - clearTimeout(o.expirationTimeoutId)), - (o = t - ? { status: 0, nextSegmentCreationReason: t } - : { status: 2 }); - } - function f(t, i) { - var a = e(); - if (a) { - var s = new Ks( - r, - a, - t, - i, - function (t) { - !s.isFlushed && t > Ys && c("max_size"); - }, - function (t, e) { - n(t, s.metadata, e, s.flushReason); - } - ); - o = { - status: 1, - segment: s, - expirationTimeoutId: setTimeout( - Tt(function () { - c("max_duration"); - }), - Xs - ), - }; - } - } - return { - addRecord: function (t) { - switch (o.status) { - case 0: - f(o.nextSegmentCreationReason, t); - break; - case 1: - o.segment.addRecord(t); - break; - } - }, - stop: function () { - c(), a(), s(), u(); - }, - }; - } - function eu(t, e, n) { - var r = e.findTrackedSession(), - i = n.findView(); - if (r && i) - return { - application: { id: t }, - session: { id: r.id }, - view: { id: i.view.id }, - }; - } - function nu() { - return ( - Zs || - (Zs = URL.createObjectURL( - new Blob(["(".concat(ru, ")(self)")]) - )), - new Worker(Zs) - ); - } - function ru() { - function t(t) { - return function () { - try { - return t.apply(this, arguments); - } catch (e) { - try { - self.postMessage({ type: "errored", error: e }); - } catch (n) { - self.postMessage({ - type: "errored", - error: "".concat(e), - }); - } - } - }; - } - function e() { - var t = 4, - e = 0, - n = 1, - r = 2; - function i(t) { - var e = t.length; - while (--e >= 0) t[e] = 0; - } - var o = 0, - a = 1, - s = 2, - u = 3, - c = 258, - f = 29, - l = 256, - d = l + 1 + f, - p = 30, - h = 19, - v = 2 * d + 1, - y = 15, - m = 16, - g = 7, - b = 256, - _ = 16, - w = 17, - x = 18, - k = new Uint8Array([ - 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, - 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, - ]), - S = new Uint8Array([ - 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, - 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, - ]), - E = new Uint8Array([ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, - 3, 7, - ]), - O = new Uint8Array([ - 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, - 2, 14, 1, 15, - ]), - T = 512, - A = new Array(2 * (d + 2)); - i(A); - var C = new Array(2 * p); - i(C); - var j = new Array(T); - i(j); - var I = new Array(c - u + 1); - i(I); - var M = new Array(f); - i(M); - var L, - P, - N, - R = new Array(p); - function $(t, e, n, r, i) { - (this.static_tree = t), - (this.extra_bits = e), - (this.extra_base = n), - (this.elems = r), - (this.max_length = i), - (this.has_stree = t && t.length); - } - function D(t, e) { - (this.dyn_tree = t), - (this.max_code = 0), - (this.stat_desc = e); - } - i(R); - var F = function (t) { - return t < 256 ? j[t] : j[256 + (t >>> 7)]; - }, - z = function (t, e) { - (t.pending_buf[t.pending++] = 255 & e), - (t.pending_buf[t.pending++] = (e >>> 8) & 255); - }, - U = function (t, e, n) { - t.bi_valid > m - n - ? ((t.bi_buf |= (e << t.bi_valid) & 65535), - z(t, t.bi_buf), - (t.bi_buf = e >> (m - t.bi_valid)), - (t.bi_valid += n - m)) - : ((t.bi_buf |= (e << t.bi_valid) & 65535), - (t.bi_valid += n)); - }, - B = function (t, e, n) { - U(t, n[2 * e], n[2 * e + 1]); - }, - V = function (t, e) { - var n = 0; - do { - (n |= 1 & t), (t >>>= 1), (n <<= 1); - } while (--e > 0); - return n >>> 1; - }, - H = function (t) { - 16 === t.bi_valid - ? (z(t, t.bi_buf), - (t.bi_buf = 0), - (t.bi_valid = 0)) - : t.bi_valid >= 8 && - ((t.pending_buf[t.pending++] = - 255 & t.bi_buf), - (t.bi_buf >>= 8), - (t.bi_valid -= 8)); - }, - q = function (t, e) { - var n, - r, - i, - o, - a, - s, - u = e.dyn_tree, - c = e.max_code, - f = e.stat_desc.static_tree, - l = e.stat_desc.has_stree, - d = e.stat_desc.extra_bits, - p = e.stat_desc.extra_base, - h = e.stat_desc.max_length, - m = 0; - for (o = 0; o <= y; o++) t.bl_count[o] = 0; - for ( - u[2 * t.heap[t.heap_max] + 1] = 0, - n = t.heap_max + 1; - n < v; - n++ - ) - (r = t.heap[n]), - (o = u[2 * u[2 * r + 1] + 1] + 1), - o > h && ((o = h), m++), - (u[2 * r + 1] = o), - r > c || - (t.bl_count[o]++, - (a = 0), - r >= p && (a = d[r - p]), - (s = u[2 * r]), - (t.opt_len += s * (o + a)), - l && - (t.static_len += - s * (f[2 * r + 1] + a))); - if (0 !== m) { - do { - o = h - 1; - while (0 === t.bl_count[o]) o--; - t.bl_count[o]--, - (t.bl_count[o + 1] += 2), - t.bl_count[h]--, - (m -= 2); - } while (m > 0); - for (o = h; 0 !== o; o--) { - r = t.bl_count[o]; - while (0 !== r) - (i = t.heap[--n]), - i > c || - (u[2 * i + 1] !== o && - ((t.opt_len += - (o - u[2 * i + 1]) * - u[2 * i]), - (u[2 * i + 1] = o)), - r--); - } - } - }, - W = function (t, e, n) { - var r, - i, - o = new Array(y + 1), - a = 0; - for (r = 1; r <= y; r++) - o[r] = a = (a + n[r - 1]) << 1; - for (i = 0; i <= e; i++) { - var s = t[2 * i + 1]; - 0 !== s && (t[2 * i] = V(o[s]++, s)); - } - }, - G = function () { - var t, - e, - n, - r, - i, - o = new Array(y + 1); - for (n = 0, r = 0; r < f - 1; r++) - for (M[r] = n, t = 0; t < 1 << k[r]; t++) - I[n++] = r; - for (I[n - 1] = r, i = 0, r = 0; r < 16; r++) - for (R[r] = i, t = 0; t < 1 << S[r]; t++) - j[i++] = r; - for (i >>= 7; r < p; r++) - for ( - R[r] = i << 7, t = 0; - t < 1 << (S[r] - 7); - t++ - ) - j[256 + i++] = r; - for (e = 0; e <= y; e++) o[e] = 0; - t = 0; - while (t <= 143) (A[2 * t + 1] = 8), t++, o[8]++; - while (t <= 255) (A[2 * t + 1] = 9), t++, o[9]++; - while (t <= 279) (A[2 * t + 1] = 7), t++, o[7]++; - while (t <= 287) (A[2 * t + 1] = 8), t++, o[8]++; - for (W(A, d + 1, o), t = 0; t < p; t++) - (C[2 * t + 1] = 5), (C[2 * t] = V(t, 5)); - (L = new $(A, k, l + 1, d, y)), - (P = new $(C, S, 0, p, y)), - (N = new $(new Array(0), E, 0, h, g)); - }, - Z = function (t) { - var e; - for (e = 0; e < d; e++) t.dyn_ltree[2 * e] = 0; - for (e = 0; e < p; e++) t.dyn_dtree[2 * e] = 0; - for (e = 0; e < h; e++) t.bl_tree[2 * e] = 0; - (t.dyn_ltree[2 * b] = 1), - (t.opt_len = t.static_len = 0), - (t.last_lit = t.matches = 0); - }, - J = function (t) { - t.bi_valid > 8 - ? z(t, t.bi_buf) - : t.bi_valid > 0 && - (t.pending_buf[t.pending++] = t.bi_buf), - (t.bi_buf = 0), - (t.bi_valid = 0); - }, - K = function (t, e, n, r) { - J(t), - r && (z(t, n), z(t, ~n)), - t.pending_buf.set( - t.window.subarray(e, e + n), - t.pending - ), - (t.pending += n); - }, - X = function (t, e, n, r) { - var i = 2 * e, - o = 2 * n; - return ( - t[i] < t[o] || (t[i] === t[o] && r[e] <= r[n]) - ); - }, - Y = function (t, e, n) { - var r = t.heap[n], - i = n << 1; - while (i <= t.heap_len) { - if ( - (i < t.heap_len && - X( - e, - t.heap[i + 1], - t.heap[i], - t.depth - ) && - i++, - X(e, r, t.heap[i], t.depth)) - ) - break; - (t.heap[n] = t.heap[i]), (n = i), (i <<= 1); - } - t.heap[n] = r; - }, - Q = function (t, e, n) { - var r, - i, - o, - a, - s = 0; - if (0 !== t.last_lit) - do { - (r = - (t.pending_buf[t.d_buf + 2 * s] << 8) | - t.pending_buf[t.d_buf + 2 * s + 1]), - (i = t.pending_buf[t.l_buf + s]), - s++, - 0 === r - ? B(t, i, e) - : ((o = I[i]), - B(t, o + l + 1, e), - (a = k[o]), - 0 !== a && - ((i -= M[o]), U(t, i, a)), - r--, - (o = F(r)), - B(t, o, n), - (a = S[o]), - 0 !== a && - ((r -= R[o]), U(t, r, a))); - } while (s < t.last_lit); - B(t, b, e); - }, - tt = function (t, e) { - var n, - r, - i, - o = e.dyn_tree, - a = e.stat_desc.static_tree, - s = e.stat_desc.has_stree, - u = e.stat_desc.elems, - c = -1; - for ( - t.heap_len = 0, t.heap_max = v, n = 0; - n < u; - n++ - ) - 0 !== o[2 * n] - ? ((t.heap[++t.heap_len] = c = n), - (t.depth[n] = 0)) - : (o[2 * n + 1] = 0); - while (t.heap_len < 2) - (i = t.heap[++t.heap_len] = c < 2 ? ++c : 0), - (o[2 * i] = 1), - (t.depth[i] = 0), - t.opt_len--, - s && (t.static_len -= a[2 * i + 1]); - for ( - e.max_code = c, n = t.heap_len >> 1; - n >= 1; - n-- - ) - Y(t, o, n); - i = u; - do { - (n = t.heap[1]), - (t.heap[1] = t.heap[t.heap_len--]), - Y(t, o, 1), - (r = t.heap[1]), - (t.heap[--t.heap_max] = n), - (t.heap[--t.heap_max] = r), - (o[2 * i] = o[2 * n] + o[2 * r]), - (t.depth[i] = - (t.depth[n] >= t.depth[r] - ? t.depth[n] - : t.depth[r]) + 1), - (o[2 * n + 1] = o[2 * r + 1] = i), - (t.heap[1] = i++), - Y(t, o, 1); - } while (t.heap_len >= 2); - (t.heap[--t.heap_max] = t.heap[1]), - q(t, e), - W(o, c, t.bl_count); - }, - et = function (t, e, n) { - var r, - i, - o = -1, - a = e[1], - s = 0, - u = 7, - c = 4; - for ( - 0 === a && ((u = 138), (c = 3)), - e[2 * (n + 1) + 1] = 65535, - r = 0; - r <= n; - r++ - ) - (i = a), - (a = e[2 * (r + 1) + 1]), - (++s < u && i === a) || - (s < c - ? (t.bl_tree[2 * i] += s) - : 0 !== i - ? (i !== o && t.bl_tree[2 * i]++, - t.bl_tree[2 * _]++) - : s <= 10 - ? t.bl_tree[2 * w]++ - : t.bl_tree[2 * x]++, - (s = 0), - (o = i), - 0 === a - ? ((u = 138), (c = 3)) - : i === a - ? ((u = 6), (c = 3)) - : ((u = 7), (c = 4))); - }, - nt = function (t, e, n) { - var r, - i, - o = -1, - a = e[1], - s = 0, - u = 7, - c = 4; - for ( - 0 === a && ((u = 138), (c = 3)), r = 0; - r <= n; - r++ - ) - if ( - ((i = a), - (a = e[2 * (r + 1) + 1]), - !(++s < u && i === a)) - ) { - if (s < c) - do { - B(t, i, t.bl_tree); - } while (0 !== --s); - else - 0 !== i - ? (i !== o && - (B(t, i, t.bl_tree), s--), - B(t, _, t.bl_tree), - U(t, s - 3, 2)) - : s <= 10 - ? (B(t, w, t.bl_tree), - U(t, s - 3, 3)) - : (B(t, x, t.bl_tree), - U(t, s - 11, 7)); - (s = 0), - (o = i), - 0 === a - ? ((u = 138), (c = 3)) - : i === a - ? ((u = 6), (c = 3)) - : ((u = 7), (c = 4)); - } - }, - rt = function (t) { - var e; - for ( - et(t, t.dyn_ltree, t.l_desc.max_code), - et(t, t.dyn_dtree, t.d_desc.max_code), - tt(t, t.bl_desc), - e = h - 1; - e >= 3; - e-- - ) - if (0 !== t.bl_tree[2 * O[e] + 1]) break; - return (t.opt_len += 3 * (e + 1) + 5 + 5 + 4), e; - }, - it = function (t, e, n, r) { - var i; - for ( - U(t, e - 257, 5), - U(t, n - 1, 5), - U(t, r - 4, 4), - i = 0; - i < r; - i++ - ) - U(t, t.bl_tree[2 * O[i] + 1], 3); - nt(t, t.dyn_ltree, e - 1), - nt(t, t.dyn_dtree, n - 1); - }, - ot = function (t) { - var r, - i = 4093624447; - for (r = 0; r <= 31; r++, i >>>= 1) - if (1 & i && 0 !== t.dyn_ltree[2 * r]) return e; - if ( - 0 !== t.dyn_ltree[18] || - 0 !== t.dyn_ltree[20] || - 0 !== t.dyn_ltree[26] - ) - return n; - for (r = 32; r < l; r++) - if (0 !== t.dyn_ltree[2 * r]) return n; - return e; - }, - at = !1, - st = function (t) { - at || (G(), (at = !0)), - (t.l_desc = new D(t.dyn_ltree, L)), - (t.d_desc = new D(t.dyn_dtree, P)), - (t.bl_desc = new D(t.bl_tree, N)), - (t.bi_buf = 0), - (t.bi_valid = 0), - Z(t); - }, - ut = function (t, e, n, r) { - U(t, (o << 1) + (r ? 1 : 0), 3), K(t, e, n, !0); - }, - ct = function (t) { - U(t, a << 1, 3), B(t, b, A), H(t); - }, - ft = function (e, n, i, o) { - var u, - c, - f = 0; - e.level > 0 - ? (e.strm.data_type === r && - (e.strm.data_type = ot(e)), - tt(e, e.l_desc), - tt(e, e.d_desc), - (f = rt(e)), - (u = (e.opt_len + 3 + 7) >>> 3), - (c = (e.static_len + 3 + 7) >>> 3), - c <= u && (u = c)) - : (u = c = i + 5), - i + 4 <= u && -1 !== n - ? ut(e, n, i, o) - : e.strategy === t || c === u - ? (U(e, (a << 1) + (o ? 1 : 0), 3), - Q(e, A, C)) - : (U(e, (s << 1) + (o ? 1 : 0), 3), - it( - e, - e.l_desc.max_code + 1, - e.d_desc.max_code + 1, - f + 1 - ), - Q(e, e.dyn_ltree, e.dyn_dtree)), - Z(e), - o && J(e); - }, - lt = function (t, e, n) { - return ( - (t.pending_buf[t.d_buf + 2 * t.last_lit] = - (e >>> 8) & 255), - (t.pending_buf[t.d_buf + 2 * t.last_lit + 1] = - 255 & e), - (t.pending_buf[t.l_buf + t.last_lit] = 255 & n), - t.last_lit++, - 0 === e - ? t.dyn_ltree[2 * n]++ - : (t.matches++, - e--, - t.dyn_ltree[2 * (I[n] + l + 1)]++, - t.dyn_dtree[2 * F(e)]++), - t.last_lit === t.lit_bufsize - 1 - ); - }, - dt = st, - pt = ut, - ht = ft, - vt = lt, - yt = ct, - mt = { - _tr_init: dt, - _tr_stored_block: pt, - _tr_flush_block: ht, - _tr_tally: vt, - _tr_align: yt, - }, - gt = function (t, e, n, r) { - var i = (65535 & t) | 0, - o = ((t >>> 16) & 65535) | 0, - a = 0; - while (0 !== n) { - (a = n > 2e3 ? 2e3 : n), (n -= a); - do { - (i = (i + e[r++]) | 0), (o = (o + i) | 0); - } while (--a); - (i %= 65521), (o %= 65521); - } - return i | (o << 16) | 0; - }, - bt = gt, - _t = function () { - for (var t, e = [], n = 0; n < 256; n++) { - t = n; - for (var r = 0; r < 8; r++) - t = - 1 & t - ? 3988292384 ^ (t >>> 1) - : t >>> 1; - e[n] = t; - } - return e; - }, - wt = new Uint32Array(_t()), - xt = function (t, e, n, r) { - var i = wt, - o = r + n; - t ^= -1; - for (var a = r; a < o; a++) - t = (t >>> 8) ^ i[255 & (t ^ e[a])]; - return -1 ^ t; - }, - kt = xt, - St = { - 2: "need dictionary", - 1: "stream end", - 0: "", - "-1": "file error", - "-2": "stream error", - "-3": "data error", - "-4": "insufficient memory", - "-5": "buffer error", - "-6": "incompatible version", - }, - Et = { - Z_NO_FLUSH: 0, - Z_PARTIAL_FLUSH: 1, - Z_SYNC_FLUSH: 2, - Z_FULL_FLUSH: 3, - Z_FINISH: 4, - Z_BLOCK: 5, - Z_TREES: 6, - Z_OK: 0, - Z_STREAM_END: 1, - Z_NEED_DICT: 2, - Z_ERRNO: -1, - Z_STREAM_ERROR: -2, - Z_DATA_ERROR: -3, - Z_MEM_ERROR: -4, - Z_BUF_ERROR: -5, - Z_NO_COMPRESSION: 0, - Z_BEST_SPEED: 1, - Z_BEST_COMPRESSION: 9, - Z_DEFAULT_COMPRESSION: -1, - Z_FILTERED: 1, - Z_HUFFMAN_ONLY: 2, - Z_RLE: 3, - Z_FIXED: 4, - Z_DEFAULT_STRATEGY: 0, - Z_BINARY: 0, - Z_TEXT: 1, - Z_UNKNOWN: 2, - Z_DEFLATED: 8, - }, - Ot = mt._tr_init, - Tt = mt._tr_stored_block, - At = mt._tr_flush_block, - Ct = mt._tr_tally, - jt = mt._tr_align, - It = Et.Z_NO_FLUSH, - Mt = Et.Z_PARTIAL_FLUSH, - Lt = Et.Z_FULL_FLUSH, - Pt = Et.Z_FINISH, - Nt = Et.Z_BLOCK, - Rt = Et.Z_OK, - $t = Et.Z_STREAM_END, - Dt = Et.Z_STREAM_ERROR, - Ft = Et.Z_DATA_ERROR, - zt = Et.Z_BUF_ERROR, - Ut = Et.Z_DEFAULT_COMPRESSION, - Bt = Et.Z_FILTERED, - Vt = Et.Z_HUFFMAN_ONLY, - Ht = Et.Z_RLE, - qt = Et.Z_FIXED, - Wt = Et.Z_DEFAULT_STRATEGY, - Gt = Et.Z_UNKNOWN, - Zt = Et.Z_DEFLATED, - Jt = 9, - Kt = 15, - Xt = 8, - Yt = 29, - Qt = 256, - te = Qt + 1 + Yt, - ee = 30, - ne = 19, - re = 2 * te + 1, - ie = 15, - oe = 3, - ae = 258, - se = ae + oe + 1, - ue = 32, - ce = 42, - fe = 69, - le = 73, - de = 91, - pe = 103, - he = 113, - ve = 666, - ye = 1, - me = 2, - ge = 3, - be = 4, - _e = 3, - we = function (t, e) { - return (t.msg = St[e]), e; - }, - xe = function (t) { - return (t << 1) - (t > 4 ? 9 : 0); - }, - ke = function (t) { - var e = t.length; - while (--e >= 0) t[e] = 0; - }, - Se = function (t, e, n) { - return ((e << t.hash_shift) ^ n) & t.hash_mask; - }, - Ee = Se, - Oe = function (t) { - var e = t.state, - n = e.pending; - n > t.avail_out && (n = t.avail_out), - 0 !== n && - (t.output.set( - e.pending_buf.subarray( - e.pending_out, - e.pending_out + n - ), - t.next_out - ), - (t.next_out += n), - (e.pending_out += n), - (t.total_out += n), - (t.avail_out -= n), - (e.pending -= n), - 0 === e.pending && (e.pending_out = 0)); - }, - Te = function (t, e) { - At( - t, - t.block_start >= 0 ? t.block_start : -1, - t.strstart - t.block_start, - e - ), - (t.block_start = t.strstart), - Oe(t.strm); - }, - Ae = function (t, e) { - t.pending_buf[t.pending++] = e; - }, - Ce = function (t, e) { - (t.pending_buf[t.pending++] = (e >>> 8) & 255), - (t.pending_buf[t.pending++] = 255 & e); - }, - je = function (t, e, n, r) { - var i = t.avail_in; - return ( - i > r && (i = r), - 0 === i - ? 0 - : ((t.avail_in -= i), - e.set( - t.input.subarray( - t.next_in, - t.next_in + i - ), - n - ), - 1 === t.state.wrap - ? (t.adler = bt(t.adler, e, i, n)) - : 2 === t.state.wrap && - (t.adler = kt(t.adler, e, i, n)), - (t.next_in += i), - (t.total_in += i), - i) - ); - }, - Ie = function (t, e) { - var n, - r, - i = t.max_chain_length, - o = t.strstart, - a = t.prev_length, - s = t.nice_match, - u = - t.strstart > t.w_size - se - ? t.strstart - (t.w_size - se) - : 0, - c = t.window, - f = t.w_mask, - l = t.prev, - d = t.strstart + ae, - p = c[o + a - 1], - h = c[o + a]; - t.prev_length >= t.good_match && (i >>= 2), - s > t.lookahead && (s = t.lookahead); - do { - if ( - ((n = e), - c[n + a] === h && - c[n + a - 1] === p && - c[n] === c[o] && - c[++n] === c[o + 1]) - ) { - (o += 2), n++; - do {} while ( - c[++o] === c[++n] && - c[++o] === c[++n] && - c[++o] === c[++n] && - c[++o] === c[++n] && - c[++o] === c[++n] && - c[++o] === c[++n] && - c[++o] === c[++n] && - c[++o] === c[++n] && - o < d - ); - if ( - ((r = ae - (d - o)), - (o = d - ae), - r > a) - ) { - if ( - ((t.match_start = e), - (a = r), - r >= s) - ) - break; - (p = c[o + a - 1]), (h = c[o + a]); - } - } - } while ((e = l[e & f]) > u && 0 !== --i); - return a <= t.lookahead ? a : t.lookahead; - }, - Me = function (t) { - var e, - n, - r, - i, - o, - a = t.w_size; - do { - if ( - ((i = - t.window_size - - t.lookahead - - t.strstart), - t.strstart >= a + (a - se)) - ) { - t.window.set( - t.window.subarray(a, a + a), - 0 - ), - (t.match_start -= a), - (t.strstart -= a), - (t.block_start -= a), - (n = t.hash_size), - (e = n); - do { - (r = t.head[--e]), - (t.head[e] = r >= a ? r - a : 0); - } while (--n); - (n = a), (e = n); - do { - (r = t.prev[--e]), - (t.prev[e] = r >= a ? r - a : 0); - } while (--n); - i += a; - } - if (0 === t.strm.avail_in) break; - if ( - ((n = je( - t.strm, - t.window, - t.strstart + t.lookahead, - i - )), - (t.lookahead += n), - t.lookahead + t.insert >= oe) - ) { - (o = t.strstart - t.insert), - (t.ins_h = t.window[o]), - (t.ins_h = Ee( - t, - t.ins_h, - t.window[o + 1] - )); - while (t.insert) - if ( - ((t.ins_h = Ee( - t, - t.ins_h, - t.window[o + oe - 1] - )), - (t.prev[o & t.w_mask] = - t.head[t.ins_h]), - (t.head[t.ins_h] = o), - o++, - t.insert--, - t.lookahead + t.insert < oe) - ) - break; - } - } while (t.lookahead < se && 0 !== t.strm.avail_in); - }, - Le = function (t, e) { - var n = 65535; - for ( - n > t.pending_buf_size - 5 && - (n = t.pending_buf_size - 5); - ; - - ) { - if (t.lookahead <= 1) { - if ((Me(t), 0 === t.lookahead && e === It)) - return ye; - if (0 === t.lookahead) break; - } - (t.strstart += t.lookahead), (t.lookahead = 0); - var r = t.block_start + n; - if ( - (0 === t.strstart || t.strstart >= r) && - ((t.lookahead = t.strstart - r), - (t.strstart = r), - Te(t, !1), - 0 === t.strm.avail_out) - ) - return ye; - if ( - t.strstart - t.block_start >= - t.w_size - se && - (Te(t, !1), 0 === t.strm.avail_out) - ) - return ye; - } - return ( - (t.insert = 0), - e === Pt - ? (Te(t, !0), - 0 === t.strm.avail_out ? ge : be) - : (t.strstart > t.block_start && - (Te(t, !1), t.strm.avail_out), - ye) - ); - }, - Pe = function (t, e) { - for (var n, r; ; ) { - if (t.lookahead < se) { - if ((Me(t), t.lookahead < se && e === It)) - return ye; - if (0 === t.lookahead) break; - } - if ( - ((n = 0), - t.lookahead >= oe && - ((t.ins_h = Ee( - t, - t.ins_h, - t.window[t.strstart + oe - 1] - )), - (n = t.prev[t.strstart & t.w_mask] = - t.head[t.ins_h]), - (t.head[t.ins_h] = t.strstart)), - 0 !== n && - t.strstart - n <= t.w_size - se && - (t.match_length = Ie(t, n)), - t.match_length >= oe) - ) - if ( - ((r = Ct( - t, - t.strstart - t.match_start, - t.match_length - oe - )), - (t.lookahead -= t.match_length), - t.match_length <= t.max_lazy_match && - t.lookahead >= oe) - ) { - t.match_length--; - do { - t.strstart++, - (t.ins_h = Ee( - t, - t.ins_h, - t.window[ - t.strstart + oe - 1 - ] - )), - (n = t.prev[ - t.strstart & t.w_mask - ] = - t.head[t.ins_h]), - (t.head[t.ins_h] = t.strstart); - } while (0 !== --t.match_length); - t.strstart++; - } else - (t.strstart += t.match_length), - (t.match_length = 0), - (t.ins_h = t.window[t.strstart]), - (t.ins_h = Ee( - t, - t.ins_h, - t.window[t.strstart + 1] - )); - else - (r = Ct(t, 0, t.window[t.strstart])), - t.lookahead--, - t.strstart++; - if (r && (Te(t, !1), 0 === t.strm.avail_out)) - return ye; - } - return ( - (t.insert = - t.strstart < oe - 1 ? t.strstart : oe - 1), - e === Pt - ? (Te(t, !0), - 0 === t.strm.avail_out ? ge : be) - : t.last_lit && - (Te(t, !1), 0 === t.strm.avail_out) - ? ye - : me - ); - }, - Ne = function (t, e) { - for (var n, r, i; ; ) { - if (t.lookahead < se) { - if ((Me(t), t.lookahead < se && e === It)) - return ye; - if (0 === t.lookahead) break; - } - if ( - ((n = 0), - t.lookahead >= oe && - ((t.ins_h = Ee( - t, - t.ins_h, - t.window[t.strstart + oe - 1] - )), - (n = t.prev[t.strstart & t.w_mask] = - t.head[t.ins_h]), - (t.head[t.ins_h] = t.strstart)), - (t.prev_length = t.match_length), - (t.prev_match = t.match_start), - (t.match_length = oe - 1), - 0 !== n && - t.prev_length < t.max_lazy_match && - t.strstart - n <= t.w_size - se && - ((t.match_length = Ie(t, n)), - t.match_length <= 5 && - (t.strategy === Bt || - (t.match_length === oe && - t.strstart - t.match_start > - 4096)) && - (t.match_length = oe - 1)), - t.prev_length >= oe && - t.match_length <= t.prev_length) - ) { - (i = t.strstart + t.lookahead - oe), - (r = Ct( - t, - t.strstart - 1 - t.prev_match, - t.prev_length - oe - )), - (t.lookahead -= t.prev_length - 1), - (t.prev_length -= 2); - do { - ++t.strstart <= i && - ((t.ins_h = Ee( - t, - t.ins_h, - t.window[t.strstart + oe - 1] - )), - (n = t.prev[t.strstart & t.w_mask] = - t.head[t.ins_h]), - (t.head[t.ins_h] = t.strstart)); - } while (0 !== --t.prev_length); - if ( - ((t.match_available = 0), - (t.match_length = oe - 1), - t.strstart++, - r && - (Te(t, !1), 0 === t.strm.avail_out)) - ) - return ye; - } else if (t.match_available) { - if ( - ((r = Ct( - t, - 0, - t.window[t.strstart - 1] - )), - r && Te(t, !1), - t.strstart++, - t.lookahead--, - 0 === t.strm.avail_out) - ) - return ye; - } else - (t.match_available = 1), - t.strstart++, - t.lookahead--; - } - return ( - t.match_available && - ((r = Ct(t, 0, t.window[t.strstart - 1])), - (t.match_available = 0)), - (t.insert = - t.strstart < oe - 1 ? t.strstart : oe - 1), - e === Pt - ? (Te(t, !0), - 0 === t.strm.avail_out ? ge : be) - : t.last_lit && - (Te(t, !1), 0 === t.strm.avail_out) - ? ye - : me - ); - }, - Re = function (t, e) { - for (var n, r, i, o, a = t.window; ; ) { - if (t.lookahead <= ae) { - if ((Me(t), t.lookahead <= ae && e === It)) - return ye; - if (0 === t.lookahead) break; - } - if ( - ((t.match_length = 0), - t.lookahead >= oe && - t.strstart > 0 && - ((i = t.strstart - 1), - (r = a[i]), - r === a[++i] && - r === a[++i] && - r === a[++i])) - ) { - o = t.strstart + ae; - do {} while ( - r === a[++i] && - r === a[++i] && - r === a[++i] && - r === a[++i] && - r === a[++i] && - r === a[++i] && - r === a[++i] && - r === a[++i] && - i < o - ); - (t.match_length = ae - (o - i)), - t.match_length > t.lookahead && - (t.match_length = t.lookahead); - } - if ( - (t.match_length >= oe - ? ((n = Ct(t, 1, t.match_length - oe)), - (t.lookahead -= t.match_length), - (t.strstart += t.match_length), - (t.match_length = 0)) - : ((n = Ct(t, 0, t.window[t.strstart])), - t.lookahead--, - t.strstart++), - n && (Te(t, !1), 0 === t.strm.avail_out)) - ) - return ye; - } - return ( - (t.insert = 0), - e === Pt - ? (Te(t, !0), - 0 === t.strm.avail_out ? ge : be) - : t.last_lit && - (Te(t, !1), 0 === t.strm.avail_out) - ? ye - : me - ); - }, - $e = function (t, e) { - for (var n; ; ) { - if ( - 0 === t.lookahead && - (Me(t), 0 === t.lookahead) - ) { - if (e === It) return ye; - break; - } - if ( - ((t.match_length = 0), - (n = Ct(t, 0, t.window[t.strstart])), - t.lookahead--, - t.strstart++, - n && (Te(t, !1), 0 === t.strm.avail_out)) - ) - return ye; - } - return ( - (t.insert = 0), - e === Pt - ? (Te(t, !0), - 0 === t.strm.avail_out ? ge : be) - : t.last_lit && - (Te(t, !1), 0 === t.strm.avail_out) - ? ye - : me - ); - }; - function De(t, e, n, r, i) { - (this.good_length = t), - (this.max_lazy = e), - (this.nice_length = n), - (this.max_chain = r), - (this.func = i); - } - var Fe = [ - new De(0, 0, 0, 0, Le), - new De(4, 4, 8, 4, Pe), - new De(4, 5, 16, 8, Pe), - new De(4, 6, 32, 32, Pe), - new De(4, 4, 16, 16, Ne), - new De(8, 16, 32, 32, Ne), - new De(8, 16, 128, 128, Ne), - new De(8, 32, 128, 256, Ne), - new De(32, 128, 258, 1024, Ne), - new De(32, 258, 258, 4096, Ne), - ], - ze = function (t) { - (t.window_size = 2 * t.w_size), - ke(t.head), - (t.max_lazy_match = Fe[t.level].max_lazy), - (t.good_match = Fe[t.level].good_length), - (t.nice_match = Fe[t.level].nice_length), - (t.max_chain_length = Fe[t.level].max_chain), - (t.strstart = 0), - (t.block_start = 0), - (t.lookahead = 0), - (t.insert = 0), - (t.match_length = t.prev_length = oe - 1), - (t.match_available = 0), - (t.ins_h = 0); - }; - function Ue() { - (this.strm = null), - (this.status = 0), - (this.pending_buf = null), - (this.pending_buf_size = 0), - (this.pending_out = 0), - (this.pending = 0), - (this.wrap = 0), - (this.gzhead = null), - (this.gzindex = 0), - (this.method = Zt), - (this.last_flush = -1), - (this.w_size = 0), - (this.w_bits = 0), - (this.w_mask = 0), - (this.window = null), - (this.window_size = 0), - (this.prev = null), - (this.head = null), - (this.ins_h = 0), - (this.hash_size = 0), - (this.hash_bits = 0), - (this.hash_mask = 0), - (this.hash_shift = 0), - (this.block_start = 0), - (this.match_length = 0), - (this.prev_match = 0), - (this.match_available = 0), - (this.strstart = 0), - (this.match_start = 0), - (this.lookahead = 0), - (this.prev_length = 0), - (this.max_chain_length = 0), - (this.max_lazy_match = 0), - (this.level = 0), - (this.strategy = 0), - (this.good_match = 0), - (this.nice_match = 0), - (this.dyn_ltree = new Uint16Array(2 * re)), - (this.dyn_dtree = new Uint16Array( - 2 * (2 * ee + 1) - )), - (this.bl_tree = new Uint16Array(2 * (2 * ne + 1))), - ke(this.dyn_ltree), - ke(this.dyn_dtree), - ke(this.bl_tree), - (this.l_desc = null), - (this.d_desc = null), - (this.bl_desc = null), - (this.bl_count = new Uint16Array(ie + 1)), - (this.heap = new Uint16Array(2 * te + 1)), - ke(this.heap), - (this.heap_len = 0), - (this.heap_max = 0), - (this.depth = new Uint16Array(2 * te + 1)), - ke(this.depth), - (this.l_buf = 0), - (this.lit_bufsize = 0), - (this.last_lit = 0), - (this.d_buf = 0), - (this.opt_len = 0), - (this.static_len = 0), - (this.matches = 0), - (this.insert = 0), - (this.bi_buf = 0), - (this.bi_valid = 0); - } - var Be = function (t) { - if (!t || !t.state) return we(t, Dt); - (t.total_in = t.total_out = 0), (t.data_type = Gt); - var e = t.state; - return ( - (e.pending = 0), - (e.pending_out = 0), - e.wrap < 0 && (e.wrap = -e.wrap), - (e.status = e.wrap ? ce : he), - (t.adler = 2 === e.wrap ? 0 : 1), - (e.last_flush = It), - Ot(e), - Rt - ); - }, - Ve = function (t) { - var e = Be(t); - return e === Rt && ze(t.state), e; - }, - He = function (t, e) { - return t && t.state - ? 2 !== t.state.wrap - ? Dt - : ((t.state.gzhead = e), Rt) - : Dt; - }, - qe = function (t, e, n, r, i, o) { - if (!t) return Dt; - var a = 1; - if ( - (e === Ut && (e = 6), - r < 0 - ? ((a = 0), (r = -r)) - : r > 15 && ((a = 2), (r -= 16)), - i < 1 || - i > Jt || - n !== Zt || - r < 8 || - r > 15 || - e < 0 || - e > 9 || - o < 0 || - o > qt) - ) - return we(t, Dt); - 8 === r && (r = 9); - var s = new Ue(); - return ( - (t.state = s), - (s.strm = t), - (s.wrap = a), - (s.gzhead = null), - (s.w_bits = r), - (s.w_size = 1 << s.w_bits), - (s.w_mask = s.w_size - 1), - (s.hash_bits = i + 7), - (s.hash_size = 1 << s.hash_bits), - (s.hash_mask = s.hash_size - 1), - (s.hash_shift = ~~( - (s.hash_bits + oe - 1) / - oe - )), - (s.window = new Uint8Array(2 * s.w_size)), - (s.head = new Uint16Array(s.hash_size)), - (s.prev = new Uint16Array(s.w_size)), - (s.lit_bufsize = 1 << (i + 6)), - (s.pending_buf_size = 4 * s.lit_bufsize), - (s.pending_buf = new Uint8Array( - s.pending_buf_size - )), - (s.d_buf = 1 * s.lit_bufsize), - (s.l_buf = 3 * s.lit_bufsize), - (s.level = e), - (s.strategy = o), - (s.method = n), - Ve(t) - ); - }, - We = function (t, e) { - return qe(t, e, Zt, Kt, Xt, Wt); - }, - Ge = function (t, e) { - var n, r; - if (!t || !t.state || e > Nt || e < 0) - return t ? we(t, Dt) : Dt; - var i = t.state; - if ( - !t.output || - (!t.input && 0 !== t.avail_in) || - (i.status === ve && e !== Pt) - ) - return we(t, 0 === t.avail_out ? zt : Dt); - i.strm = t; - var o = i.last_flush; - if (((i.last_flush = e), i.status === ce)) - if (2 === i.wrap) - (t.adler = 0), - Ae(i, 31), - Ae(i, 139), - Ae(i, 8), - i.gzhead - ? (Ae( - i, - (i.gzhead.text ? 1 : 0) + - (i.gzhead.hcrc ? 2 : 0) + - (i.gzhead.extra ? 4 : 0) + - (i.gzhead.name ? 8 : 0) + - (i.gzhead.comment - ? 16 - : 0) - ), - Ae(i, 255 & i.gzhead.time), - Ae(i, (i.gzhead.time >> 8) & 255), - Ae( - i, - (i.gzhead.time >> 16) & 255 - ), - Ae( - i, - (i.gzhead.time >> 24) & 255 - ), - Ae( - i, - 9 === i.level - ? 2 - : i.strategy >= Vt || - i.level < 2 - ? 4 - : 0 - ), - Ae(i, 255 & i.gzhead.os), - i.gzhead.extra && - i.gzhead.extra.length && - (Ae( - i, - 255 & - i.gzhead.extra.length - ), - Ae( - i, - (i.gzhead.extra.length >> - 8) & - 255 - )), - i.gzhead.hcrc && - (t.adler = kt( - t.adler, - i.pending_buf, - i.pending, - 0 - )), - (i.gzindex = 0), - (i.status = fe)) - : (Ae(i, 0), - Ae(i, 0), - Ae(i, 0), - Ae(i, 0), - Ae(i, 0), - Ae( - i, - 9 === i.level - ? 2 - : i.strategy >= Vt || - i.level < 2 - ? 4 - : 0 - ), - Ae(i, _e), - (i.status = he)); - else { - var a = (Zt + ((i.w_bits - 8) << 4)) << 8, - s = -1; - (s = - i.strategy >= Vt || i.level < 2 - ? 0 - : i.level < 6 - ? 1 - : 6 === i.level - ? 2 - : 3), - (a |= s << 6), - 0 !== i.strstart && (a |= ue), - (a += 31 - (a % 31)), - (i.status = he), - Ce(i, a), - 0 !== i.strstart && - (Ce(i, t.adler >>> 16), - Ce(i, 65535 & t.adler)), - (t.adler = 1); - } - if (i.status === fe) - if (i.gzhead.extra) { - n = i.pending; - while ( - i.gzindex < - (65535 & i.gzhead.extra.length) - ) { - if ( - i.pending === i.pending_buf_size && - (i.gzhead.hcrc && - i.pending > n && - (t.adler = kt( - t.adler, - i.pending_buf, - i.pending - n, - n - )), - Oe(t), - (n = i.pending), - i.pending === i.pending_buf_size) - ) - break; - Ae(i, 255 & i.gzhead.extra[i.gzindex]), - i.gzindex++; - } - i.gzhead.hcrc && - i.pending > n && - (t.adler = kt( - t.adler, - i.pending_buf, - i.pending - n, - n - )), - i.gzindex === i.gzhead.extra.length && - ((i.gzindex = 0), (i.status = le)); - } else i.status = le; - if (i.status === le) - if (i.gzhead.name) { - n = i.pending; - do { - if ( - i.pending === i.pending_buf_size && - (i.gzhead.hcrc && - i.pending > n && - (t.adler = kt( - t.adler, - i.pending_buf, - i.pending - n, - n - )), - Oe(t), - (n = i.pending), - i.pending === i.pending_buf_size) - ) { - r = 1; - break; - } - (r = - i.gzindex < i.gzhead.name.length - ? 255 & - i.gzhead.name.charCodeAt( - i.gzindex++ - ) - : 0), - Ae(i, r); - } while (0 !== r); - i.gzhead.hcrc && - i.pending > n && - (t.adler = kt( - t.adler, - i.pending_buf, - i.pending - n, - n - )), - 0 === r && - ((i.gzindex = 0), (i.status = de)); - } else i.status = de; - if (i.status === de) - if (i.gzhead.comment) { - n = i.pending; - do { - if ( - i.pending === i.pending_buf_size && - (i.gzhead.hcrc && - i.pending > n && - (t.adler = kt( - t.adler, - i.pending_buf, - i.pending - n, - n - )), - Oe(t), - (n = i.pending), - i.pending === i.pending_buf_size) - ) { - r = 1; - break; - } - (r = - i.gzindex < i.gzhead.comment.length - ? 255 & - i.gzhead.comment.charCodeAt( - i.gzindex++ - ) - : 0), - Ae(i, r); - } while (0 !== r); - i.gzhead.hcrc && - i.pending > n && - (t.adler = kt( - t.adler, - i.pending_buf, - i.pending - n, - n - )), - 0 === r && (i.status = pe); - } else i.status = pe; - if ( - (i.status === pe && - (i.gzhead.hcrc - ? (i.pending + 2 > i.pending_buf_size && - Oe(t), - i.pending + 2 <= i.pending_buf_size && - (Ae(i, 255 & t.adler), - Ae(i, (t.adler >> 8) & 255), - (t.adler = 0), - (i.status = he))) - : (i.status = he)), - 0 !== i.pending) - ) { - if ((Oe(t), 0 === t.avail_out)) - return (i.last_flush = -1), Rt; - } else if ( - 0 === t.avail_in && - xe(e) <= xe(o) && - e !== Pt - ) - return we(t, zt); - if (i.status === ve && 0 !== t.avail_in) - return we(t, zt); - if ( - 0 !== t.avail_in || - 0 !== i.lookahead || - (e !== It && i.status !== ve) - ) { - var u = - i.strategy === Vt - ? $e(i, e) - : i.strategy === Ht - ? Re(i, e) - : Fe[i.level].func(i, e); - if ( - ((u !== ge && u !== be) || (i.status = ve), - u === ye || u === ge) - ) - return ( - 0 === t.avail_out && - (i.last_flush = -1), - Rt - ); - if ( - u === me && - (e === Mt - ? jt(i) - : e !== Nt && - (Tt(i, 0, 0, !1), - e === Lt && - (ke(i.head), - 0 === i.lookahead && - ((i.strstart = 0), - (i.block_start = 0), - (i.insert = 0)))), - Oe(t), - 0 === t.avail_out) - ) - return (i.last_flush = -1), Rt; - } - return e !== Pt - ? Rt - : i.wrap <= 0 - ? $t - : (2 === i.wrap - ? (Ae(i, 255 & t.adler), - Ae(i, (t.adler >> 8) & 255), - Ae(i, (t.adler >> 16) & 255), - Ae(i, (t.adler >> 24) & 255), - Ae(i, 255 & t.total_in), - Ae(i, (t.total_in >> 8) & 255), - Ae(i, (t.total_in >> 16) & 255), - Ae(i, (t.total_in >> 24) & 255)) - : (Ce(i, t.adler >>> 16), - Ce(i, 65535 & t.adler)), - Oe(t), - i.wrap > 0 && (i.wrap = -i.wrap), - 0 !== i.pending ? Rt : $t); - }, - Ze = function (t) { - if (!t || !t.state) return Dt; - var e = t.state.status; - return e !== ce && - e !== fe && - e !== le && - e !== de && - e !== pe && - e !== he && - e !== ve - ? we(t, Dt) - : ((t.state = null), e === he ? we(t, Ft) : Rt); - }, - Je = function (t, e) { - var n = e.length; - if (!t || !t.state) return Dt; - var r = t.state, - i = r.wrap; - if ( - 2 === i || - (1 === i && r.status !== ce) || - r.lookahead - ) - return Dt; - if ( - (1 === i && (t.adler = bt(t.adler, e, n, 0)), - (r.wrap = 0), - n >= r.w_size) - ) { - 0 === i && - (ke(r.head), - (r.strstart = 0), - (r.block_start = 0), - (r.insert = 0)); - var o = new Uint8Array(r.w_size); - o.set(e.subarray(n - r.w_size, n), 0), - (e = o), - (n = r.w_size); - } - var a = t.avail_in, - s = t.next_in, - u = t.input; - (t.avail_in = n), - (t.next_in = 0), - (t.input = e), - Me(r); - while (r.lookahead >= oe) { - var c = r.strstart, - f = r.lookahead - (oe - 1); - do { - (r.ins_h = Ee( - r, - r.ins_h, - r.window[c + oe - 1] - )), - (r.prev[c & r.w_mask] = - r.head[r.ins_h]), - (r.head[r.ins_h] = c), - c++; - } while (--f); - (r.strstart = c), (r.lookahead = oe - 1), Me(r); - } - return ( - (r.strstart += r.lookahead), - (r.block_start = r.strstart), - (r.insert = r.lookahead), - (r.lookahead = 0), - (r.match_length = r.prev_length = oe - 1), - (r.match_available = 0), - (t.next_in = s), - (t.input = u), - (t.avail_in = a), - (r.wrap = i), - Rt - ); - }, - Ke = We, - Xe = qe, - Ye = Ve, - Qe = Be, - tn = He, - en = Ge, - nn = Ze, - rn = Je, - on = "pako deflate (from Nodeca project)", - an = { - deflateInit: Ke, - deflateInit2: Xe, - deflateReset: Ye, - deflateResetKeep: Qe, - deflateSetHeader: tn, - deflate: en, - deflateEnd: nn, - deflateSetDictionary: rn, - deflateInfo: on, - }; - function sn(t) { - for (var e = 0, n = 0, r = t.length; n < r; n++) - e += t[n].length; - for ( - var i = new Uint8Array(e), - o = 0, - a = 0, - s = t.length; - o < s; - o++ - ) { - var u = t[o]; - i.set(u, a), (a += u.length); - } - return i; - } - for (var un = new Uint8Array(256), cn = 0; cn < 256; cn++) - un[cn] = - cn >= 252 - ? 6 - : cn >= 248 - ? 5 - : cn >= 240 - ? 4 - : cn >= 224 - ? 3 - : cn >= 192 - ? 2 - : 1; - function fn() { - (this.input = null), - (this.next_in = 0), - (this.avail_in = 0), - (this.total_in = 0), - (this.output = null), - (this.next_out = 0), - (this.avail_out = 0), - (this.total_out = 0), - (this.msg = ""), - (this.state = null), - (this.data_type = 2), - (this.adler = 0); - } - un[254] = un[254] = 1; - var ln = fn, - dn = Object.prototype.toString, - pn = Et.Z_NO_FLUSH, - hn = Et.Z_SYNC_FLUSH, - vn = Et.Z_FULL_FLUSH, - yn = Et.Z_FINISH, - mn = Et.Z_OK, - gn = Et.Z_STREAM_END, - bn = Et.Z_DEFAULT_COMPRESSION, - _n = Et.Z_DEFAULT_STRATEGY, - wn = Et.Z_DEFLATED; - function xn() { - this.options = { - level: bn, - method: wn, - chunkSize: 16384, - windowBits: 15, - memLevel: 8, - strategy: _n, - }; - var t = this.options; - t.raw && t.windowBits > 0 - ? (t.windowBits = -t.windowBits) - : t.gzip && - t.windowBits > 0 && - t.windowBits < 16 && - (t.windowBits += 16), - (this.err = 0), - (this.msg = ""), - (this.ended = !1), - (this.chunks = []), - (this.strm = new ln()), - (this.strm.avail_out = 0); - var e = an.deflateInit2( - this.strm, - t.level, - t.method, - t.windowBits, - t.memLevel, - t.strategy - ); - if (e !== mn) throw new Error(St[e]); - if ( - (t.header && - an.deflateSetHeader(this.strm, t.header), - t.dictionary) - ) { - var n; - if ( - ((n = - "[object ArrayBuffer]" === - dn.call(t.dictionary) - ? new Uint8Array(t.dictionary) - : t.dictionary), - (e = an.deflateSetDictionary(this.strm, n)), - e !== mn) - ) - throw new Error(St[e]); - this._dict_set = !0; - } - } - function kn(t) { - if ( - "function" === typeof TextEncoder && - TextEncoder.prototype.encode - ) - return new TextEncoder().encode(t); - var e, - n, - r, - i, - o, - a = t.length, - s = 0; - for (i = 0; i < a; i++) - (n = t.charCodeAt(i)), - 55296 === (64512 & n) && - i + 1 < a && - ((r = t.charCodeAt(i + 1)), - 56320 === (64512 & r) && - ((n = - 65536 + - ((n - 55296) << 10) + - (r - 56320)), - i++)), - (s += - n < 128 - ? 1 - : n < 2048 - ? 2 - : n < 65536 - ? 3 - : 4); - for (e = new Uint8Array(s), o = 0, i = 0; o < s; i++) - (n = t.charCodeAt(i)), - 55296 === (64512 & n) && - i + 1 < a && - ((r = t.charCodeAt(i + 1)), - 56320 === (64512 & r) && - ((n = - 65536 + - ((n - 55296) << 10) + - (r - 56320)), - i++)), - n < 128 - ? (e[o++] = n) - : n < 2048 - ? ((e[o++] = 192 | (n >>> 6)), - (e[o++] = 128 | (63 & n))) - : n < 65536 - ? ((e[o++] = 224 | (n >>> 12)), - (e[o++] = 128 | ((n >>> 6) & 63)), - (e[o++] = 128 | (63 & n))) - : ((e[o++] = 240 | (n >>> 18)), - (e[o++] = 128 | ((n >>> 12) & 63)), - (e[o++] = 128 | ((n >>> 6) & 63)), - (e[o++] = 128 | (63 & n))); - return e; - } - return ( - (xn.prototype.push = function (t, e) { - var n, - r, - i = this.strm, - o = this.options.chunkSize; - if (this.ended) return !1; - for ( - r = e === ~~e ? e : !0 === e ? yn : pn, - "[object ArrayBuffer]" === dn.call(t) - ? (i.input = new Uint8Array(t)) - : (i.input = t), - i.next_in = 0, - i.avail_in = i.input.length; - ; - - ) - if ( - (0 === i.avail_out && - ((i.output = new Uint8Array(o)), - (i.next_out = 0), - (i.avail_out = o)), - (r === hn || r === vn) && i.avail_out <= 6) - ) - this.onData( - i.output.subarray(0, i.next_out) - ), - (i.avail_out = 0); - else { - if (((n = an.deflate(i, r)), n === gn)) - return ( - i.next_out > 0 && - this.onData( - i.output.subarray( - 0, - i.next_out - ) - ), - (n = an.deflateEnd(this.strm)), - this.onEnd(n), - (this.ended = !0), - n === mn - ); - if (0 !== i.avail_out) { - if (r > 0 && i.next_out > 0) - this.onData( - i.output.subarray(0, i.next_out) - ), - (i.avail_out = 0); - else if (0 === i.avail_in) break; - } else this.onData(i.output); - } - return !0; - }), - (xn.prototype.onData = function (t) { - this.chunks.push(t); - }), - (xn.prototype.onEnd = function (t) { - t === mn && (this.result = sn(this.chunks)), - (this.chunks = []), - (this.err = t), - (this.msg = this.strm.msg); - }), - { Deflate: xn, constants: Et, string2buf: kn } - ); - } - t(function () { - var n = e(), - r = n.Deflate, - i = n.constants, - o = n.string2buf, - a = new r(), - s = 0; - function u(t) { - var e = o(t); - return ( - a.push(e, i.Z_SYNC_FLUSH), (s += e.length), e.length - ); - } - self.addEventListener( - "message", - t(function (t) { - var e = t.data; - switch (e.action) { - case "init": - self.postMessage({ type: "initialized" }); - break; - case "write": - var n = u(e.data); - self.postMessage({ - type: "wrote", - id: e.id, - compressedSize: a.chunks.reduce( - function (t, e) { - return t + e.length; - }, - 0 - ), - additionalRawSize: n, - }); - break; - case "flush": - n = e.data ? u(e.data) : 0; - a.push("", i.Z_FINISH), - self.postMessage({ - type: "flushed", - id: e.id, - result: a.result, - additionalRawSize: n, - rawSize: s, - }), - (a = new r()), - (s = 0); - break; - } - }) - ); - })(); - } - var iu = { status: 0 }; - function ou(t, e) { - switch ((void 0 === e && (e = nu), iu.status)) { - case 0: - (iu = { status: 1, callbacks: [t] }), au(e); - break; - case 1: - iu.callbacks.push(t); - break; - case 2: - t(); - break; - case 3: - t(iu.worker); - break; - } - } - function au(t) { - void 0 === t && (t = nu); - try { - var e = t(); - return ( - e.addEventListener("error", Tt(uu)), - e.addEventListener( - "message", - Tt(function (t) { - var n = t.data; - "errored" === n.type - ? uu(n.error) - : "initialized" === n.type && su(e); - }) - ), - e.postMessage({ action: "init" }), - e - ); - } catch (n) { - uu(n); - } - } - function su(t) { - 1 === iu.status && - (iu.callbacks.forEach(function (e) { - return e(t); - }), - (iu = { status: 3, worker: t })); - } - function uu(t) { - 1 === iu.status - ? (r.error( - "Session Replay recording failed to start: an error occurred while creating the Worker:", - t - ), - t instanceof Event || - (t instanceof Error && - j(t.message, "Content Security Policy")) - ? r.error( - "Please make sure CSP is correctly configured https://docs.datadoghq.com/real_user_monitoring/faq/content_security_policy" - ) - : jt(t), - iu.callbacks.forEach(function (t) { - return t(); - }), - (iu = { status: 2 })) - : jt(t); - } - function cu(t, e, n, r, i) { - var o = Qs( - t, - e.applicationId, - n, - r, - function (t, n, r, i) { - return $s( - e.sessionReplayEndpointBuilder, - t, - n, - r, - i - ); - }, - i - ), - a = o.addRecord, - s = o.stop, - u = Ps({ - emit: a, - defaultPrivacyLevel: e.defaultPrivacyLevel, - }), - c = u.stop, - f = u.takeFullSnapshot, - l = u.flushMutations, - d = t.subscribe(4, function () { - l(), a({ timestamp: pt(), type: Qo.ViewEnd }); - }).unsubscribe, - p = t.subscribe(2, function (t) { - f(t.startClocks.timeStamp); - }).unsubscribe; - return { - stop: function () { - d(), p(), c(), s(); - }, - }; - } - function fu(t, e) { - if ((void 0 === e && (e = ou), Vt())) - return { - start: T, - stop: T, - getReplayStats: function () {}, - onRumStart: T, - isRecording: function () { - return !1; - }, - }; - var n = { status: 0 }, - r = function () { - n = { status: 1 }; - }, - i = function () { - n = { status: 0 }; - }; - return { - start: function () { - return r(); - }, - stop: function () { - return i(); - }, - getReplayStats: qs, - onRumStart: function (o, a, s, u) { - o.subscribe(7, function () { - (2 !== n.status && 3 !== n.status) || - (i(), (n = { status: 1 })); - }), - o.subscribe(8, function () { - 1 === n.status && r(); - }), - (r = function () { - var r = s.findTrackedSession(); - r && r.hasReplayPlan - ? 2 !== n.status && - 3 !== n.status && - ((n = { status: 2 }), - G("interactive", function () { - 2 === n.status && - e(function (e) { - if (2 === n.status) - if (e) { - var r = t( - o, - a, - s, - u, - e - ).stop; - n = { - status: 3, - stopRecording: r, - }; - } else n = { status: 0 }; - }); - })) - : (n = { status: 1 }); - }), - (i = function () { - 0 !== n.status && - (3 === n.status && n.stopRecording(), - (n = { status: 0 })); - }), - 1 === n.status && r(); - }, - isRecording: function () { - return 3 === n.status; - }, - }; - } - n.d(e, "a", function () { - return du; - }); - var lu = fu(cu), - du = Oe(Ko, lu); - Dt(z(), "DD_RUM", du); - }, - "0d58": function (t, e, n) { - var r = n("ce10"), - i = n("e11e"); - t.exports = - Object.keys || - function (t) { - return r(t, i); - }; - }, - "0f89": function (t, e, n) { - var r = n("6f8a"); - t.exports = function (t) { - if (!r(t)) throw TypeError(t + " is not an object!"); - return t; - }; - }, - "103a": function (t, e, n) { - var r = n("da3c").document; - t.exports = r && r.documentElement; - }, - "111f": function (t, e, n) { - "use strict"; - var r = n("d13f"); - t.exports = function (t) { - r(r.S, t, { - of: function () { - var t = arguments.length, - e = new Array(t); - while (t--) e[t] = arguments[t]; - return new this(e); - }, - }); - }; - }, - 1169: function (t, e, n) { - var r = n("2d95"); - t.exports = - Array.isArray || - function (t) { - return "Array" == r(t); - }; - }, - "11c1": function (t, e, n) { - var r = n("c437"), - i = n("c64e"), - o = i; - (o.v1 = r), (o.v4 = i), (t.exports = o); - }, - "11e9": function (t, e, n) { - var r = n("52a7"), - i = n("4630"), - o = n("6821"), - a = n("6a99"), - s = n("69a8"), - u = n("c69a"), - c = Object.getOwnPropertyDescriptor; - e.f = n("9e1e") - ? c - : function (t, e) { - if (((t = o(t)), (e = a(e, !0)), u)) - try { - return c(t, e); - } catch (n) {} - if (s(t, e)) return i(!r.f.call(t, e), t[e]); - }; - }, - "12fd": function (t, e, n) { - var r = n("6f8a"), - i = n("da3c").document, - o = r(i) && r(i.createElement); - t.exports = function (t) { - return o ? i.createElement(t) : {}; - }; - }, - "12fd9": function (t, e) {}, - "1331e": function (t, e, n) { - "use strict"; - Object.defineProperty(e, "__esModule", { value: !0 }), - (e.default = void 0); - var r = n("78ef"), - i = (0, r.regex)("integer", /^-?[0-9]*$/); - e.default = i; - }, - 1495: function (t, e, n) { - var r = n("86cc"), - i = n("cb7c"), - o = n("0d58"); - t.exports = n("9e1e") - ? Object.defineProperties - : function (t, e) { - i(t); - var n, - a = o(e), - s = a.length, - u = 0; - while (s > u) r.f(t, (n = a[u++]), e[n]); - return t; - }; - }, - 1938: function (t, e, n) { - var r = n("d13f"); - r(r.S, "Array", { isArray: n("b5aa") }); - }, - "196c": function (t, e) { - t.exports = function (t, e, n) { - var r = void 0 === n; - switch (e.length) { - case 0: - return r ? t() : t.call(n); - case 1: - return r ? t(e[0]) : t.call(n, e[0]); - case 2: - return r ? t(e[0], e[1]) : t.call(n, e[0], e[1]); - case 3: - return r - ? t(e[0], e[1], e[2]) - : t.call(n, e[0], e[1], e[2]); - case 4: - return r - ? t(e[0], e[1], e[2], e[3]) - : t.call(n, e[0], e[1], e[2], e[3]); - } - return t.apply(n, e); - }; - }, - 1991: function (t, e, n) { - var r, - i, - o, - a = n("9b43"), - s = n("31f4"), - u = n("fab2"), - c = n("230e"), - f = n("7726"), - l = f.process, - d = f.setImmediate, - p = f.clearImmediate, - h = f.MessageChannel, - v = f.Dispatch, - y = 0, - m = {}, - g = "onreadystatechange", - b = function () { - var t = +this; - if (m.hasOwnProperty(t)) { - var e = m[t]; - delete m[t], e(); - } - }, - _ = function (t) { - b.call(t.data); - }; - (d && p) || - ((d = function (t) { - var e = [], - n = 1; - while (arguments.length > n) e.push(arguments[n++]); - return ( - (m[++y] = function () { - s("function" == typeof t ? t : Function(t), e); - }), - r(y), - y - ); - }), - (p = function (t) { - delete m[t]; - }), - "process" == n("2d95")(l) - ? (r = function (t) { - l.nextTick(a(b, t, 1)); - }) - : v && v.now - ? (r = function (t) { - v.now(a(b, t, 1)); - }) - : h - ? ((i = new h()), - (o = i.port2), - (i.port1.onmessage = _), - (r = a(o.postMessage, o, 1))) - : f.addEventListener && - "function" == typeof postMessage && - !f.importScripts - ? ((r = function (t) { - f.postMessage(t + "", "*"); - }), - f.addEventListener("message", _, !1)) - : (r = - g in c("script") - ? function (t) { - u.appendChild(c("script"))[g] = - function () { - u.removeChild(this), b.call(t); - }; - } - : function (t) { - setTimeout(a(b, t, 1), 0); - })), - (t.exports = { set: d, clear: p }); - }, - "1b55": function (t, e, n) { - var r = n("7772")("wks"), - i = n("7b00"), - o = n("da3c").Symbol, - a = "function" == typeof o, - s = (t.exports = function (t) { - return ( - r[t] || - (r[t] = (a && o[t]) || (a ? o : i)("Symbol." + t)) - ); - }); - s.store = r; - }, - "1b8f": function (t, e, n) { - var r = n("a812"), - i = Math.max, - o = Math.min; - t.exports = function (t, e) { - return (t = r(t)), t < 0 ? i(t + e, 0) : o(t, e); - }; - }, - "1be4": function (t, e, n) { - "use strict"; - var r = n("da3c"), - i = n("a7d3"), - o = n("3adc"), - a = n("7d95"), - s = n("1b55")("species"); - t.exports = function (t) { - var e = "function" == typeof i[t] ? i[t] : r[t]; - a && - e && - !e[s] && - o.f(e, s, { - configurable: !0, - get: function () { - return this; - }, - }); - }; - }, - "1dce": function (t, e, n) { - "use strict"; - Object.defineProperty(e, "__esModule", { value: !0 }), - (e.Vuelidate = I), - Object.defineProperty(e, "withParams", { - enumerable: !0, - get: function () { - return i.withParams; - }, - }), - (e.default = e.validationMixin = void 0); - var r = n("fbf4"), - i = n("0234"); - function o(t) { - return u(t) || s(t) || a(); - } - function a() { - throw new TypeError( - "Invalid attempt to spread non-iterable instance" - ); - } - function s(t) { - if ( - Symbol.iterator in Object(t) || - "[object Arguments]" === Object.prototype.toString.call(t) - ) - return Array.from(t); - } - function u(t) { - if (Array.isArray(t)) { - for (var e = 0, n = new Array(t.length); e < t.length; e++) - n[e] = t[e]; - return n; - } - } - function c(t) { - for (var e = 1; e < arguments.length; e++) { - var n = null != arguments[e] ? arguments[e] : {}, - r = Object.keys(n); - "function" === typeof Object.getOwnPropertySymbols && - (r = r.concat( - Object.getOwnPropertySymbols(n).filter(function ( - t - ) { - return Object.getOwnPropertyDescriptor(n, t) - .enumerable; - }) - )), - r.forEach(function (e) { - f(t, e, n[e]); - }); - } - return t; - } - function f(t, e, n) { - return ( - e in t - ? Object.defineProperty(t, e, { - value: n, - enumerable: !0, - configurable: !0, - writable: !0, - }) - : (t[e] = n), - t - ); - } - function l(t) { - return ( - (l = - "function" === typeof Symbol && - "symbol" === typeof Symbol.iterator - ? function (t) { - return typeof t; - } - : function (t) { - return t && - "function" === typeof Symbol && - t.constructor === Symbol && - t !== Symbol.prototype - ? "symbol" - : typeof t; - }), - l(t) - ); - } - var d = function () { - return null; - }, - p = function (t, e, n) { - return t.reduce(function (t, r) { - return (t[n ? n(r) : r] = e(r)), t; - }, {}); - }; - function h(t) { - return "function" === typeof t; - } - function v(t) { - return null !== t && ("object" === l(t) || h(t)); - } - function y(t) { - return v(t) && h(t.then); - } - var m = function (t, e, n, r) { - if ("function" === typeof n) return n.call(t, e, r); - n = Array.isArray(n) ? n : n.split("."); - for (var i = 0; i < n.length; i++) { - if (!e || "object" !== l(e)) return r; - e = e[n[i]]; - } - return "undefined" === typeof e ? r : e; - }, - g = "__isVuelidateAsyncVm"; - function b(t, e) { - var n = new t({ data: { p: !0, v: !1 } }); - return ( - e.then( - function (t) { - (n.p = !1), (n.v = t); - }, - function (t) { - throw ((n.p = !1), (n.v = !1), t); - } - ), - (n[g] = !0), - n - ); - } - var _ = { - $invalid: function () { - var t = this, - e = this.proxy; - return ( - this.nestedKeys.some(function (e) { - return t.refProxy(e).$invalid; - }) || - this.ruleKeys.some(function (t) { - return !e[t]; - }) - ); - }, - $dirty: function () { - var t = this; - return ( - !!this.dirty || - (0 !== this.nestedKeys.length && - this.nestedKeys.every(function (e) { - return t.refProxy(e).$dirty; - })) - ); - }, - $anyDirty: function () { - var t = this; - return ( - !!this.dirty || - (0 !== this.nestedKeys.length && - this.nestedKeys.some(function (e) { - return t.refProxy(e).$anyDirty; - })) - ); - }, - $error: function () { - return this.$dirty && !this.$pending && this.$invalid; - }, - $anyError: function () { - return this.$anyDirty && !this.$pending && this.$invalid; - }, - $pending: function () { - var t = this; - return ( - this.ruleKeys.some(function (e) { - return t.getRef(e).$pending; - }) || - this.nestedKeys.some(function (e) { - return t.refProxy(e).$pending; - }) - ); - }, - $params: function () { - var t = this, - e = this.validations; - return c( - {}, - p(this.nestedKeys, function (t) { - return (e[t] && e[t].$params) || null; - }), - p(this.ruleKeys, function (e) { - return t.getRef(e).$params; - }) - ); - }, - }; - function w(t) { - this.dirty = t; - var e = this.proxy, - n = t ? "$touch" : "$reset"; - this.nestedKeys.forEach(function (t) { - e[t][n](); - }); - } - var x = { - $touch: function () { - w.call(this, !0); - }, - $reset: function () { - w.call(this, !1); - }, - $flattenParams: function () { - var t = this.proxy, - e = []; - for (var n in this.$params) - if (this.isNested(n)) { - for ( - var r = t[n].$flattenParams(), i = 0; - i < r.length; - i++ - ) - r[i].path.unshift(n); - e = e.concat(r); - } else - e.push({ - path: [], - name: n, - params: this.$params[n], - }); - return e; - }, - }, - k = Object.keys(_), - S = Object.keys(x), - E = null, - O = function (t) { - if (E) return E; - var e = t.extend({ - computed: { - refs: function () { - var t = this._vval; - (this._vval = this.children), - (0, r.patchChildren)(t, this._vval); - var e = {}; - return ( - this._vval.forEach(function (t) { - e[t.key] = t.vm; - }), - e - ); - }, - }, - beforeCreate: function () { - this._vval = null; - }, - beforeDestroy: function () { - this._vval && - ((0, r.patchChildren)(this._vval), - (this._vval = null)); - }, - methods: { - getModel: function () { - return this.lazyModel - ? this.lazyModel(this.prop) - : this.model; - }, - getModelKey: function (t) { - var e = this.getModel(); - if (e) return e[t]; - }, - hasIter: function () { - return !1; - }, - }, - }), - n = e.extend({ - data: function () { - return { - rule: null, - lazyModel: null, - model: null, - lazyParentModel: null, - rootModel: null, - }; - }, - methods: { - runRule: function (e) { - var n = this.getModel(); - (0, i.pushParams)(); - var r = this.rule.call( - this.rootModel, - n, - e - ), - o = y(r) ? b(t, r) : r, - a = (0, i.popParams)(), - s = - a && a.$sub - ? a.$sub.length > 1 - ? a - : a.$sub[0] - : null; - return { output: o, params: s }; - }, - }, - computed: { - run: function () { - var t = this, - e = this.lazyParentModel(), - n = Array.isArray(e) && e.__ob__; - if (n) { - var r = e.__ob__.dep; - r.depend(); - var i = r.constructor.target; - if (!this._indirectWatcher) { - var o = i.constructor; - this._indirectWatcher = new o( - this, - function () { - return t.runRule(e); - }, - null, - { lazy: !0 } - ); - } - var a = this.getModel(); - if ( - !this._indirectWatcher.dirty && - this._lastModel === a - ) - return ( - this._indirectWatcher.depend(), - i.value - ); - (this._lastModel = a), - this._indirectWatcher.evaluate(), - this._indirectWatcher.depend(); - } else - this._indirectWatcher && - (this._indirectWatcher.teardown(), - (this._indirectWatcher = null)); - return this._indirectWatcher - ? this._indirectWatcher.value - : this.runRule(e); - }, - $params: function () { - return this.run.params; - }, - proxy: function () { - var t = this.run.output; - return t[g] ? !!t.v : !!t; - }, - $pending: function () { - var t = this.run.output; - return !!t[g] && t.p; - }, - }, - destroyed: function () { - this._indirectWatcher && - (this._indirectWatcher.teardown(), - (this._indirectWatcher = null)); - }, - }), - a = e.extend({ - data: function () { - return { - dirty: !1, - validations: null, - lazyModel: null, - model: null, - prop: null, - lazyParentModel: null, - rootModel: null, - }; - }, - methods: c({}, x, { - refProxy: function (t) { - return this.getRef(t).proxy; - }, - getRef: function (t) { - return this.refs[t]; - }, - isNested: function (t) { - return ( - "function" !== - typeof this.validations[t] - ); - }, - }), - computed: c({}, _, { - nestedKeys: function () { - return this.keys.filter(this.isNested); - }, - ruleKeys: function () { - var t = this; - return this.keys.filter(function (e) { - return !t.isNested(e); - }); - }, - keys: function () { - return Object.keys(this.validations).filter( - function (t) { - return "$params" !== t; - } - ); - }, - proxy: function () { - var t = this, - e = p(this.keys, function (e) { - return { - enumerable: !0, - configurable: !0, - get: function () { - return t.refProxy(e); - }, - }; - }), - n = p(k, function (e) { - return { - enumerable: !0, - configurable: !0, - get: function () { - return t[e]; - }, - }; - }), - r = p(S, function (e) { - return { - enumerable: !1, - configurable: !0, - get: function () { - return t[e]; - }, - }; - }), - i = this.hasIter() - ? { - $iter: { - enumerable: !0, - value: Object.defineProperties( - {}, - c({}, e) - ), - }, - } - : {}; - return Object.defineProperties( - {}, - c( - {}, - e, - i, - { - $model: { - enumerable: !0, - get: function () { - var e = - t.lazyParentModel(); - return null != e - ? e[t.prop] - : null; - }, - set: function (e) { - var n = - t.lazyParentModel(); - null != n && - ((n[t.prop] = e), - t.$touch()); - }, - }, - }, - n, - r - ) - ); - }, - children: function () { - var t = this; - return o( - this.nestedKeys.map(function (e) { - return f(t, e); - }) - ) - .concat( - o( - this.ruleKeys.map(function (e) { - return l(t, e); - }) - ) - ) - .filter(Boolean); - }, - }), - }), - s = a.extend({ - methods: { - isNested: function (t) { - return ( - "undefined" !== - typeof this.validations[t]() - ); - }, - getRef: function (t) { - var e = this; - return { - get proxy() { - return e.validations[t]() || !1; - }, - }; - }, - }, - }), - u = a.extend({ - computed: { - keys: function () { - var t = this.getModel(); - return v(t) ? Object.keys(t) : []; - }, - tracker: function () { - var t = this, - e = this.validations.$trackBy; - return e - ? function (n) { - return "".concat( - m( - t.rootModel, - t.getModelKey(n), - e - ) - ); - } - : function (t) { - return "".concat(t); - }; - }, - getModelLazy: function () { - var t = this; - return function () { - return t.getModel(); - }; - }, - children: function () { - var t = this, - e = this.validations, - n = this.getModel(), - i = c({}, e); - delete i["$trackBy"]; - var o = {}; - return this.keys - .map(function (e) { - var s = t.tracker(e); - return o.hasOwnProperty(s) - ? null - : ((o[s] = !0), - (0, r.h)(a, s, { - validations: i, - prop: e, - lazyParentModel: - t.getModelLazy, - model: n[e], - rootModel: t.rootModel, - })); - }) - .filter(Boolean); - }, - }, - methods: { - isNested: function () { - return !0; - }, - getRef: function (t) { - return this.refs[this.tracker(t)]; - }, - hasIter: function () { - return !0; - }, - }, - }), - f = function (t, e) { - if ("$each" === e) - return (0, r.h)(u, e, { - validations: t.validations[e], - lazyParentModel: t.lazyParentModel, - prop: e, - lazyModel: t.getModel, - rootModel: t.rootModel, - }); - var n = t.validations[e]; - if (Array.isArray(n)) { - var i = t.rootModel, - o = p( - n, - function (t) { - return function () { - return m(i, i.$v, t); - }; - }, - function (t) { - return Array.isArray(t) - ? t.join(".") - : t; - } - ); - return (0, r.h)(s, e, { - validations: o, - lazyParentModel: d, - prop: e, - lazyModel: d, - rootModel: i, - }); - } - return (0, r.h)(a, e, { - validations: n, - lazyParentModel: t.getModel, - prop: e, - lazyModel: t.getModelKey, - rootModel: t.rootModel, - }); - }, - l = function (t, e) { - return (0, r.h)(n, e, { - rule: t.validations[e], - lazyParentModel: t.lazyParentModel, - lazyModel: t.getModel, - rootModel: t.rootModel, - }); - }; - return (E = { VBase: e, Validation: a }), E; - }, - T = null; - function A(t) { - if (T) return T; - var e = t.constructor; - while (e.super) e = e.super; - return (T = e), e; - } - var C = function (t, e) { - var n = A(t), - i = O(n), - o = i.Validation, - a = i.VBase, - s = new a({ - computed: { - children: function () { - var n = - "function" === typeof e ? e.call(t) : e; - return [ - (0, r.h)(o, "$v", { - validations: n, - lazyParentModel: d, - prop: "$v", - model: t, - rootModel: t, - }), - ]; - }, - }, - }); - return s; - }, - j = { - data: function () { - var t = this.$options.validations; - return t && (this._vuelidate = C(this, t)), {}; - }, - beforeCreate: function () { - var t = this.$options, - e = t.validations; - e && - (t.computed || (t.computed = {}), - t.computed.$v || - (t.computed.$v = function () { - return this._vuelidate - ? this._vuelidate.refs.$v.proxy - : null; - })); - }, - beforeDestroy: function () { - this._vuelidate && - (this._vuelidate.$destroy(), - (this._vuelidate = null)); - }, - }; - function I(t) { - t.mixin(j); - } - e.validationMixin = j; - var M = I; - e.default = M; - }, - "1ea0": function (t, e, n) { - "use strict"; - var r = n("f2fe"), - i = n("6f8a"), - o = n("196c"), - a = [].slice, - s = {}, - u = function (t, e, n) { - if (!(e in s)) { - for (var r = [], i = 0; i < e; i++) - r[i] = "a[" + i + "]"; - s[e] = Function( - "F,a", - "return new F(" + r.join(",") + ")" - ); - } - return s[e](t, n); - }; - t.exports = - Function.bind || - function (t) { - var e = r(this), - n = a.call(arguments, 1), - s = function () { - var r = n.concat(a.call(arguments)); - return this instanceof s - ? u(e, r.length, r) - : o(e, r, t); - }; - return i(e.prototype) && (s.prototype = e.prototype), s; - }; - }, - "1fa8": function (t, e, n) { - var r = n("cb7c"); - t.exports = function (t, e, n, i) { - try { - return i ? e(r(n)[0], n[1]) : e(n); - } catch (a) { - var o = t["return"]; - throw (void 0 !== o && r(o.call(t)), a); - } - }; - }, - "1fca": function (t, e, n) { - var r = n("6f8a"); - t.exports = function (t, e) { - if (!r(t) || t._t !== e) - throw TypeError( - "Incompatible receiver, " + e + " required!" - ); - return t; - }; - }, - "214f": function (t, e, n) { - "use strict"; - n("b0c5"); - var r = n("2aba"), - i = n("32e9"), - o = n("79e5"), - a = n("be13"), - s = n("2b4c"), - u = n("520a"), - c = s("species"), - f = !o(function () { - var t = /./; - return ( - (t.exec = function () { - var t = []; - return (t.groups = { a: "7" }), t; - }), - "7" !== "".replace(t, "$
    ") - ); - }), - l = (function () { - var t = /(?:)/, - e = t.exec; - t.exec = function () { - return e.apply(this, arguments); - }; - var n = "ab".split(t); - return 2 === n.length && "a" === n[0] && "b" === n[1]; - })(); - t.exports = function (t, e, n) { - var d = s(t), - p = !o(function () { - var e = {}; - return ( - (e[d] = function () { - return 7; - }), - 7 != ""[t](e) - ); - }), - h = p - ? !o(function () { - var e = !1, - n = /a/; - return ( - (n.exec = function () { - return (e = !0), null; - }), - "split" === t && - ((n.constructor = {}), - (n.constructor[c] = function () { - return n; - })), - n[d](""), - !e - ); - }) - : void 0; - if ( - !p || - !h || - ("replace" === t && !f) || - ("split" === t && !l) - ) { - var v = /./[d], - y = n(a, d, ""[t], function (t, e, n, r, i) { - return e.exec === u - ? p && !i - ? { done: !0, value: v.call(e, n, r) } - : { done: !0, value: t.call(n, e, r) } - : { done: !1 }; - }), - m = y[0], - g = y[1]; - r(String.prototype, t, m), - i( - RegExp.prototype, - d, - 2 == e - ? function (t, e) { - return g.call(t, this, e); - } - : function (t) { - return g.call(t, this); - } - ); - } - }; - }, - "230e": function (t, e, n) { - var r = n("d3f4"), - i = n("7726").document, - o = r(i) && r(i.createElement); - t.exports = function (t) { - return o ? i.createElement(t) : {}; - }; - }, - 2312: function (t, e, n) { - t.exports = n("8ce0"); - }, - 2366: function (t, e) { - for (var n = [], r = 0; r < 256; ++r) - n[r] = (r + 256).toString(16).substr(1); - function i(t, e) { - var r = e || 0, - i = n; - return [ - i[t[r++]], - i[t[r++]], - i[t[r++]], - i[t[r++]], - "-", - i[t[r++]], - i[t[r++]], - "-", - i[t[r++]], - i[t[r++]], - "-", - i[t[r++]], - i[t[r++]], - "-", - i[t[r++]], - i[t[r++]], - i[t[r++]], - i[t[r++]], - i[t[r++]], - i[t[r++]], - ].join(""); - } - t.exports = i; - }, - 2397: function (t, e, n) { - var r = n("5ca1"), - i = n("2aeb"), - o = n("d8e8"), - a = n("cb7c"), - s = n("d3f4"), - u = n("79e5"), - c = n("f0c1"), - f = (n("7726").Reflect || {}).construct, - l = u(function () { - function t() {} - return !(f(function () {}, [], t) instanceof t); - }), - d = !u(function () { - f(function () {}); - }); - r(r.S + r.F * (l || d), "Reflect", { - construct: function (t, e) { - o(t), a(e); - var n = arguments.length < 3 ? t : o(arguments[2]); - if (d && !l) return f(t, e, n); - if (t == n) { - switch (e.length) { - case 0: - return new t(); - case 1: - return new t(e[0]); - case 2: - return new t(e[0], e[1]); - case 3: - return new t(e[0], e[1], e[2]); - case 4: - return new t(e[0], e[1], e[2], e[3]); - } - var r = [null]; - return r.push.apply(r, e), new (c.apply(t, r))(); - } - var u = n.prototype, - p = i(s(u) ? u : Object.prototype), - h = Function.apply.call(t, p, e); - return s(h) ? h : p; - }, - }); - }, - "23c6": function (t, e, n) { - var r = n("2d95"), - i = n("2b4c")("toStringTag"), - o = - "Arguments" == - r( - (function () { - return arguments; - })() - ), - a = function (t, e) { - try { - return t[e]; - } catch (n) {} - }; - t.exports = function (t) { - var e, n, s; - return void 0 === t - ? "Undefined" - : null === t - ? "Null" - : "string" == typeof (n = a((e = Object(t)), i)) - ? n - : o - ? r(e) - : "Object" == (s = r(e)) && "function" == typeof e.callee - ? "Arguments" - : s; - }; - }, - 2418: function (t, e, n) { - var r = n("6a9b"), - i = n("a5ab"), - o = n("1b8f"); - t.exports = function (t) { - return function (e, n, a) { - var s, - u = r(e), - c = i(u.length), - f = o(a, c); - if (t && n != n) { - while (c > f) if (((s = u[f++]), s != s)) return !0; - } else - for (; c > f; f++) - if ((t || f in u) && u[f] === n) return t || f || 0; - return !t && -1; - }; - }; - }, - "245b": function (t, e) { - t.exports = function (t, e) { - return { value: e, done: !!t }; - }; - }, - 2498: function (t, e, n) { - var r = n("d13f"), - i = n("7108"), - o = n("f2fe"), - a = n("0f89"), - s = n("6f8a"), - u = n("d782"), - c = n("1ea0"), - f = (n("da3c").Reflect || {}).construct, - l = u(function () { - function t() {} - return !(f(function () {}, [], t) instanceof t); - }), - d = !u(function () { - f(function () {}); - }); - r(r.S + r.F * (l || d), "Reflect", { - construct: function (t, e) { - o(t), a(e); - var n = arguments.length < 3 ? t : o(arguments[2]); - if (d && !l) return f(t, e, n); - if (t == n) { - switch (e.length) { - case 0: - return new t(); - case 1: - return new t(e[0]); - case 2: - return new t(e[0], e[1]); - case 3: - return new t(e[0], e[1], e[2]); - case 4: - return new t(e[0], e[1], e[2], e[3]); - } - var r = [null]; - return r.push.apply(r, e), new (c.apply(t, r))(); - } - var u = n.prototype, - p = i(s(u) ? u : Object.prototype), - h = Function.apply.call(t, p, e); - return s(h) ? h : p; - }, - }); - }, - "261e": function (t, e, n) { - n("012f")("Map"); - }, - 2621: function (t, e) { - e.f = Object.getOwnPropertySymbols; - }, - 2695: function (t, e, n) { - var r = n("43c8"), - i = n("6a9b"), - o = n("2418")(!1), - a = n("5d8f")("IE_PROTO"); - t.exports = function (t, e) { - var n, - s = i(t), - u = 0, - c = []; - for (n in s) n != a && r(s, n) && c.push(n); - while (e.length > u) - r(s, (n = e[u++])) && (~o(c, n) || c.push(n)); - return c; - }; - }, - "27ee": function (t, e, n) { - var r = n("23c6"), - i = n("2b4c")("iterator"), - o = n("84f2"); - t.exports = n("8378").getIteratorMethod = function (t) { - if (void 0 != t) return t[i] || t["@@iterator"] || o[r(t)]; - }; - }, - 2877: function (t, e, n) { - "use strict"; - function r(t, e, n, r, i, o, a, s) { - var u, - c = "function" === typeof t ? t.options : t; - if ( - (e && - ((c.render = e), - (c.staticRenderFns = n), - (c._compiled = !0)), - r && (c.functional = !0), - o && (c._scopeId = "data-v-" + o), - a - ? ((u = function (t) { - (t = - t || - (this.$vnode && this.$vnode.ssrContext) || - (this.parent && - this.parent.$vnode && - this.parent.$vnode.ssrContext)), - t || - "undefined" === - typeof __VUE_SSR_CONTEXT__ || - (t = __VUE_SSR_CONTEXT__), - i && i.call(this, t), - t && - t._registeredComponents && - t._registeredComponents.add(a); - }), - (c._ssrRegister = u)) - : i && - (u = s - ? function () { - i.call( - this, - (c.functional ? this.parent : this) - .$root.$options.shadowRoot - ); - } - : i), - u) - ) - if (c.functional) { - c._injectStyles = u; - var f = c.render; - c.render = function (t, e) { - return u.call(e), f(t, e); - }; - } else { - var l = c.beforeCreate; - c.beforeCreate = l ? [].concat(l, u) : [u]; - } - return { exports: t, options: c }; - } - n.d(e, "a", function () { - return r; - }); - }, - "28a5": function (t, e, n) { - "use strict"; - var r = n("aae3"), - i = n("cb7c"), - o = n("ebd6"), - a = n("0390"), - s = n("9def"), - u = n("5f1b"), - c = n("520a"), - f = n("79e5"), - l = Math.min, - d = [].push, - p = "split", - h = "length", - v = "lastIndex", - y = 4294967295, - m = !f(function () { - RegExp(y, "y"); - }); - n("214f")("split", 2, function (t, e, n, f) { - var g; - return ( - (g = - "c" == "abbc"[p](/(b)*/)[1] || - 4 != "test"[p](/(?:)/, -1)[h] || - 2 != "ab"[p](/(?:ab)*/)[h] || - 4 != "."[p](/(.?)(.?)/)[h] || - "."[p](/()()/)[h] > 1 || - ""[p](/.?/)[h] - ? function (t, e) { - var i = String(this); - if (void 0 === t && 0 === e) return []; - if (!r(t)) return n.call(i, t, e); - var o, - a, - s, - u = [], - f = - (t.ignoreCase ? "i" : "") + - (t.multiline ? "m" : "") + - (t.unicode ? "u" : "") + - (t.sticky ? "y" : ""), - l = 0, - p = void 0 === e ? y : e >>> 0, - m = new RegExp(t.source, f + "g"); - while ((o = c.call(m, i))) { - if ( - ((a = m[v]), - a > l && - (u.push(i.slice(l, o.index)), - o[h] > 1 && - o.index < i[h] && - d.apply(u, o.slice(1)), - (s = o[0][h]), - (l = a), - u[h] >= p)) - ) - break; - m[v] === o.index && m[v]++; - } - return ( - l === i[h] - ? (!s && m.test("")) || u.push("") - : u.push(i.slice(l)), - u[h] > p ? u.slice(0, p) : u - ); - } - : "0"[p](void 0, 0)[h] - ? function (t, e) { - return void 0 === t && 0 === e - ? [] - : n.call(this, t, e); - } - : n), - [ - function (n, r) { - var i = t(this), - o = void 0 == n ? void 0 : n[e]; - return void 0 !== o - ? o.call(n, i, r) - : g.call(String(i), n, r); - }, - function (t, e) { - var r = f(g, t, this, e, g !== n); - if (r.done) return r.value; - var c = i(t), - d = String(this), - p = o(c, RegExp), - h = c.unicode, - v = - (c.ignoreCase ? "i" : "") + - (c.multiline ? "m" : "") + - (c.unicode ? "u" : "") + - (m ? "y" : "g"), - b = new p(m ? c : "^(?:" + c.source + ")", v), - _ = void 0 === e ? y : e >>> 0; - if (0 === _) return []; - if (0 === d.length) - return null === u(b, d) ? [d] : []; - var w = 0, - x = 0, - k = []; - while (x < d.length) { - b.lastIndex = m ? x : 0; - var S, - E = u(b, m ? d : d.slice(x)); - if ( - null === E || - (S = l( - s(b.lastIndex + (m ? 0 : x)), - d.length - )) === w - ) - x = a(d, x, h); - else { - if ((k.push(d.slice(w, x)), k.length === _)) - return k; - for (var O = 1; O <= E.length - 1; O++) - if ((k.push(E[O]), k.length === _)) - return k; - x = w = S; - } - } - return k.push(d.slice(w)), k; - }, - ] - ); - }); - }, - "2a12": function (t, e, n) { - "use strict"; - Object.defineProperty(e, "__esModule", { value: !0 }), - (e.default = void 0); - var r = n("78ef"), - i = function (t) { - return (0, r.withParams)( - { type: "maxLength", max: t }, - function (e) { - return !(0, r.req)(e) || (0, r.len)(e) <= t; - } - ); - }; - e.default = i; - }, - "2a4e": function (t, e, n) { - var r = n("a812"), - i = n("e5fa"); - t.exports = function (t) { - return function (e, n) { - var o, - a, - s = String(i(e)), - u = r(n), - c = s.length; - return u < 0 || u >= c - ? t - ? "" - : void 0 - : ((o = s.charCodeAt(u)), - o < 55296 || - o > 56319 || - u + 1 === c || - (a = s.charCodeAt(u + 1)) < 56320 || - a > 57343 - ? t - ? s.charAt(u) - : o - : t - ? s.slice(u, u + 2) - : a - 56320 + ((o - 55296) << 10) + 65536); - }; - }; - }, - "2aba": function (t, e, n) { - var r = n("7726"), - i = n("32e9"), - o = n("69a8"), - a = n("ca5a")("src"), - s = n("fa5b"), - u = "toString", - c = ("" + s).split(u); - (n("8378").inspectSource = function (t) { - return s.call(t); - }), - (t.exports = function (t, e, n, s) { - var u = "function" == typeof n; - u && (o(n, "name") || i(n, "name", e)), - t[e] !== n && - (u && - (o(n, a) || - i( - n, - a, - t[e] ? "" + t[e] : c.join(String(e)) - )), - t === r - ? (t[e] = n) - : s - ? t[e] - ? (t[e] = n) - : i(t, e, n) - : (delete t[e], i(t, e, n))); - })(Function.prototype, u, function () { - return ( - ("function" == typeof this && this[a]) || s.call(this) - ); - }); - }, - "2aeb": function (t, e, n) { - var r = n("cb7c"), - i = n("1495"), - o = n("e11e"), - a = n("613b")("IE_PROTO"), - s = function () {}, - u = "prototype", - c = function () { - var t, - e = n("230e")("iframe"), - r = o.length, - i = "<", - a = ">"; - (e.style.display = "none"), - n("fab2").appendChild(e), - (e.src = "javascript:"), - (t = e.contentWindow.document), - t.open(), - t.write( - i + - "script" + - a + - "document.F=Object" + - i + - "/script" + - a - ), - t.close(), - (c = t.F); - while (r--) delete c[u][o[r]]; - return c(); - }; - t.exports = - Object.create || - function (t, e) { - var n; - return ( - null !== t - ? ((s[u] = r(t)), - (n = new s()), - (s[u] = null), - (n[a] = t)) - : (n = c()), - void 0 === e ? n : i(n, e) - ); - }; - }, - "2b0e": function (t, e, n) { - "use strict"; - (function (t) { - /*! - * Vue.js v2.6.14 - * (c) 2014-2021 Evan You - * Released under the MIT License. - */ - var n = Object.freeze({}); - function r(t) { - return void 0 === t || null === t; - } - function i(t) { - return void 0 !== t && null !== t; - } - function o(t) { - return !0 === t; - } - function a(t) { - return !1 === t; - } - function s(t) { - return ( - "string" === typeof t || - "number" === typeof t || - "symbol" === typeof t || - "boolean" === typeof t - ); - } - function u(t) { - return null !== t && "object" === typeof t; - } - var c = Object.prototype.toString; - function f(t) { - return "[object Object]" === c.call(t); - } - function l(t) { - return "[object RegExp]" === c.call(t); - } - function d(t) { - var e = parseFloat(String(t)); - return e >= 0 && Math.floor(e) === e && isFinite(t); - } - function p(t) { - return ( - i(t) && - "function" === typeof t.then && - "function" === typeof t.catch - ); - } - function h(t) { - return null == t - ? "" - : Array.isArray(t) || (f(t) && t.toString === c) - ? JSON.stringify(t, null, 2) - : String(t); - } - function v(t) { - var e = parseFloat(t); - return isNaN(e) ? t : e; - } - function y(t, e) { - for ( - var n = Object.create(null), r = t.split(","), i = 0; - i < r.length; - i++ - ) - n[r[i]] = !0; - return e - ? function (t) { - return n[t.toLowerCase()]; - } - : function (t) { - return n[t]; - }; - } - y("slot,component", !0); - var m = y("key,ref,slot,slot-scope,is"); - function g(t, e) { - if (t.length) { - var n = t.indexOf(e); - if (n > -1) return t.splice(n, 1); - } - } - var b = Object.prototype.hasOwnProperty; - function _(t, e) { - return b.call(t, e); - } - function w(t) { - var e = Object.create(null); - return function (n) { - var r = e[n]; - return r || (e[n] = t(n)); - }; - } - var x = /-(\w)/g, - k = w(function (t) { - return t.replace(x, function (t, e) { - return e ? e.toUpperCase() : ""; - }); - }), - S = w(function (t) { - return t.charAt(0).toUpperCase() + t.slice(1); - }), - E = /\B([A-Z])/g, - O = w(function (t) { - return t.replace(E, "-$1").toLowerCase(); - }); - function T(t, e) { - function n(n) { - var r = arguments.length; - return r - ? r > 1 - ? t.apply(e, arguments) - : t.call(e, n) - : t.call(e); - } - return (n._length = t.length), n; - } - function A(t, e) { - return t.bind(e); - } - var C = Function.prototype.bind ? A : T; - function j(t, e) { - e = e || 0; - var n = t.length - e, - r = new Array(n); - while (n--) r[n] = t[n + e]; - return r; - } - function I(t, e) { - for (var n in e) t[n] = e[n]; - return t; - } - function M(t) { - for (var e = {}, n = 0; n < t.length; n++) - t[n] && I(e, t[n]); - return e; - } - function L(t, e, n) {} - var P = function (t, e, n) { - return !1; - }, - N = function (t) { - return t; - }; - function R(t, e) { - if (t === e) return !0; - var n = u(t), - r = u(e); - if (!n || !r) return !n && !r && String(t) === String(e); - try { - var i = Array.isArray(t), - o = Array.isArray(e); - if (i && o) - return ( - t.length === e.length && - t.every(function (t, n) { - return R(t, e[n]); - }) - ); - if (t instanceof Date && e instanceof Date) - return t.getTime() === e.getTime(); - if (i || o) return !1; - var a = Object.keys(t), - s = Object.keys(e); - return ( - a.length === s.length && - a.every(function (n) { - return R(t[n], e[n]); - }) - ); - } catch (c) { - return !1; - } - } - function $(t, e) { - for (var n = 0; n < t.length; n++) if (R(t[n], e)) return n; - return -1; - } - function D(t) { - var e = !1; - return function () { - e || ((e = !0), t.apply(this, arguments)); - }; - } - var F = "data-server-rendered", - z = ["component", "directive", "filter"], - U = [ - "beforeCreate", - "created", - "beforeMount", - "mounted", - "beforeUpdate", - "updated", - "beforeDestroy", - "destroyed", - "activated", - "deactivated", - "errorCaptured", - "serverPrefetch", - ], - B = { - optionMergeStrategies: Object.create(null), - silent: !1, - productionTip: !1, - devtools: !1, - performance: !1, - errorHandler: null, - warnHandler: null, - ignoredElements: [], - keyCodes: Object.create(null), - isReservedTag: P, - isReservedAttr: P, - isUnknownElement: P, - getTagNamespace: L, - parsePlatformTagName: N, - mustUseProp: P, - async: !0, - _lifecycleHooks: U, - }, - V = - /a-zA-Z\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD/; - function H(t) { - var e = (t + "").charCodeAt(0); - return 36 === e || 95 === e; - } - function q(t, e, n, r) { - Object.defineProperty(t, e, { - value: n, - enumerable: !!r, - writable: !0, - configurable: !0, - }); - } - var W = new RegExp("[^" + V.source + ".$_\\d]"); - function G(t) { - if (!W.test(t)) { - var e = t.split("."); - return function (t) { - for (var n = 0; n < e.length; n++) { - if (!t) return; - t = t[e[n]]; - } - return t; - }; - } - } - var Z, - J = "__proto__" in {}, - K = "undefined" !== typeof window, - X = - "undefined" !== typeof WXEnvironment && - !!WXEnvironment.platform, - Y = X && WXEnvironment.platform.toLowerCase(), - Q = K && window.navigator.userAgent.toLowerCase(), - tt = Q && /msie|trident/.test(Q), - et = Q && Q.indexOf("msie 9.0") > 0, - nt = Q && Q.indexOf("edge/") > 0, - rt = - (Q && Q.indexOf("android"), - (Q && /iphone|ipad|ipod|ios/.test(Q)) || "ios" === Y), - it = - (Q && /chrome\/\d+/.test(Q), - Q && /phantomjs/.test(Q), - Q && Q.match(/firefox\/(\d+)/)), - ot = {}.watch, - at = !1; - if (K) - try { - var st = {}; - Object.defineProperty(st, "passive", { - get: function () { - at = !0; - }, - }), - window.addEventListener("test-passive", null, st); - } catch (Sa) {} - var ut = function () { - return ( - void 0 === Z && - (Z = - !K && - !X && - "undefined" !== typeof t && - t["process"] && - "server" === t["process"].env.VUE_ENV), - Z - ); - }, - ct = K && window.__VUE_DEVTOOLS_GLOBAL_HOOK__; - function ft(t) { - return ( - "function" === typeof t && - /native code/.test(t.toString()) - ); - } - var lt, - dt = - "undefined" !== typeof Symbol && - ft(Symbol) && - "undefined" !== typeof Reflect && - ft(Reflect.ownKeys); - lt = - "undefined" !== typeof Set && ft(Set) - ? Set - : (function () { - function t() { - this.set = Object.create(null); - } - return ( - (t.prototype.has = function (t) { - return !0 === this.set[t]; - }), - (t.prototype.add = function (t) { - this.set[t] = !0; - }), - (t.prototype.clear = function () { - this.set = Object.create(null); - }), - t - ); - })(); - var pt = L, - ht = 0, - vt = function () { - (this.id = ht++), (this.subs = []); - }; - (vt.prototype.addSub = function (t) { - this.subs.push(t); - }), - (vt.prototype.removeSub = function (t) { - g(this.subs, t); - }), - (vt.prototype.depend = function () { - vt.target && vt.target.addDep(this); - }), - (vt.prototype.notify = function () { - var t = this.subs.slice(); - for (var e = 0, n = t.length; e < n; e++) t[e].update(); - }), - (vt.target = null); - var yt = []; - function mt(t) { - yt.push(t), (vt.target = t); - } - function gt() { - yt.pop(), (vt.target = yt[yt.length - 1]); - } - var bt = function (t, e, n, r, i, o, a, s) { - (this.tag = t), - (this.data = e), - (this.children = n), - (this.text = r), - (this.elm = i), - (this.ns = void 0), - (this.context = o), - (this.fnContext = void 0), - (this.fnOptions = void 0), - (this.fnScopeId = void 0), - (this.key = e && e.key), - (this.componentOptions = a), - (this.componentInstance = void 0), - (this.parent = void 0), - (this.raw = !1), - (this.isStatic = !1), - (this.isRootInsert = !0), - (this.isComment = !1), - (this.isCloned = !1), - (this.isOnce = !1), - (this.asyncFactory = s), - (this.asyncMeta = void 0), - (this.isAsyncPlaceholder = !1); - }, - _t = { child: { configurable: !0 } }; - (_t.child.get = function () { - return this.componentInstance; - }), - Object.defineProperties(bt.prototype, _t); - var wt = function (t) { - void 0 === t && (t = ""); - var e = new bt(); - return (e.text = t), (e.isComment = !0), e; - }; - function xt(t) { - return new bt(void 0, void 0, void 0, String(t)); - } - function kt(t) { - var e = new bt( - t.tag, - t.data, - t.children && t.children.slice(), - t.text, - t.elm, - t.context, - t.componentOptions, - t.asyncFactory - ); - return ( - (e.ns = t.ns), - (e.isStatic = t.isStatic), - (e.key = t.key), - (e.isComment = t.isComment), - (e.fnContext = t.fnContext), - (e.fnOptions = t.fnOptions), - (e.fnScopeId = t.fnScopeId), - (e.asyncMeta = t.asyncMeta), - (e.isCloned = !0), - e - ); - } - var St = Array.prototype, - Et = Object.create(St), - Ot = [ - "push", - "pop", - "shift", - "unshift", - "splice", - "sort", - "reverse", - ]; - Ot.forEach(function (t) { - var e = St[t]; - q(Et, t, function () { - var n = [], - r = arguments.length; - while (r--) n[r] = arguments[r]; - var i, - o = e.apply(this, n), - a = this.__ob__; - switch (t) { - case "push": - case "unshift": - i = n; - break; - case "splice": - i = n.slice(2); - break; - } - return i && a.observeArray(i), a.dep.notify(), o; - }); - }); - var Tt = Object.getOwnPropertyNames(Et), - At = !0; - function Ct(t) { - At = t; - } - var jt = function (t) { - (this.value = t), - (this.dep = new vt()), - (this.vmCount = 0), - q(t, "__ob__", this), - Array.isArray(t) - ? (J ? It(t, Et) : Mt(t, Et, Tt), - this.observeArray(t)) - : this.walk(t); - }; - function It(t, e) { - t.__proto__ = e; - } - function Mt(t, e, n) { - for (var r = 0, i = n.length; r < i; r++) { - var o = n[r]; - q(t, o, e[o]); - } - } - function Lt(t, e) { - var n; - if (u(t) && !(t instanceof bt)) - return ( - _(t, "__ob__") && t.__ob__ instanceof jt - ? (n = t.__ob__) - : At && - !ut() && - (Array.isArray(t) || f(t)) && - Object.isExtensible(t) && - !t._isVue && - (n = new jt(t)), - e && n && n.vmCount++, - n - ); - } - function Pt(t, e, n, r, i) { - var o = new vt(), - a = Object.getOwnPropertyDescriptor(t, e); - if (!a || !1 !== a.configurable) { - var s = a && a.get, - u = a && a.set; - (s && !u) || 2 !== arguments.length || (n = t[e]); - var c = !i && Lt(n); - Object.defineProperty(t, e, { - enumerable: !0, - configurable: !0, - get: function () { - var e = s ? s.call(t) : n; - return ( - vt.target && - (o.depend(), - c && - (c.dep.depend(), - Array.isArray(e) && $t(e))), - e - ); - }, - set: function (e) { - var r = s ? s.call(t) : n; - e === r || - (e !== e && r !== r) || - (s && !u) || - (u ? u.call(t, e) : (n = e), - (c = !i && Lt(e)), - o.notify()); - }, - }); - } - } - function Nt(t, e, n) { - if (Array.isArray(t) && d(e)) - return ( - (t.length = Math.max(t.length, e)), - t.splice(e, 1, n), - n - ); - if (e in t && !(e in Object.prototype)) - return (t[e] = n), n; - var r = t.__ob__; - return t._isVue || (r && r.vmCount) - ? n - : r - ? (Pt(r.value, e, n), r.dep.notify(), n) - : ((t[e] = n), n); - } - function Rt(t, e) { - if (Array.isArray(t) && d(e)) t.splice(e, 1); - else { - var n = t.__ob__; - t._isVue || - (n && n.vmCount) || - (_(t, e) && (delete t[e], n && n.dep.notify())); - } - } - function $t(t) { - for (var e = void 0, n = 0, r = t.length; n < r; n++) - (e = t[n]), - e && e.__ob__ && e.__ob__.dep.depend(), - Array.isArray(e) && $t(e); - } - (jt.prototype.walk = function (t) { - for (var e = Object.keys(t), n = 0; n < e.length; n++) - Pt(t, e[n]); - }), - (jt.prototype.observeArray = function (t) { - for (var e = 0, n = t.length; e < n; e++) Lt(t[e]); - }); - var Dt = B.optionMergeStrategies; - function Ft(t, e) { - if (!e) return t; - for ( - var n, - r, - i, - o = dt ? Reflect.ownKeys(e) : Object.keys(e), - a = 0; - a < o.length; - a++ - ) - (n = o[a]), - "__ob__" !== n && - ((r = t[n]), - (i = e[n]), - _(t, n) - ? r !== i && f(r) && f(i) && Ft(r, i) - : Nt(t, n, i)); - return t; - } - function zt(t, e, n) { - return n - ? function () { - var r = - "function" === typeof e - ? e.call(n, n) - : e, - i = - "function" === typeof t - ? t.call(n, n) - : t; - return r ? Ft(r, i) : i; - } - : e - ? t - ? function () { - return Ft( - "function" === typeof e - ? e.call(this, this) - : e, - "function" === typeof t - ? t.call(this, this) - : t - ); - } - : e - : t; - } - function Ut(t, e) { - var n = e - ? t - ? t.concat(e) - : Array.isArray(e) - ? e - : [e] - : t; - return n ? Bt(n) : n; - } - function Bt(t) { - for (var e = [], n = 0; n < t.length; n++) - -1 === e.indexOf(t[n]) && e.push(t[n]); - return e; - } - function Vt(t, e, n, r) { - var i = Object.create(t || null); - return e ? I(i, e) : i; - } - (Dt.data = function (t, e, n) { - return n - ? zt(t, e, n) - : e && "function" !== typeof e - ? t - : zt(t, e); - }), - U.forEach(function (t) { - Dt[t] = Ut; - }), - z.forEach(function (t) { - Dt[t + "s"] = Vt; - }), - (Dt.watch = function (t, e, n, r) { - if ( - (t === ot && (t = void 0), - e === ot && (e = void 0), - !e) - ) - return Object.create(t || null); - if (!t) return e; - var i = {}; - for (var o in (I(i, t), e)) { - var a = i[o], - s = e[o]; - a && !Array.isArray(a) && (a = [a]), - (i[o] = a - ? a.concat(s) - : Array.isArray(s) - ? s - : [s]); - } - return i; - }), - (Dt.props = - Dt.methods = - Dt.inject = - Dt.computed = - function (t, e, n, r) { - if (!t) return e; - var i = Object.create(null); - return I(i, t), e && I(i, e), i; - }), - (Dt.provide = zt); - var Ht = function (t, e) { - return void 0 === e ? t : e; - }; - function qt(t, e) { - var n = t.props; - if (n) { - var r, - i, - o, - a = {}; - if (Array.isArray(n)) { - r = n.length; - while (r--) - (i = n[r]), - "string" === typeof i && - ((o = k(i)), (a[o] = { type: null })); - } else if (f(n)) - for (var s in n) - (i = n[s]), - (o = k(s)), - (a[o] = f(i) ? i : { type: i }); - else 0; - t.props = a; - } - } - function Wt(t, e) { - var n = t.inject; - if (n) { - var r = (t.inject = {}); - if (Array.isArray(n)) - for (var i = 0; i < n.length; i++) - r[n[i]] = { from: n[i] }; - else if (f(n)) - for (var o in n) { - var a = n[o]; - r[o] = f(a) ? I({ from: o }, a) : { from: a }; - } - else 0; - } - } - function Gt(t) { - var e = t.directives; - if (e) - for (var n in e) { - var r = e[n]; - "function" === typeof r && - (e[n] = { bind: r, update: r }); - } - } - function Zt(t, e, n) { - if ( - ("function" === typeof e && (e = e.options), - qt(e, n), - Wt(e, n), - Gt(e), - !e._base && - (e.extends && (t = Zt(t, e.extends, n)), e.mixins)) - ) - for (var r = 0, i = e.mixins.length; r < i; r++) - t = Zt(t, e.mixins[r], n); - var o, - a = {}; - for (o in t) s(o); - for (o in e) _(t, o) || s(o); - function s(r) { - var i = Dt[r] || Ht; - a[r] = i(t[r], e[r], n, r); - } - return a; - } - function Jt(t, e, n, r) { - if ("string" === typeof n) { - var i = t[e]; - if (_(i, n)) return i[n]; - var o = k(n); - if (_(i, o)) return i[o]; - var a = S(o); - if (_(i, a)) return i[a]; - var s = i[n] || i[o] || i[a]; - return s; - } - } - function Kt(t, e, n, r) { - var i = e[t], - o = !_(n, t), - a = n[t], - s = ee(Boolean, i.type); - if (s > -1) - if (o && !_(i, "default")) a = !1; - else if ("" === a || a === O(t)) { - var u = ee(String, i.type); - (u < 0 || s < u) && (a = !0); - } - if (void 0 === a) { - a = Xt(r, i, t); - var c = At; - Ct(!0), Lt(a), Ct(c); - } - return a; - } - function Xt(t, e, n) { - if (_(e, "default")) { - var r = e.default; - return t && - t.$options.propsData && - void 0 === t.$options.propsData[n] && - void 0 !== t._props[n] - ? t._props[n] - : "function" === typeof r && - "Function" !== Qt(e.type) - ? r.call(t) - : r; - } - } - var Yt = /^\s*function (\w+)/; - function Qt(t) { - var e = t && t.toString().match(Yt); - return e ? e[1] : ""; - } - function te(t, e) { - return Qt(t) === Qt(e); - } - function ee(t, e) { - if (!Array.isArray(e)) return te(e, t) ? 0 : -1; - for (var n = 0, r = e.length; n < r; n++) - if (te(e[n], t)) return n; - return -1; - } - function ne(t, e, n) { - mt(); - try { - if (e) { - var r = e; - while ((r = r.$parent)) { - var i = r.$options.errorCaptured; - if (i) - for (var o = 0; o < i.length; o++) - try { - var a = - !1 === i[o].call(r, t, e, n); - if (a) return; - } catch (Sa) { - ie(Sa, r, "errorCaptured hook"); - } - } - } - ie(t, e, n); - } finally { - gt(); - } - } - function re(t, e, n, r, i) { - var o; - try { - (o = n ? t.apply(e, n) : t.call(e)), - o && - !o._isVue && - p(o) && - !o._handled && - (o.catch(function (t) { - return ne(t, r, i + " (Promise/async)"); - }), - (o._handled = !0)); - } catch (Sa) { - ne(Sa, r, i); - } - return o; - } - function ie(t, e, n) { - if (B.errorHandler) - try { - return B.errorHandler.call(null, t, e, n); - } catch (Sa) { - Sa !== t && oe(Sa, null, "config.errorHandler"); - } - oe(t, e, n); - } - function oe(t, e, n) { - if ((!K && !X) || "undefined" === typeof console) throw t; - console.error(t); - } - var ae, - se = !1, - ue = [], - ce = !1; - function fe() { - ce = !1; - var t = ue.slice(0); - ue.length = 0; - for (var e = 0; e < t.length; e++) t[e](); - } - if ("undefined" !== typeof Promise && ft(Promise)) { - var le = Promise.resolve(); - (ae = function () { - le.then(fe), rt && setTimeout(L); - }), - (se = !0); - } else if ( - tt || - "undefined" === typeof MutationObserver || - (!ft(MutationObserver) && - "[object MutationObserverConstructor]" !== - MutationObserver.toString()) - ) - ae = - "undefined" !== typeof setImmediate && ft(setImmediate) - ? function () { - setImmediate(fe); - } - : function () { - setTimeout(fe, 0); - }; - else { - var de = 1, - pe = new MutationObserver(fe), - he = document.createTextNode(String(de)); - pe.observe(he, { characterData: !0 }), - (ae = function () { - (de = (de + 1) % 2), (he.data = String(de)); - }), - (se = !0); - } - function ve(t, e) { - var n; - if ( - (ue.push(function () { - if (t) - try { - t.call(e); - } catch (Sa) { - ne(Sa, e, "nextTick"); - } - else n && n(e); - }), - ce || ((ce = !0), ae()), - !t && "undefined" !== typeof Promise) - ) - return new Promise(function (t) { - n = t; - }); - } - var ye = new lt(); - function me(t) { - ge(t, ye), ye.clear(); - } - function ge(t, e) { - var n, - r, - i = Array.isArray(t); - if ( - !( - (!i && !u(t)) || - Object.isFrozen(t) || - t instanceof bt - ) - ) { - if (t.__ob__) { - var o = t.__ob__.dep.id; - if (e.has(o)) return; - e.add(o); - } - if (i) { - n = t.length; - while (n--) ge(t[n], e); - } else { - (r = Object.keys(t)), (n = r.length); - while (n--) ge(t[r[n]], e); - } - } - } - var be = w(function (t) { - var e = "&" === t.charAt(0); - t = e ? t.slice(1) : t; - var n = "~" === t.charAt(0); - t = n ? t.slice(1) : t; - var r = "!" === t.charAt(0); - return ( - (t = r ? t.slice(1) : t), - { name: t, once: n, capture: r, passive: e } - ); - }); - function _e(t, e) { - function n() { - var t = arguments, - r = n.fns; - if (!Array.isArray(r)) - return re(r, null, arguments, e, "v-on handler"); - for (var i = r.slice(), o = 0; o < i.length; o++) - re(i[o], null, t, e, "v-on handler"); - } - return (n.fns = t), n; - } - function we(t, e, n, i, a, s) { - var u, c, f, l; - for (u in t) - (c = t[u]), - (f = e[u]), - (l = be(u)), - r(c) || - (r(f) - ? (r(c.fns) && (c = t[u] = _e(c, s)), - o(l.once) && - (c = t[u] = a(l.name, c, l.capture)), - n( - l.name, - c, - l.capture, - l.passive, - l.params - )) - : c !== f && ((f.fns = c), (t[u] = f))); - for (u in e) - r(t[u]) && ((l = be(u)), i(l.name, e[u], l.capture)); - } - function xe(t, e, n) { - var a; - t instanceof bt && (t = t.data.hook || (t.data.hook = {})); - var s = t[e]; - function u() { - n.apply(this, arguments), g(a.fns, u); - } - r(s) - ? (a = _e([u])) - : i(s.fns) && o(s.merged) - ? ((a = s), a.fns.push(u)) - : (a = _e([s, u])), - (a.merged = !0), - (t[e] = a); - } - function ke(t, e, n) { - var o = e.options.props; - if (!r(o)) { - var a = {}, - s = t.attrs, - u = t.props; - if (i(s) || i(u)) - for (var c in o) { - var f = O(c); - Se(a, u, c, f, !0) || Se(a, s, c, f, !1); - } - return a; - } - } - function Se(t, e, n, r, o) { - if (i(e)) { - if (_(e, n)) return (t[n] = e[n]), o || delete e[n], !0; - if (_(e, r)) return (t[n] = e[r]), o || delete e[r], !0; - } - return !1; - } - function Ee(t) { - for (var e = 0; e < t.length; e++) - if (Array.isArray(t[e])) - return Array.prototype.concat.apply([], t); - return t; - } - function Oe(t) { - return s(t) ? [xt(t)] : Array.isArray(t) ? Ae(t) : void 0; - } - function Te(t) { - return i(t) && i(t.text) && a(t.isComment); - } - function Ae(t, e) { - var n, - a, - u, - c, - f = []; - for (n = 0; n < t.length; n++) - (a = t[n]), - r(a) || - "boolean" === typeof a || - ((u = f.length - 1), - (c = f[u]), - Array.isArray(a) - ? a.length > 0 && - ((a = Ae(a, (e || "") + "_" + n)), - Te(a[0]) && - Te(c) && - ((f[u] = xt(c.text + a[0].text)), - a.shift()), - f.push.apply(f, a)) - : s(a) - ? Te(c) - ? (f[u] = xt(c.text + a)) - : "" !== a && f.push(xt(a)) - : Te(a) && Te(c) - ? (f[u] = xt(c.text + a.text)) - : (o(t._isVList) && - i(a.tag) && - r(a.key) && - i(e) && - (a.key = - "__vlist" + e + "_" + n + "__"), - f.push(a))); - return f; - } - function Ce(t) { - var e = t.$options.provide; - e && - (t._provided = "function" === typeof e ? e.call(t) : e); - } - function je(t) { - var e = Ie(t.$options.inject, t); - e && - (Ct(!1), - Object.keys(e).forEach(function (n) { - Pt(t, n, e[n]); - }), - Ct(!0)); - } - function Ie(t, e) { - if (t) { - for ( - var n = Object.create(null), - r = dt ? Reflect.ownKeys(t) : Object.keys(t), - i = 0; - i < r.length; - i++ - ) { - var o = r[i]; - if ("__ob__" !== o) { - var a = t[o].from, - s = e; - while (s) { - if (s._provided && _(s._provided, a)) { - n[o] = s._provided[a]; - break; - } - s = s.$parent; - } - if (!s) - if ("default" in t[o]) { - var u = t[o].default; - n[o] = - "function" === typeof u - ? u.call(e) - : u; - } else 0; - } - } - return n; - } - } - function Me(t, e) { - if (!t || !t.length) return {}; - for (var n = {}, r = 0, i = t.length; r < i; r++) { - var o = t[r], - a = o.data; - if ( - (a && - a.attrs && - a.attrs.slot && - delete a.attrs.slot, - (o.context !== e && o.fnContext !== e) || - !a || - null == a.slot) - ) - (n.default || (n.default = [])).push(o); - else { - var s = a.slot, - u = n[s] || (n[s] = []); - "template" === o.tag - ? u.push.apply(u, o.children || []) - : u.push(o); - } - } - for (var c in n) n[c].every(Le) && delete n[c]; - return n; - } - function Le(t) { - return (t.isComment && !t.asyncFactory) || " " === t.text; - } - function Pe(t) { - return t.isComment && t.asyncFactory; - } - function Ne(t, e, r) { - var i, - o = Object.keys(e).length > 0, - a = t ? !!t.$stable : !o, - s = t && t.$key; - if (t) { - if (t._normalized) return t._normalized; - if ( - a && - r && - r !== n && - s === r.$key && - !o && - !r.$hasNormal - ) - return r; - for (var u in ((i = {}), t)) - t[u] && "$" !== u[0] && (i[u] = Re(e, u, t[u])); - } else i = {}; - for (var c in e) c in i || (i[c] = $e(e, c)); - return ( - t && Object.isExtensible(t) && (t._normalized = i), - q(i, "$stable", a), - q(i, "$key", s), - q(i, "$hasNormal", o), - i - ); - } - function Re(t, e, n) { - var r = function () { - var t = arguments.length - ? n.apply(null, arguments) - : n({}); - t = - t && "object" === typeof t && !Array.isArray(t) - ? [t] - : Oe(t); - var e = t && t[0]; - return t && - (!e || (1 === t.length && e.isComment && !Pe(e))) - ? void 0 - : t; - }; - return ( - n.proxy && - Object.defineProperty(t, e, { - get: r, - enumerable: !0, - configurable: !0, - }), - r - ); - } - function $e(t, e) { - return function () { - return t[e]; - }; - } - function De(t, e) { - var n, r, o, a, s; - if (Array.isArray(t) || "string" === typeof t) - for ( - n = new Array(t.length), r = 0, o = t.length; - r < o; - r++ - ) - n[r] = e(t[r], r); - else if ("number" === typeof t) - for (n = new Array(t), r = 0; r < t; r++) - n[r] = e(r + 1, r); - else if (u(t)) - if (dt && t[Symbol.iterator]) { - n = []; - var c = t[Symbol.iterator](), - f = c.next(); - while (!f.done) - n.push(e(f.value, n.length)), (f = c.next()); - } else - for ( - a = Object.keys(t), - n = new Array(a.length), - r = 0, - o = a.length; - r < o; - r++ - ) - (s = a[r]), (n[r] = e(t[s], s, r)); - return i(n) || (n = []), (n._isVList = !0), n; - } - function Fe(t, e, n, r) { - var i, - o = this.$scopedSlots[t]; - o - ? ((n = n || {}), - r && (n = I(I({}, r), n)), - (i = o(n) || ("function" === typeof e ? e() : e))) - : (i = - this.$slots[t] || - ("function" === typeof e ? e() : e)); - var a = n && n.slot; - return a - ? this.$createElement("template", { slot: a }, i) - : i; - } - function ze(t) { - return Jt(this.$options, "filters", t, !0) || N; - } - function Ue(t, e) { - return Array.isArray(t) ? -1 === t.indexOf(e) : t !== e; - } - function Be(t, e, n, r, i) { - var o = B.keyCodes[e] || n; - return i && r && !B.keyCodes[e] - ? Ue(i, r) - : o - ? Ue(o, t) - : r - ? O(r) !== e - : void 0 === t; - } - function Ve(t, e, n, r, i) { - if (n) - if (u(n)) { - var o; - Array.isArray(n) && (n = M(n)); - var a = function (a) { - if ("class" === a || "style" === a || m(a)) - o = t; - else { - var s = t.attrs && t.attrs.type; - o = - r || B.mustUseProp(e, s, a) - ? t.domProps || (t.domProps = {}) - : t.attrs || (t.attrs = {}); - } - var u = k(a), - c = O(a); - if ( - !(u in o) && - !(c in o) && - ((o[a] = n[a]), i) - ) { - var f = t.on || (t.on = {}); - f["update:" + a] = function (t) { - n[a] = t; - }; - } - }; - for (var s in n) a(s); - } else; - return t; - } - function He(t, e) { - var n = this._staticTrees || (this._staticTrees = []), - r = n[t]; - return ( - (r && !e) || - ((r = n[t] = - this.$options.staticRenderFns[t].call( - this._renderProxy, - null, - this - )), - We(r, "__static__" + t, !1)), - r - ); - } - function qe(t, e, n) { - return We(t, "__once__" + e + (n ? "_" + n : ""), !0), t; - } - function We(t, e, n) { - if (Array.isArray(t)) - for (var r = 0; r < t.length; r++) - t[r] && - "string" !== typeof t[r] && - Ge(t[r], e + "_" + r, n); - else Ge(t, e, n); - } - function Ge(t, e, n) { - (t.isStatic = !0), (t.key = e), (t.isOnce = n); - } - function Ze(t, e) { - if (e) - if (f(e)) { - var n = (t.on = t.on ? I({}, t.on) : {}); - for (var r in e) { - var i = n[r], - o = e[r]; - n[r] = i ? [].concat(i, o) : o; - } - } else; - return t; - } - function Je(t, e, n, r) { - e = e || { $stable: !n }; - for (var i = 0; i < t.length; i++) { - var o = t[i]; - Array.isArray(o) - ? Je(o, e, n) - : o && - (o.proxy && (o.fn.proxy = !0), (e[o.key] = o.fn)); - } - return r && (e.$key = r), e; - } - function Ke(t, e) { - for (var n = 0; n < e.length; n += 2) { - var r = e[n]; - "string" === typeof r && r && (t[e[n]] = e[n + 1]); - } - return t; - } - function Xe(t, e) { - return "string" === typeof t ? e + t : t; - } - function Ye(t) { - (t._o = qe), - (t._n = v), - (t._s = h), - (t._l = De), - (t._t = Fe), - (t._q = R), - (t._i = $), - (t._m = He), - (t._f = ze), - (t._k = Be), - (t._b = Ve), - (t._v = xt), - (t._e = wt), - (t._u = Je), - (t._g = Ze), - (t._d = Ke), - (t._p = Xe); - } - function Qe(t, e, r, i, a) { - var s, - u = this, - c = a.options; - _(i, "_uid") - ? ((s = Object.create(i)), (s._original = i)) - : ((s = i), (i = i._original)); - var f = o(c._compiled), - l = !f; - (this.data = t), - (this.props = e), - (this.children = r), - (this.parent = i), - (this.listeners = t.on || n), - (this.injections = Ie(c.inject, i)), - (this.slots = function () { - return ( - u.$slots || - Ne(t.scopedSlots, (u.$slots = Me(r, i))), - u.$slots - ); - }), - Object.defineProperty(this, "scopedSlots", { - enumerable: !0, - get: function () { - return Ne(t.scopedSlots, this.slots()); - }, - }), - f && - ((this.$options = c), - (this.$slots = this.slots()), - (this.$scopedSlots = Ne( - t.scopedSlots, - this.$slots - ))), - c._scopeId - ? (this._c = function (t, e, n, r) { - var o = pn(s, t, e, n, r, l); - return ( - o && - !Array.isArray(o) && - ((o.fnScopeId = c._scopeId), - (o.fnContext = i)), - o - ); - }) - : (this._c = function (t, e, n, r) { - return pn(s, t, e, n, r, l); - }); - } - function tn(t, e, r, o, a) { - var s = t.options, - u = {}, - c = s.props; - if (i(c)) for (var f in c) u[f] = Kt(f, c, e || n); - else - i(r.attrs) && nn(u, r.attrs), - i(r.props) && nn(u, r.props); - var l = new Qe(r, u, a, o, t), - d = s.render.call(null, l._c, l); - if (d instanceof bt) return en(d, r, l.parent, s, l); - if (Array.isArray(d)) { - for ( - var p = Oe(d) || [], h = new Array(p.length), v = 0; - v < p.length; - v++ - ) - h[v] = en(p[v], r, l.parent, s, l); - return h; - } - } - function en(t, e, n, r, i) { - var o = kt(t); - return ( - (o.fnContext = n), - (o.fnOptions = r), - e.slot && ((o.data || (o.data = {})).slot = e.slot), - o - ); - } - function nn(t, e) { - for (var n in e) t[k(n)] = e[n]; - } - Ye(Qe.prototype); - var rn = { - init: function (t, e) { - if ( - t.componentInstance && - !t.componentInstance._isDestroyed && - t.data.keepAlive - ) { - var n = t; - rn.prepatch(n, n); - } else { - var r = (t.componentInstance = sn(t, In)); - r.$mount(e ? t.elm : void 0, e); - } - }, - prepatch: function (t, e) { - var n = e.componentOptions, - r = (e.componentInstance = t.componentInstance); - Rn(r, n.propsData, n.listeners, e, n.children); - }, - insert: function (t) { - var e = t.context, - n = t.componentInstance; - n._isMounted || - ((n._isMounted = !0), zn(n, "mounted")), - t.data.keepAlive && - (e._isMounted ? Qn(n) : Dn(n, !0)); - }, - destroy: function (t) { - var e = t.componentInstance; - e._isDestroyed || - (t.data.keepAlive ? Fn(e, !0) : e.$destroy()); - }, - }, - on = Object.keys(rn); - function an(t, e, n, a, s) { - if (!r(t)) { - var c = n.$options._base; - if ( - (u(t) && (t = c.extend(t)), "function" === typeof t) - ) { - var f; - if ( - r(t.cid) && - ((f = t), (t = kn(f, c)), void 0 === t) - ) - return xn(f, e, n, a, s); - (e = e || {}), - xr(t), - i(e.model) && fn(t.options, e); - var l = ke(e, t, s); - if (o(t.options.functional)) - return tn(t, l, e, n, a); - var d = e.on; - if (((e.on = e.nativeOn), o(t.options.abstract))) { - var p = e.slot; - (e = {}), p && (e.slot = p); - } - un(e); - var h = t.options.name || s, - v = new bt( - "vue-component-" + - t.cid + - (h ? "-" + h : ""), - e, - void 0, - void 0, - void 0, - n, - { - Ctor: t, - propsData: l, - listeners: d, - tag: s, - children: a, - }, - f - ); - return v; - } - } - } - function sn(t, e) { - var n = { _isComponent: !0, _parentVnode: t, parent: e }, - r = t.data.inlineTemplate; - return ( - i(r) && - ((n.render = r.render), - (n.staticRenderFns = r.staticRenderFns)), - new t.componentOptions.Ctor(n) - ); - } - function un(t) { - for ( - var e = t.hook || (t.hook = {}), n = 0; - n < on.length; - n++ - ) { - var r = on[n], - i = e[r], - o = rn[r]; - i === o || - (i && i._merged) || - (e[r] = i ? cn(o, i) : o); - } - } - function cn(t, e) { - var n = function (n, r) { - t(n, r), e(n, r); - }; - return (n._merged = !0), n; - } - function fn(t, e) { - var n = (t.model && t.model.prop) || "value", - r = (t.model && t.model.event) || "input"; - (e.attrs || (e.attrs = {}))[n] = e.model.value; - var o = e.on || (e.on = {}), - a = o[r], - s = e.model.callback; - i(a) - ? (Array.isArray(a) ? -1 === a.indexOf(s) : a !== s) && - (o[r] = [s].concat(a)) - : (o[r] = s); - } - var ln = 1, - dn = 2; - function pn(t, e, n, r, i, a) { - return ( - (Array.isArray(n) || s(n)) && - ((i = r), (r = n), (n = void 0)), - o(a) && (i = dn), - hn(t, e, n, r, i) - ); - } - function hn(t, e, n, r, o) { - if (i(n) && i(n.__ob__)) return wt(); - if ((i(n) && i(n.is) && (e = n.is), !e)) return wt(); - var a, s, u; - (Array.isArray(r) && - "function" === typeof r[0] && - ((n = n || {}), - (n.scopedSlots = { default: r[0] }), - (r.length = 0)), - o === dn ? (r = Oe(r)) : o === ln && (r = Ee(r)), - "string" === typeof e) - ? ((s = - (t.$vnode && t.$vnode.ns) || - B.getTagNamespace(e)), - (a = B.isReservedTag(e) - ? new bt( - B.parsePlatformTagName(e), - n, - r, - void 0, - void 0, - t - ) - : (n && n.pre) || - !i((u = Jt(t.$options, "components", e))) - ? new bt(e, n, r, void 0, void 0, t) - : an(u, n, t, r, e))) - : (a = an(e, n, t, r)); - return Array.isArray(a) - ? a - : i(a) - ? (i(s) && vn(a, s), i(n) && yn(n), a) - : wt(); - } - function vn(t, e, n) { - if ( - ((t.ns = e), - "foreignObject" === t.tag && ((e = void 0), (n = !0)), - i(t.children)) - ) - for (var a = 0, s = t.children.length; a < s; a++) { - var u = t.children[a]; - i(u.tag) && - (r(u.ns) || (o(n) && "svg" !== u.tag)) && - vn(u, e, n); - } - } - function yn(t) { - u(t.style) && me(t.style), u(t.class) && me(t.class); - } - function mn(t) { - (t._vnode = null), (t._staticTrees = null); - var e = t.$options, - r = (t.$vnode = e._parentVnode), - i = r && r.context; - (t.$slots = Me(e._renderChildren, i)), - (t.$scopedSlots = n), - (t._c = function (e, n, r, i) { - return pn(t, e, n, r, i, !1); - }), - (t.$createElement = function (e, n, r, i) { - return pn(t, e, n, r, i, !0); - }); - var o = r && r.data; - Pt(t, "$attrs", (o && o.attrs) || n, null, !0), - Pt(t, "$listeners", e._parentListeners || n, null, !0); - } - var gn, - bn = null; - function _n(t) { - Ye(t.prototype), - (t.prototype.$nextTick = function (t) { - return ve(t, this); - }), - (t.prototype._render = function () { - var t, - e = this, - n = e.$options, - r = n.render, - i = n._parentVnode; - i && - (e.$scopedSlots = Ne( - i.data.scopedSlots, - e.$slots, - e.$scopedSlots - )), - (e.$vnode = i); - try { - (bn = e), - (t = r.call( - e._renderProxy, - e.$createElement - )); - } catch (Sa) { - ne(Sa, e, "render"), (t = e._vnode); - } finally { - bn = null; - } - return ( - Array.isArray(t) && - 1 === t.length && - (t = t[0]), - t instanceof bt || (t = wt()), - (t.parent = i), - t - ); - }); - } - function wn(t, e) { - return ( - (t.__esModule || - (dt && "Module" === t[Symbol.toStringTag])) && - (t = t.default), - u(t) ? e.extend(t) : t - ); - } - function xn(t, e, n, r, i) { - var o = wt(); - return ( - (o.asyncFactory = t), - (o.asyncMeta = { - data: e, - context: n, - children: r, - tag: i, - }), - o - ); - } - function kn(t, e) { - if (o(t.error) && i(t.errorComp)) return t.errorComp; - if (i(t.resolved)) return t.resolved; - var n = bn; - if ( - (n && - i(t.owners) && - -1 === t.owners.indexOf(n) && - t.owners.push(n), - o(t.loading) && i(t.loadingComp)) - ) - return t.loadingComp; - if (n && !i(t.owners)) { - var a = (t.owners = [n]), - s = !0, - c = null, - f = null; - n.$on("hook:destroyed", function () { - return g(a, n); - }); - var l = function (t) { - for (var e = 0, n = a.length; e < n; e++) - a[e].$forceUpdate(); - t && - ((a.length = 0), - null !== c && (clearTimeout(c), (c = null)), - null !== f && - (clearTimeout(f), (f = null))); - }, - d = D(function (n) { - (t.resolved = wn(n, e)), - s ? (a.length = 0) : l(!0); - }), - h = D(function (e) { - i(t.errorComp) && ((t.error = !0), l(!0)); - }), - v = t(d, h); - return ( - u(v) && - (p(v) - ? r(t.resolved) && v.then(d, h) - : p(v.component) && - (v.component.then(d, h), - i(v.error) && - (t.errorComp = wn(v.error, e)), - i(v.loading) && - ((t.loadingComp = wn(v.loading, e)), - 0 === v.delay - ? (t.loading = !0) - : (c = setTimeout(function () { - (c = null), - r(t.resolved) && - r(t.error) && - ((t.loading = !0), - l(!1)); - }, v.delay || 200))), - i(v.timeout) && - (f = setTimeout(function () { - (f = null), - r(t.resolved) && h(null); - }, v.timeout)))), - (s = !1), - t.loading ? t.loadingComp : t.resolved - ); - } - } - function Sn(t) { - if (Array.isArray(t)) - for (var e = 0; e < t.length; e++) { - var n = t[e]; - if (i(n) && (i(n.componentOptions) || Pe(n))) - return n; - } - } - function En(t) { - (t._events = Object.create(null)), (t._hasHookEvent = !1); - var e = t.$options._parentListeners; - e && Cn(t, e); - } - function On(t, e) { - gn.$on(t, e); - } - function Tn(t, e) { - gn.$off(t, e); - } - function An(t, e) { - var n = gn; - return function r() { - var i = e.apply(null, arguments); - null !== i && n.$off(t, r); - }; - } - function Cn(t, e, n) { - (gn = t), we(e, n || {}, On, Tn, An, t), (gn = void 0); - } - function jn(t) { - var e = /^hook:/; - (t.prototype.$on = function (t, n) { - var r = this; - if (Array.isArray(t)) - for (var i = 0, o = t.length; i < o; i++) - r.$on(t[i], n); - else - (r._events[t] || (r._events[t] = [])).push(n), - e.test(t) && (r._hasHookEvent = !0); - return r; - }), - (t.prototype.$once = function (t, e) { - var n = this; - function r() { - n.$off(t, r), e.apply(n, arguments); - } - return (r.fn = e), n.$on(t, r), n; - }), - (t.prototype.$off = function (t, e) { - var n = this; - if (!arguments.length) - return (n._events = Object.create(null)), n; - if (Array.isArray(t)) { - for (var r = 0, i = t.length; r < i; r++) - n.$off(t[r], e); - return n; - } - var o, - a = n._events[t]; - if (!a) return n; - if (!e) return (n._events[t] = null), n; - var s = a.length; - while (s--) - if (((o = a[s]), o === e || o.fn === e)) { - a.splice(s, 1); - break; - } - return n; - }), - (t.prototype.$emit = function (t) { - var e = this, - n = e._events[t]; - if (n) { - n = n.length > 1 ? j(n) : n; - for ( - var r = j(arguments, 1), - i = 'event handler for "' + t + '"', - o = 0, - a = n.length; - o < a; - o++ - ) - re(n[o], e, r, e, i); - } - return e; - }); - } - var In = null; - function Mn(t) { - var e = In; - return ( - (In = t), - function () { - In = e; - } - ); - } - function Ln(t) { - var e = t.$options, - n = e.parent; - if (n && !e.abstract) { - while (n.$options.abstract && n.$parent) n = n.$parent; - n.$children.push(t); - } - (t.$parent = n), - (t.$root = n ? n.$root : t), - (t.$children = []), - (t.$refs = {}), - (t._watcher = null), - (t._inactive = null), - (t._directInactive = !1), - (t._isMounted = !1), - (t._isDestroyed = !1), - (t._isBeingDestroyed = !1); - } - function Pn(t) { - (t.prototype._update = function (t, e) { - var n = this, - r = n.$el, - i = n._vnode, - o = Mn(n); - (n._vnode = t), - (n.$el = i - ? n.__patch__(i, t) - : n.__patch__(n.$el, t, e, !1)), - o(), - r && (r.__vue__ = null), - n.$el && (n.$el.__vue__ = n), - n.$vnode && - n.$parent && - n.$vnode === n.$parent._vnode && - (n.$parent.$el = n.$el); - }), - (t.prototype.$forceUpdate = function () { - var t = this; - t._watcher && t._watcher.update(); - }), - (t.prototype.$destroy = function () { - var t = this; - if (!t._isBeingDestroyed) { - zn(t, "beforeDestroy"), - (t._isBeingDestroyed = !0); - var e = t.$parent; - !e || - e._isBeingDestroyed || - t.$options.abstract || - g(e.$children, t), - t._watcher && t._watcher.teardown(); - var n = t._watchers.length; - while (n--) t._watchers[n].teardown(); - t._data.__ob__ && t._data.__ob__.vmCount--, - (t._isDestroyed = !0), - t.__patch__(t._vnode, null), - zn(t, "destroyed"), - t.$off(), - t.$el && (t.$el.__vue__ = null), - t.$vnode && (t.$vnode.parent = null); - } - }); - } - function Nn(t, e, n) { - var r; - return ( - (t.$el = e), - t.$options.render || (t.$options.render = wt), - zn(t, "beforeMount"), - (r = function () { - t._update(t._render(), n); - }), - new rr( - t, - r, - L, - { - before: function () { - t._isMounted && - !t._isDestroyed && - zn(t, "beforeUpdate"); - }, - }, - !0 - ), - (n = !1), - null == t.$vnode && - ((t._isMounted = !0), zn(t, "mounted")), - t - ); - } - function Rn(t, e, r, i, o) { - var a = i.data.scopedSlots, - s = t.$scopedSlots, - u = !!( - (a && !a.$stable) || - (s !== n && !s.$stable) || - (a && t.$scopedSlots.$key !== a.$key) || - (!a && t.$scopedSlots.$key) - ), - c = !!(o || t.$options._renderChildren || u); - if ( - ((t.$options._parentVnode = i), - (t.$vnode = i), - t._vnode && (t._vnode.parent = i), - (t.$options._renderChildren = o), - (t.$attrs = i.data.attrs || n), - (t.$listeners = r || n), - e && t.$options.props) - ) { - Ct(!1); - for ( - var f = t._props, - l = t.$options._propKeys || [], - d = 0; - d < l.length; - d++ - ) { - var p = l[d], - h = t.$options.props; - f[p] = Kt(p, h, e, t); - } - Ct(!0), (t.$options.propsData = e); - } - r = r || n; - var v = t.$options._parentListeners; - (t.$options._parentListeners = r), - Cn(t, r, v), - c && ((t.$slots = Me(o, i.context)), t.$forceUpdate()); - } - function $n(t) { - while (t && (t = t.$parent)) if (t._inactive) return !0; - return !1; - } - function Dn(t, e) { - if (e) { - if (((t._directInactive = !1), $n(t))) return; - } else if (t._directInactive) return; - if (t._inactive || null === t._inactive) { - t._inactive = !1; - for (var n = 0; n < t.$children.length; n++) - Dn(t.$children[n]); - zn(t, "activated"); - } - } - function Fn(t, e) { - if ( - (!e || ((t._directInactive = !0), !$n(t))) && - !t._inactive - ) { - t._inactive = !0; - for (var n = 0; n < t.$children.length; n++) - Fn(t.$children[n]); - zn(t, "deactivated"); - } - } - function zn(t, e) { - mt(); - var n = t.$options[e], - r = e + " hook"; - if (n) - for (var i = 0, o = n.length; i < o; i++) - re(n[i], t, null, t, r); - t._hasHookEvent && t.$emit("hook:" + e), gt(); - } - var Un = [], - Bn = [], - Vn = {}, - Hn = !1, - qn = !1, - Wn = 0; - function Gn() { - (Wn = Un.length = Bn.length = 0), (Vn = {}), (Hn = qn = !1); - } - var Zn = 0, - Jn = Date.now; - if (K && !tt) { - var Kn = window.performance; - Kn && - "function" === typeof Kn.now && - Jn() > document.createEvent("Event").timeStamp && - (Jn = function () { - return Kn.now(); - }); - } - function Xn() { - var t, e; - for ( - Zn = Jn(), - qn = !0, - Un.sort(function (t, e) { - return t.id - e.id; - }), - Wn = 0; - Wn < Un.length; - Wn++ - ) - (t = Un[Wn]), - t.before && t.before(), - (e = t.id), - (Vn[e] = null), - t.run(); - var n = Bn.slice(), - r = Un.slice(); - Gn(), tr(n), Yn(r), ct && B.devtools && ct.emit("flush"); - } - function Yn(t) { - var e = t.length; - while (e--) { - var n = t[e], - r = n.vm; - r._watcher === n && - r._isMounted && - !r._isDestroyed && - zn(r, "updated"); - } - } - function Qn(t) { - (t._inactive = !1), Bn.push(t); - } - function tr(t) { - for (var e = 0; e < t.length; e++) - (t[e]._inactive = !0), Dn(t[e], !0); - } - function er(t) { - var e = t.id; - if (null == Vn[e]) { - if (((Vn[e] = !0), qn)) { - var n = Un.length - 1; - while (n > Wn && Un[n].id > t.id) n--; - Un.splice(n + 1, 0, t); - } else Un.push(t); - Hn || ((Hn = !0), ve(Xn)); - } - } - var nr = 0, - rr = function (t, e, n, r, i) { - (this.vm = t), - i && (t._watcher = this), - t._watchers.push(this), - r - ? ((this.deep = !!r.deep), - (this.user = !!r.user), - (this.lazy = !!r.lazy), - (this.sync = !!r.sync), - (this.before = r.before)) - : (this.deep = - this.user = - this.lazy = - this.sync = - !1), - (this.cb = n), - (this.id = ++nr), - (this.active = !0), - (this.dirty = this.lazy), - (this.deps = []), - (this.newDeps = []), - (this.depIds = new lt()), - (this.newDepIds = new lt()), - (this.expression = ""), - "function" === typeof e - ? (this.getter = e) - : ((this.getter = G(e)), - this.getter || (this.getter = L)), - (this.value = this.lazy ? void 0 : this.get()); - }; - (rr.prototype.get = function () { - var t; - mt(this); - var e = this.vm; - try { - t = this.getter.call(e, e); - } catch (Sa) { - if (!this.user) throw Sa; - ne( - Sa, - e, - 'getter for watcher "' + this.expression + '"' - ); - } finally { - this.deep && me(t), gt(), this.cleanupDeps(); - } - return t; - }), - (rr.prototype.addDep = function (t) { - var e = t.id; - this.newDepIds.has(e) || - (this.newDepIds.add(e), - this.newDeps.push(t), - this.depIds.has(e) || t.addSub(this)); - }), - (rr.prototype.cleanupDeps = function () { - var t = this.deps.length; - while (t--) { - var e = this.deps[t]; - this.newDepIds.has(e.id) || e.removeSub(this); - } - var n = this.depIds; - (this.depIds = this.newDepIds), - (this.newDepIds = n), - this.newDepIds.clear(), - (n = this.deps), - (this.deps = this.newDeps), - (this.newDeps = n), - (this.newDeps.length = 0); - }), - (rr.prototype.update = function () { - this.lazy - ? (this.dirty = !0) - : this.sync - ? this.run() - : er(this); - }), - (rr.prototype.run = function () { - if (this.active) { - var t = this.get(); - if (t !== this.value || u(t) || this.deep) { - var e = this.value; - if (((this.value = t), this.user)) { - var n = - 'callback for watcher "' + - this.expression + - '"'; - re(this.cb, this.vm, [t, e], this.vm, n); - } else this.cb.call(this.vm, t, e); - } - } - }), - (rr.prototype.evaluate = function () { - (this.value = this.get()), (this.dirty = !1); - }), - (rr.prototype.depend = function () { - var t = this.deps.length; - while (t--) this.deps[t].depend(); - }), - (rr.prototype.teardown = function () { - if (this.active) { - this.vm._isBeingDestroyed || - g(this.vm._watchers, this); - var t = this.deps.length; - while (t--) this.deps[t].removeSub(this); - this.active = !1; - } - }); - var ir = { enumerable: !0, configurable: !0, get: L, set: L }; - function or(t, e, n) { - (ir.get = function () { - return this[e][n]; - }), - (ir.set = function (t) { - this[e][n] = t; - }), - Object.defineProperty(t, n, ir); - } - function ar(t) { - t._watchers = []; - var e = t.$options; - e.props && sr(t, e.props), - e.methods && vr(t, e.methods), - e.data ? ur(t) : Lt((t._data = {}), !0), - e.computed && lr(t, e.computed), - e.watch && e.watch !== ot && yr(t, e.watch); - } - function sr(t, e) { - var n = t.$options.propsData || {}, - r = (t._props = {}), - i = (t.$options._propKeys = []), - o = !t.$parent; - o || Ct(!1); - var a = function (o) { - i.push(o); - var a = Kt(o, e, n, t); - Pt(r, o, a), o in t || or(t, "_props", o); - }; - for (var s in e) a(s); - Ct(!0); - } - function ur(t) { - var e = t.$options.data; - (e = t._data = - "function" === typeof e ? cr(e, t) : e || {}), - f(e) || (e = {}); - var n = Object.keys(e), - r = t.$options.props, - i = (t.$options.methods, n.length); - while (i--) { - var o = n[i]; - 0, (r && _(r, o)) || H(o) || or(t, "_data", o); - } - Lt(e, !0); - } - function cr(t, e) { - mt(); - try { - return t.call(e, e); - } catch (Sa) { - return ne(Sa, e, "data()"), {}; - } finally { - gt(); - } - } - var fr = { lazy: !0 }; - function lr(t, e) { - var n = (t._computedWatchers = Object.create(null)), - r = ut(); - for (var i in e) { - var o = e[i], - a = "function" === typeof o ? o : o.get; - 0, - r || (n[i] = new rr(t, a || L, L, fr)), - i in t || dr(t, i, o); - } - } - function dr(t, e, n) { - var r = !ut(); - "function" === typeof n - ? ((ir.get = r ? pr(e) : hr(n)), (ir.set = L)) - : ((ir.get = n.get - ? r && !1 !== n.cache - ? pr(e) - : hr(n.get) - : L), - (ir.set = n.set || L)), - Object.defineProperty(t, e, ir); - } - function pr(t) { - return function () { - var e = - this._computedWatchers && this._computedWatchers[t]; - if (e) - return ( - e.dirty && e.evaluate(), - vt.target && e.depend(), - e.value - ); - }; - } - function hr(t) { - return function () { - return t.call(this, this); - }; - } - function vr(t, e) { - t.$options.props; - for (var n in e) - t[n] = "function" !== typeof e[n] ? L : C(e[n], t); - } - function yr(t, e) { - for (var n in e) { - var r = e[n]; - if (Array.isArray(r)) - for (var i = 0; i < r.length; i++) mr(t, n, r[i]); - else mr(t, n, r); - } - } - function mr(t, e, n, r) { - return ( - f(n) && ((r = n), (n = n.handler)), - "string" === typeof n && (n = t[n]), - t.$watch(e, n, r) - ); - } - function gr(t) { - var e = { - get: function () { - return this._data; - }, - }, - n = { - get: function () { - return this._props; - }, - }; - Object.defineProperty(t.prototype, "$data", e), - Object.defineProperty(t.prototype, "$props", n), - (t.prototype.$set = Nt), - (t.prototype.$delete = Rt), - (t.prototype.$watch = function (t, e, n) { - var r = this; - if (f(e)) return mr(r, t, e, n); - (n = n || {}), (n.user = !0); - var i = new rr(r, t, e, n); - if (n.immediate) { - var o = - 'callback for immediate watcher "' + - i.expression + - '"'; - mt(), re(e, r, [i.value], r, o), gt(); - } - return function () { - i.teardown(); - }; - }); - } - var br = 0; - function _r(t) { - t.prototype._init = function (t) { - var e = this; - (e._uid = br++), - (e._isVue = !0), - t && t._isComponent - ? wr(e, t) - : (e.$options = Zt( - xr(e.constructor), - t || {}, - e - )), - (e._renderProxy = e), - (e._self = e), - Ln(e), - En(e), - mn(e), - zn(e, "beforeCreate"), - je(e), - ar(e), - Ce(e), - zn(e, "created"), - e.$options.el && e.$mount(e.$options.el); - }; - } - function wr(t, e) { - var n = (t.$options = Object.create(t.constructor.options)), - r = e._parentVnode; - (n.parent = e.parent), (n._parentVnode = r); - var i = r.componentOptions; - (n.propsData = i.propsData), - (n._parentListeners = i.listeners), - (n._renderChildren = i.children), - (n._componentTag = i.tag), - e.render && - ((n.render = e.render), - (n.staticRenderFns = e.staticRenderFns)); - } - function xr(t) { - var e = t.options; - if (t.super) { - var n = xr(t.super), - r = t.superOptions; - if (n !== r) { - t.superOptions = n; - var i = kr(t); - i && I(t.extendOptions, i), - (e = t.options = Zt(n, t.extendOptions)), - e.name && (e.components[e.name] = t); - } - } - return e; - } - function kr(t) { - var e, - n = t.options, - r = t.sealedOptions; - for (var i in n) - n[i] !== r[i] && (e || (e = {}), (e[i] = n[i])); - return e; - } - function Sr(t) { - this._init(t); - } - function Er(t) { - t.use = function (t) { - var e = - this._installedPlugins || - (this._installedPlugins = []); - if (e.indexOf(t) > -1) return this; - var n = j(arguments, 1); - return ( - n.unshift(this), - "function" === typeof t.install - ? t.install.apply(t, n) - : "function" === typeof t && t.apply(null, n), - e.push(t), - this - ); - }; - } - function Or(t) { - t.mixin = function (t) { - return (this.options = Zt(this.options, t)), this; - }; - } - function Tr(t) { - t.cid = 0; - var e = 1; - t.extend = function (t) { - t = t || {}; - var n = this, - r = n.cid, - i = t._Ctor || (t._Ctor = {}); - if (i[r]) return i[r]; - var o = t.name || n.options.name; - var a = function (t) { - this._init(t); - }; - return ( - (a.prototype = Object.create(n.prototype)), - (a.prototype.constructor = a), - (a.cid = e++), - (a.options = Zt(n.options, t)), - (a["super"] = n), - a.options.props && Ar(a), - a.options.computed && Cr(a), - (a.extend = n.extend), - (a.mixin = n.mixin), - (a.use = n.use), - z.forEach(function (t) { - a[t] = n[t]; - }), - o && (a.options.components[o] = a), - (a.superOptions = n.options), - (a.extendOptions = t), - (a.sealedOptions = I({}, a.options)), - (i[r] = a), - a - ); - }; - } - function Ar(t) { - var e = t.options.props; - for (var n in e) or(t.prototype, "_props", n); - } - function Cr(t) { - var e = t.options.computed; - for (var n in e) dr(t.prototype, n, e[n]); - } - function jr(t) { - z.forEach(function (e) { - t[e] = function (t, n) { - return n - ? ("component" === e && - f(n) && - ((n.name = n.name || t), - (n = this.options._base.extend(n))), - "directive" === e && - "function" === typeof n && - (n = { bind: n, update: n }), - (this.options[e + "s"][t] = n), - n) - : this.options[e + "s"][t]; - }; - }); - } - function Ir(t) { - return t && (t.Ctor.options.name || t.tag); - } - function Mr(t, e) { - return Array.isArray(t) - ? t.indexOf(e) > -1 - : "string" === typeof t - ? t.split(",").indexOf(e) > -1 - : !!l(t) && t.test(e); - } - function Lr(t, e) { - var n = t.cache, - r = t.keys, - i = t._vnode; - for (var o in n) { - var a = n[o]; - if (a) { - var s = a.name; - s && !e(s) && Pr(n, o, r, i); - } - } - } - function Pr(t, e, n, r) { - var i = t[e]; - !i || - (r && i.tag === r.tag) || - i.componentInstance.$destroy(), - (t[e] = null), - g(n, e); - } - _r(Sr), gr(Sr), jn(Sr), Pn(Sr), _n(Sr); - var Nr = [String, RegExp, Array], - Rr = { - name: "keep-alive", - abstract: !0, - props: { - include: Nr, - exclude: Nr, - max: [String, Number], - }, - methods: { - cacheVNode: function () { - var t = this, - e = t.cache, - n = t.keys, - r = t.vnodeToCache, - i = t.keyToCache; - if (r) { - var o = r.tag, - a = r.componentInstance, - s = r.componentOptions; - (e[i] = { - name: Ir(s), - tag: o, - componentInstance: a, - }), - n.push(i), - this.max && - n.length > parseInt(this.max) && - Pr(e, n[0], n, this._vnode), - (this.vnodeToCache = null); - } - }, - }, - created: function () { - (this.cache = Object.create(null)), - (this.keys = []); - }, - destroyed: function () { - for (var t in this.cache) - Pr(this.cache, t, this.keys); - }, - mounted: function () { - var t = this; - this.cacheVNode(), - this.$watch("include", function (e) { - Lr(t, function (t) { - return Mr(e, t); - }); - }), - this.$watch("exclude", function (e) { - Lr(t, function (t) { - return !Mr(e, t); - }); - }); - }, - updated: function () { - this.cacheVNode(); - }, - render: function () { - var t = this.$slots.default, - e = Sn(t), - n = e && e.componentOptions; - if (n) { - var r = Ir(n), - i = this, - o = i.include, - a = i.exclude; - if ( - (o && (!r || !Mr(o, r))) || - (a && r && Mr(a, r)) - ) - return e; - var s = this, - u = s.cache, - c = s.keys, - f = - null == e.key - ? n.Ctor.cid + - (n.tag ? "::" + n.tag : "") - : e.key; - u[f] - ? ((e.componentInstance = - u[f].componentInstance), - g(c, f), - c.push(f)) - : ((this.vnodeToCache = e), - (this.keyToCache = f)), - (e.data.keepAlive = !0); - } - return e || (t && t[0]); - }, - }, - $r = { KeepAlive: Rr }; - function Dr(t) { - var e = { - get: function () { - return B; - }, - }; - Object.defineProperty(t, "config", e), - (t.util = { - warn: pt, - extend: I, - mergeOptions: Zt, - defineReactive: Pt, - }), - (t.set = Nt), - (t.delete = Rt), - (t.nextTick = ve), - (t.observable = function (t) { - return Lt(t), t; - }), - (t.options = Object.create(null)), - z.forEach(function (e) { - t.options[e + "s"] = Object.create(null); - }), - (t.options._base = t), - I(t.options.components, $r), - Er(t), - Or(t), - Tr(t), - jr(t); - } - Dr(Sr), - Object.defineProperty(Sr.prototype, "$isServer", { - get: ut, - }), - Object.defineProperty(Sr.prototype, "$ssrContext", { - get: function () { - return this.$vnode && this.$vnode.ssrContext; - }, - }), - Object.defineProperty(Sr, "FunctionalRenderContext", { - value: Qe, - }), - (Sr.version = "2.6.14"); - var Fr = y("style,class"), - zr = y("input,textarea,option,select,progress"), - Ur = function (t, e, n) { - return ( - ("value" === n && zr(t) && "button" !== e) || - ("selected" === n && "option" === t) || - ("checked" === n && "input" === t) || - ("muted" === n && "video" === t) - ); - }, - Br = y("contenteditable,draggable,spellcheck"), - Vr = y("events,caret,typing,plaintext-only"), - Hr = function (t, e) { - return Jr(e) || "false" === e - ? "false" - : "contenteditable" === t && Vr(e) - ? e - : "true"; - }, - qr = y( - "allowfullscreen,async,autofocus,autoplay,checked,compact,controls,declare,default,defaultchecked,defaultmuted,defaultselected,defer,disabled,enabled,formnovalidate,hidden,indeterminate,inert,ismap,itemscope,loop,multiple,muted,nohref,noresize,noshade,novalidate,nowrap,open,pauseonexit,readonly,required,reversed,scoped,seamless,selected,sortable,truespeed,typemustmatch,visible" - ), - Wr = "http://www.w3.org/1999/xlink", - Gr = function (t) { - return ":" === t.charAt(5) && "xlink" === t.slice(0, 5); - }, - Zr = function (t) { - return Gr(t) ? t.slice(6, t.length) : ""; - }, - Jr = function (t) { - return null == t || !1 === t; - }; - function Kr(t) { - var e = t.data, - n = t, - r = t; - while (i(r.componentInstance)) - (r = r.componentInstance._vnode), - r && r.data && (e = Xr(r.data, e)); - while (i((n = n.parent))) - n && n.data && (e = Xr(e, n.data)); - return Yr(e.staticClass, e.class); - } - function Xr(t, e) { - return { - staticClass: Qr(t.staticClass, e.staticClass), - class: i(t.class) ? [t.class, e.class] : e.class, - }; - } - function Yr(t, e) { - return i(t) || i(e) ? Qr(t, ti(e)) : ""; - } - function Qr(t, e) { - return t ? (e ? t + " " + e : t) : e || ""; - } - function ti(t) { - return Array.isArray(t) - ? ei(t) - : u(t) - ? ni(t) - : "string" === typeof t - ? t - : ""; - } - function ei(t) { - for (var e, n = "", r = 0, o = t.length; r < o; r++) - i((e = ti(t[r]))) && - "" !== e && - (n && (n += " "), (n += e)); - return n; - } - function ni(t) { - var e = ""; - for (var n in t) t[n] && (e && (e += " "), (e += n)); - return e; - } - var ri = { - svg: "http://www.w3.org/2000/svg", - math: "http://www.w3.org/1998/Math/MathML", - }, - ii = y( - "html,body,base,head,link,meta,style,title,address,article,aside,footer,header,h1,h2,h3,h4,h5,h6,hgroup,nav,section,div,dd,dl,dt,figcaption,figure,picture,hr,img,li,main,ol,p,pre,ul,a,b,abbr,bdi,bdo,br,cite,code,data,dfn,em,i,kbd,mark,q,rp,rt,rtc,ruby,s,samp,small,span,strong,sub,sup,time,u,var,wbr,area,audio,map,track,video,embed,object,param,source,canvas,script,noscript,del,ins,caption,col,colgroup,table,thead,tbody,td,th,tr,button,datalist,fieldset,form,input,label,legend,meter,optgroup,option,output,progress,select,textarea,details,dialog,menu,menuitem,summary,content,element,shadow,template,blockquote,iframe,tfoot" - ), - oi = y( - "svg,animate,circle,clippath,cursor,defs,desc,ellipse,filter,font-face,foreignobject,g,glyph,image,line,marker,mask,missing-glyph,path,pattern,polygon,polyline,rect,switch,symbol,text,textpath,tspan,use,view", - !0 - ), - ai = function (t) { - return ii(t) || oi(t); - }; - function si(t) { - return oi(t) ? "svg" : "math" === t ? "math" : void 0; - } - var ui = Object.create(null); - function ci(t) { - if (!K) return !0; - if (ai(t)) return !1; - if (((t = t.toLowerCase()), null != ui[t])) return ui[t]; - var e = document.createElement(t); - return t.indexOf("-") > -1 - ? (ui[t] = - e.constructor === window.HTMLUnknownElement || - e.constructor === window.HTMLElement) - : (ui[t] = /HTMLUnknownElement/.test(e.toString())); - } - var fi = y("text,number,password,search,email,tel,url"); - function li(t) { - if ("string" === typeof t) { - var e = document.querySelector(t); - return e || document.createElement("div"); - } - return t; - } - function di(t, e) { - var n = document.createElement(t); - return ( - "select" !== t || - (e.data && - e.data.attrs && - void 0 !== e.data.attrs.multiple && - n.setAttribute("multiple", "multiple")), - n - ); - } - function pi(t, e) { - return document.createElementNS(ri[t], e); - } - function hi(t) { - return document.createTextNode(t); - } - function vi(t) { - return document.createComment(t); - } - function yi(t, e, n) { - t.insertBefore(e, n); - } - function mi(t, e) { - t.removeChild(e); - } - function gi(t, e) { - t.appendChild(e); - } - function bi(t) { - return t.parentNode; - } - function _i(t) { - return t.nextSibling; - } - function wi(t) { - return t.tagName; - } - function xi(t, e) { - t.textContent = e; - } - function ki(t, e) { - t.setAttribute(e, ""); - } - var Si = Object.freeze({ - createElement: di, - createElementNS: pi, - createTextNode: hi, - createComment: vi, - insertBefore: yi, - removeChild: mi, - appendChild: gi, - parentNode: bi, - nextSibling: _i, - tagName: wi, - setTextContent: xi, - setStyleScope: ki, - }), - Ei = { - create: function (t, e) { - Oi(e); - }, - update: function (t, e) { - t.data.ref !== e.data.ref && (Oi(t, !0), Oi(e)); - }, - destroy: function (t) { - Oi(t, !0); - }, - }; - function Oi(t, e) { - var n = t.data.ref; - if (i(n)) { - var r = t.context, - o = t.componentInstance || t.elm, - a = r.$refs; - e - ? Array.isArray(a[n]) - ? g(a[n], o) - : a[n] === o && (a[n] = void 0) - : t.data.refInFor - ? Array.isArray(a[n]) - ? a[n].indexOf(o) < 0 && a[n].push(o) - : (a[n] = [o]) - : (a[n] = o); - } - } - var Ti = new bt("", {}, []), - Ai = ["create", "activate", "update", "remove", "destroy"]; - function Ci(t, e) { - return ( - t.key === e.key && - t.asyncFactory === e.asyncFactory && - ((t.tag === e.tag && - t.isComment === e.isComment && - i(t.data) === i(e.data) && - ji(t, e)) || - (o(t.isAsyncPlaceholder) && - r(e.asyncFactory.error))) - ); - } - function ji(t, e) { - if ("input" !== t.tag) return !0; - var n, - r = i((n = t.data)) && i((n = n.attrs)) && n.type, - o = i((n = e.data)) && i((n = n.attrs)) && n.type; - return r === o || (fi(r) && fi(o)); - } - function Ii(t, e, n) { - var r, - o, - a = {}; - for (r = e; r <= n; ++r) (o = t[r].key), i(o) && (a[o] = r); - return a; - } - function Mi(t) { - var e, - n, - a = {}, - u = t.modules, - c = t.nodeOps; - for (e = 0; e < Ai.length; ++e) - for (a[Ai[e]] = [], n = 0; n < u.length; ++n) - i(u[n][Ai[e]]) && a[Ai[e]].push(u[n][Ai[e]]); - function f(t) { - return new bt( - c.tagName(t).toLowerCase(), - {}, - [], - void 0, - t - ); - } - function l(t, e) { - function n() { - 0 === --n.listeners && d(t); - } - return (n.listeners = e), n; - } - function d(t) { - var e = c.parentNode(t); - i(e) && c.removeChild(e, t); - } - function p(t, e, n, r, a, s, u) { - if ( - (i(t.elm) && i(s) && (t = s[u] = kt(t)), - (t.isRootInsert = !a), - !h(t, e, n, r)) - ) { - var f = t.data, - l = t.children, - d = t.tag; - i(d) - ? ((t.elm = t.ns - ? c.createElementNS(t.ns, d) - : c.createElement(d, t)), - x(t), - b(t, l, e), - i(f) && w(t, e), - g(n, t.elm, r)) - : o(t.isComment) - ? ((t.elm = c.createComment(t.text)), - g(n, t.elm, r)) - : ((t.elm = c.createTextNode(t.text)), - g(n, t.elm, r)); - } - } - function h(t, e, n, r) { - var a = t.data; - if (i(a)) { - var s = i(t.componentInstance) && a.keepAlive; - if ( - (i((a = a.hook)) && i((a = a.init)) && a(t, !1), - i(t.componentInstance)) - ) - return ( - v(t, e), - g(n, t.elm, r), - o(s) && m(t, e, n, r), - !0 - ); - } - } - function v(t, e) { - i(t.data.pendingInsert) && - (e.push.apply(e, t.data.pendingInsert), - (t.data.pendingInsert = null)), - (t.elm = t.componentInstance.$el), - _(t) ? (w(t, e), x(t)) : (Oi(t), e.push(t)); - } - function m(t, e, n, r) { - var o, - s = t; - while (s.componentInstance) - if ( - ((s = s.componentInstance._vnode), - i((o = s.data)) && i((o = o.transition))) - ) { - for (o = 0; o < a.activate.length; ++o) - a.activate[o](Ti, s); - e.push(s); - break; - } - g(n, t.elm, r); - } - function g(t, e, n) { - i(t) && - (i(n) - ? c.parentNode(n) === t && - c.insertBefore(t, e, n) - : c.appendChild(t, e)); - } - function b(t, e, n) { - if (Array.isArray(e)) { - 0; - for (var r = 0; r < e.length; ++r) - p(e[r], n, t.elm, null, !0, e, r); - } else - s(t.text) && - c.appendChild( - t.elm, - c.createTextNode(String(t.text)) - ); - } - function _(t) { - while (t.componentInstance) - t = t.componentInstance._vnode; - return i(t.tag); - } - function w(t, n) { - for (var r = 0; r < a.create.length; ++r) - a.create[r](Ti, t); - (e = t.data.hook), - i(e) && - (i(e.create) && e.create(Ti, t), - i(e.insert) && n.push(t)); - } - function x(t) { - var e; - if (i((e = t.fnScopeId))) c.setStyleScope(t.elm, e); - else { - var n = t; - while (n) - i((e = n.context)) && - i((e = e.$options._scopeId)) && - c.setStyleScope(t.elm, e), - (n = n.parent); - } - i((e = In)) && - e !== t.context && - e !== t.fnContext && - i((e = e.$options._scopeId)) && - c.setStyleScope(t.elm, e); - } - function k(t, e, n, r, i, o) { - for (; r <= i; ++r) p(n[r], o, t, e, !1, n, r); - } - function S(t) { - var e, - n, - r = t.data; - if (i(r)) - for ( - i((e = r.hook)) && i((e = e.destroy)) && e(t), - e = 0; - e < a.destroy.length; - ++e - ) - a.destroy[e](t); - if (i((e = t.children))) - for (n = 0; n < t.children.length; ++n) - S(t.children[n]); - } - function E(t, e, n) { - for (; e <= n; ++e) { - var r = t[e]; - i(r) && (i(r.tag) ? (O(r), S(r)) : d(r.elm)); - } - } - function O(t, e) { - if (i(e) || i(t.data)) { - var n, - r = a.remove.length + 1; - for ( - i(e) ? (e.listeners += r) : (e = l(t.elm, r)), - i((n = t.componentInstance)) && - i((n = n._vnode)) && - i(n.data) && - O(n, e), - n = 0; - n < a.remove.length; - ++n - ) - a.remove[n](t, e); - i((n = t.data.hook)) && i((n = n.remove)) - ? n(t, e) - : e(); - } else d(t.elm); - } - function T(t, e, n, o, a) { - var s, - u, - f, - l, - d = 0, - h = 0, - v = e.length - 1, - y = e[0], - m = e[v], - g = n.length - 1, - b = n[0], - _ = n[g], - w = !a; - while (d <= v && h <= g) - r(y) - ? (y = e[++d]) - : r(m) - ? (m = e[--v]) - : Ci(y, b) - ? (C(y, b, o, n, h), (y = e[++d]), (b = n[++h])) - : Ci(m, _) - ? (C(m, _, o, n, g), (m = e[--v]), (_ = n[--g])) - : Ci(y, _) - ? (C(y, _, o, n, g), - w && - c.insertBefore( - t, - y.elm, - c.nextSibling(m.elm) - ), - (y = e[++d]), - (_ = n[--g])) - : Ci(m, b) - ? (C(m, b, o, n, h), - w && c.insertBefore(t, m.elm, y.elm), - (m = e[--v]), - (b = n[++h])) - : (r(s) && (s = Ii(e, d, v)), - (u = i(b.key) ? s[b.key] : A(b, e, d, v)), - r(u) - ? p(b, o, t, y.elm, !1, n, h) - : ((f = e[u]), - Ci(f, b) - ? (C(f, b, o, n, h), - (e[u] = void 0), - w && - c.insertBefore( - t, - f.elm, - y.elm - )) - : p(b, o, t, y.elm, !1, n, h)), - (b = n[++h])); - d > v - ? ((l = r(n[g + 1]) ? null : n[g + 1].elm), - k(t, l, n, h, g, o)) - : h > g && E(e, d, v); - } - function A(t, e, n, r) { - for (var o = n; o < r; o++) { - var a = e[o]; - if (i(a) && Ci(t, a)) return o; - } - } - function C(t, e, n, s, u, f) { - if (t !== e) { - i(e.elm) && i(s) && (e = s[u] = kt(e)); - var l = (e.elm = t.elm); - if (o(t.isAsyncPlaceholder)) - i(e.asyncFactory.resolved) - ? M(t.elm, e, n) - : (e.isAsyncPlaceholder = !0); - else if ( - o(e.isStatic) && - o(t.isStatic) && - e.key === t.key && - (o(e.isCloned) || o(e.isOnce)) - ) - e.componentInstance = t.componentInstance; - else { - var d, - p = e.data; - i(p) && - i((d = p.hook)) && - i((d = d.prepatch)) && - d(t, e); - var h = t.children, - v = e.children; - if (i(p) && _(e)) { - for (d = 0; d < a.update.length; ++d) - a.update[d](t, e); - i((d = p.hook)) && - i((d = d.update)) && - d(t, e); - } - r(e.text) - ? i(h) && i(v) - ? h !== v && T(l, h, v, n, f) - : i(v) - ? (i(t.text) && c.setTextContent(l, ""), - k(l, null, v, 0, v.length - 1, n)) - : i(h) - ? E(h, 0, h.length - 1) - : i(t.text) && c.setTextContent(l, "") - : t.text !== e.text && - c.setTextContent(l, e.text), - i(p) && - i((d = p.hook)) && - i((d = d.postpatch)) && - d(t, e); - } - } - } - function j(t, e, n) { - if (o(n) && i(t.parent)) - t.parent.data.pendingInsert = e; - else - for (var r = 0; r < e.length; ++r) - e[r].data.hook.insert(e[r]); - } - var I = y("attrs,class,staticClass,staticStyle,key"); - function M(t, e, n, r) { - var a, - s = e.tag, - u = e.data, - c = e.children; - if ( - ((r = r || (u && u.pre)), - (e.elm = t), - o(e.isComment) && i(e.asyncFactory)) - ) - return (e.isAsyncPlaceholder = !0), !0; - if ( - i(u) && - (i((a = u.hook)) && i((a = a.init)) && a(e, !0), - i((a = e.componentInstance))) - ) - return v(e, n), !0; - if (i(s)) { - if (i(c)) - if (t.hasChildNodes()) - if ( - i((a = u)) && - i((a = a.domProps)) && - i((a = a.innerHTML)) - ) { - if (a !== t.innerHTML) return !1; - } else { - for ( - var f = !0, l = t.firstChild, d = 0; - d < c.length; - d++ - ) { - if (!l || !M(l, c[d], n, r)) { - f = !1; - break; - } - l = l.nextSibling; - } - if (!f || l) return !1; - } - else b(e, c, n); - if (i(u)) { - var p = !1; - for (var h in u) - if (!I(h)) { - (p = !0), w(e, n); - break; - } - !p && u["class"] && me(u["class"]); - } - } else t.data !== e.text && (t.data = e.text); - return !0; - } - return function (t, e, n, s) { - if (!r(e)) { - var u = !1, - l = []; - if (r(t)) (u = !0), p(e, l); - else { - var d = i(t.nodeType); - if (!d && Ci(t, e)) C(t, e, l, null, null, s); - else { - if (d) { - if ( - (1 === t.nodeType && - t.hasAttribute(F) && - (t.removeAttribute(F), - (n = !0)), - o(n) && M(t, e, l)) - ) - return j(e, l, !0), t; - t = f(t); - } - var h = t.elm, - v = c.parentNode(h); - if ( - (p( - e, - l, - h._leaveCb ? null : v, - c.nextSibling(h) - ), - i(e.parent)) - ) { - var y = e.parent, - m = _(e); - while (y) { - for ( - var g = 0; - g < a.destroy.length; - ++g - ) - a.destroy[g](y); - if (((y.elm = e.elm), m)) { - for ( - var b = 0; - b < a.create.length; - ++b - ) - a.create[b](Ti, y); - var w = y.data.hook.insert; - if (w.merged) - for ( - var x = 1; - x < w.fns.length; - x++ - ) - w.fns[x](); - } else Oi(y); - y = y.parent; - } - } - i(v) ? E([t], 0, 0) : i(t.tag) && S(t); - } - } - return j(e, l, u), e.elm; - } - i(t) && S(t); - }; - } - var Li = { - create: Pi, - update: Pi, - destroy: function (t) { - Pi(t, Ti); - }, - }; - function Pi(t, e) { - (t.data.directives || e.data.directives) && Ni(t, e); - } - function Ni(t, e) { - var n, - r, - i, - o = t === Ti, - a = e === Ti, - s = $i(t.data.directives, t.context), - u = $i(e.data.directives, e.context), - c = [], - f = []; - for (n in u) - (r = s[n]), - (i = u[n]), - r - ? ((i.oldValue = r.value), - (i.oldArg = r.arg), - Fi(i, "update", e, t), - i.def && i.def.componentUpdated && f.push(i)) - : (Fi(i, "bind", e, t), - i.def && i.def.inserted && c.push(i)); - if (c.length) { - var l = function () { - for (var n = 0; n < c.length; n++) - Fi(c[n], "inserted", e, t); - }; - o ? xe(e, "insert", l) : l(); - } - if ( - (f.length && - xe(e, "postpatch", function () { - for (var n = 0; n < f.length; n++) - Fi(f[n], "componentUpdated", e, t); - }), - !o) - ) - for (n in s) u[n] || Fi(s[n], "unbind", t, t, a); - } - var Ri = Object.create(null); - function $i(t, e) { - var n, - r, - i = Object.create(null); - if (!t) return i; - for (n = 0; n < t.length; n++) - (r = t[n]), - r.modifiers || (r.modifiers = Ri), - (i[Di(r)] = r), - (r.def = Jt(e.$options, "directives", r.name, !0)); - return i; - } - function Di(t) { - return ( - t.rawName || - t.name + "." + Object.keys(t.modifiers || {}).join(".") - ); - } - function Fi(t, e, n, r, i) { - var o = t.def && t.def[e]; - if (o) - try { - o(n.elm, t, n, r, i); - } catch (Sa) { - ne( - Sa, - n.context, - "directive " + t.name + " " + e + " hook" - ); - } - } - var zi = [Ei, Li]; - function Ui(t, e) { - var n = e.componentOptions; - if ( - (!i(n) || !1 !== n.Ctor.options.inheritAttrs) && - (!r(t.data.attrs) || !r(e.data.attrs)) - ) { - var o, - a, - s, - u = e.elm, - c = t.data.attrs || {}, - f = e.data.attrs || {}; - for (o in (i(f.__ob__) && (f = e.data.attrs = I({}, f)), - f)) - (a = f[o]), - (s = c[o]), - s !== a && Bi(u, o, a, e.data.pre); - for (o in ((tt || nt) && - f.value !== c.value && - Bi(u, "value", f.value), - c)) - r(f[o]) && - (Gr(o) - ? u.removeAttributeNS(Wr, Zr(o)) - : Br(o) || u.removeAttribute(o)); - } - } - function Bi(t, e, n, r) { - r || t.tagName.indexOf("-") > -1 - ? Vi(t, e, n) - : qr(e) - ? Jr(n) - ? t.removeAttribute(e) - : ((n = - "allowfullscreen" === e && - "EMBED" === t.tagName - ? "true" - : e), - t.setAttribute(e, n)) - : Br(e) - ? t.setAttribute(e, Hr(e, n)) - : Gr(e) - ? Jr(n) - ? t.removeAttributeNS(Wr, Zr(e)) - : t.setAttributeNS(Wr, e, n) - : Vi(t, e, n); - } - function Vi(t, e, n) { - if (Jr(n)) t.removeAttribute(e); - else { - if ( - tt && - !et && - "TEXTAREA" === t.tagName && - "placeholder" === e && - "" !== n && - !t.__ieph - ) { - var r = function (e) { - e.stopImmediatePropagation(), - t.removeEventListener("input", r); - }; - t.addEventListener("input", r), (t.__ieph = !0); - } - t.setAttribute(e, n); - } - } - var Hi = { create: Ui, update: Ui }; - function qi(t, e) { - var n = e.elm, - o = e.data, - a = t.data; - if ( - !( - r(o.staticClass) && - r(o.class) && - (r(a) || (r(a.staticClass) && r(a.class))) - ) - ) { - var s = Kr(e), - u = n._transitionClasses; - i(u) && (s = Qr(s, ti(u))), - s !== n._prevClass && - (n.setAttribute("class", s), - (n._prevClass = s)); - } - } - var Wi, - Gi = { create: qi, update: qi }, - Zi = "__r", - Ji = "__c"; - function Ki(t) { - if (i(t[Zi])) { - var e = tt ? "change" : "input"; - (t[e] = [].concat(t[Zi], t[e] || [])), delete t[Zi]; - } - i(t[Ji]) && - ((t.change = [].concat(t[Ji], t.change || [])), - delete t[Ji]); - } - function Xi(t, e, n) { - var r = Wi; - return function i() { - var o = e.apply(null, arguments); - null !== o && to(t, i, n, r); - }; - } - var Yi = se && !(it && Number(it[1]) <= 53); - function Qi(t, e, n, r) { - if (Yi) { - var i = Zn, - o = e; - e = o._wrapper = function (t) { - if ( - t.target === t.currentTarget || - t.timeStamp >= i || - t.timeStamp <= 0 || - t.target.ownerDocument !== document - ) - return o.apply(this, arguments); - }; - } - Wi.addEventListener( - t, - e, - at ? { capture: n, passive: r } : n - ); - } - function to(t, e, n, r) { - (r || Wi).removeEventListener(t, e._wrapper || e, n); - } - function eo(t, e) { - if (!r(t.data.on) || !r(e.data.on)) { - var n = e.data.on || {}, - i = t.data.on || {}; - (Wi = e.elm), - Ki(n), - we(n, i, Qi, to, Xi, e.context), - (Wi = void 0); - } - } - var no, - ro = { create: eo, update: eo }; - function io(t, e) { - if (!r(t.data.domProps) || !r(e.data.domProps)) { - var n, - o, - a = e.elm, - s = t.data.domProps || {}, - u = e.data.domProps || {}; - for (n in (i(u.__ob__) && - (u = e.data.domProps = I({}, u)), - s)) - n in u || (a[n] = ""); - for (n in u) { - if ( - ((o = u[n]), - "textContent" === n || "innerHTML" === n) - ) { - if ( - (e.children && (e.children.length = 0), - o === s[n]) - ) - continue; - 1 === a.childNodes.length && - a.removeChild(a.childNodes[0]); - } - if ("value" === n && "PROGRESS" !== a.tagName) { - a._value = o; - var c = r(o) ? "" : String(o); - oo(a, c) && (a.value = c); - } else if ( - "innerHTML" === n && - oi(a.tagName) && - r(a.innerHTML) - ) { - (no = no || document.createElement("div")), - (no.innerHTML = "" + o + ""); - var f = no.firstChild; - while (a.firstChild) - a.removeChild(a.firstChild); - while (f.firstChild) - a.appendChild(f.firstChild); - } else if (o !== s[n]) - try { - a[n] = o; - } catch (Sa) {} - } - } - } - function oo(t, e) { - return ( - !t.composing && - ("OPTION" === t.tagName || ao(t, e) || so(t, e)) - ); - } - function ao(t, e) { - var n = !0; - try { - n = document.activeElement !== t; - } catch (Sa) {} - return n && t.value !== e; - } - function so(t, e) { - var n = t.value, - r = t._vModifiers; - if (i(r)) { - if (r.number) return v(n) !== v(e); - if (r.trim) return n.trim() !== e.trim(); - } - return n !== e; - } - var uo = { create: io, update: io }, - co = w(function (t) { - var e = {}, - n = /;(?![^(]*\))/g, - r = /:(.+)/; - return ( - t.split(n).forEach(function (t) { - if (t) { - var n = t.split(r); - n.length > 1 && - (e[n[0].trim()] = n[1].trim()); - } - }), - e - ); - }); - function fo(t) { - var e = lo(t.style); - return t.staticStyle ? I(t.staticStyle, e) : e; - } - function lo(t) { - return Array.isArray(t) - ? M(t) - : "string" === typeof t - ? co(t) - : t; - } - function po(t, e) { - var n, - r = {}; - if (e) { - var i = t; - while (i.componentInstance) - (i = i.componentInstance._vnode), - i && i.data && (n = fo(i.data)) && I(r, n); - } - (n = fo(t.data)) && I(r, n); - var o = t; - while ((o = o.parent)) - o.data && (n = fo(o.data)) && I(r, n); - return r; - } - var ho, - vo = /^--/, - yo = /\s*!important$/, - mo = function (t, e, n) { - if (vo.test(e)) t.style.setProperty(e, n); - else if (yo.test(n)) - t.style.setProperty( - O(e), - n.replace(yo, ""), - "important" - ); - else { - var r = bo(e); - if (Array.isArray(n)) - for (var i = 0, o = n.length; i < o; i++) - t.style[r] = n[i]; - else t.style[r] = n; - } - }, - go = ["Webkit", "Moz", "ms"], - bo = w(function (t) { - if ( - ((ho = ho || document.createElement("div").style), - (t = k(t)), - "filter" !== t && t in ho) - ) - return t; - for ( - var e = t.charAt(0).toUpperCase() + t.slice(1), - n = 0; - n < go.length; - n++ - ) { - var r = go[n] + e; - if (r in ho) return r; - } - }); - function _o(t, e) { - var n = e.data, - o = t.data; - if ( - !( - r(n.staticStyle) && - r(n.style) && - r(o.staticStyle) && - r(o.style) - ) - ) { - var a, - s, - u = e.elm, - c = o.staticStyle, - f = o.normalizedStyle || o.style || {}, - l = c || f, - d = lo(e.data.style) || {}; - e.data.normalizedStyle = i(d.__ob__) ? I({}, d) : d; - var p = po(e, !0); - for (s in l) r(p[s]) && mo(u, s, ""); - for (s in p) - (a = p[s]), - a !== l[s] && mo(u, s, null == a ? "" : a); - } - } - var wo = { create: _o, update: _o }, - xo = /\s+/; - function ko(t, e) { - if (e && (e = e.trim())) - if (t.classList) - e.indexOf(" ") > -1 - ? e.split(xo).forEach(function (e) { - return t.classList.add(e); - }) - : t.classList.add(e); - else { - var n = " " + (t.getAttribute("class") || "") + " "; - n.indexOf(" " + e + " ") < 0 && - t.setAttribute("class", (n + e).trim()); - } - } - function So(t, e) { - if (e && (e = e.trim())) - if (t.classList) - e.indexOf(" ") > -1 - ? e.split(xo).forEach(function (e) { - return t.classList.remove(e); - }) - : t.classList.remove(e), - t.classList.length || - t.removeAttribute("class"); - else { - var n = " " + (t.getAttribute("class") || "") + " ", - r = " " + e + " "; - while (n.indexOf(r) >= 0) n = n.replace(r, " "); - (n = n.trim()), - n - ? t.setAttribute("class", n) - : t.removeAttribute("class"); - } - } - function Eo(t) { - if (t) { - if ("object" === typeof t) { - var e = {}; - return ( - !1 !== t.css && I(e, Oo(t.name || "v")), - I(e, t), - e - ); - } - return "string" === typeof t ? Oo(t) : void 0; - } - } - var Oo = w(function (t) { - return { - enterClass: t + "-enter", - enterToClass: t + "-enter-to", - enterActiveClass: t + "-enter-active", - leaveClass: t + "-leave", - leaveToClass: t + "-leave-to", - leaveActiveClass: t + "-leave-active", - }; - }), - To = K && !et, - Ao = "transition", - Co = "animation", - jo = "transition", - Io = "transitionend", - Mo = "animation", - Lo = "animationend"; - To && - (void 0 === window.ontransitionend && - void 0 !== window.onwebkittransitionend && - ((jo = "WebkitTransition"), - (Io = "webkitTransitionEnd")), - void 0 === window.onanimationend && - void 0 !== window.onwebkitanimationend && - ((Mo = "WebkitAnimation"), - (Lo = "webkitAnimationEnd"))); - var Po = K - ? window.requestAnimationFrame - ? window.requestAnimationFrame.bind(window) - : setTimeout - : function (t) { - return t(); - }; - function No(t) { - Po(function () { - Po(t); - }); - } - function Ro(t, e) { - var n = t._transitionClasses || (t._transitionClasses = []); - n.indexOf(e) < 0 && (n.push(e), ko(t, e)); - } - function $o(t, e) { - t._transitionClasses && g(t._transitionClasses, e), - So(t, e); - } - function Do(t, e, n) { - var r = zo(t, e), - i = r.type, - o = r.timeout, - a = r.propCount; - if (!i) return n(); - var s = i === Ao ? Io : Lo, - u = 0, - c = function () { - t.removeEventListener(s, f), n(); - }, - f = function (e) { - e.target === t && ++u >= a && c(); - }; - setTimeout(function () { - u < a && c(); - }, o + 1), - t.addEventListener(s, f); - } - var Fo = /\b(transform|all)(,|$)/; - function zo(t, e) { - var n, - r = window.getComputedStyle(t), - i = (r[jo + "Delay"] || "").split(", "), - o = (r[jo + "Duration"] || "").split(", "), - a = Uo(i, o), - s = (r[Mo + "Delay"] || "").split(", "), - u = (r[Mo + "Duration"] || "").split(", "), - c = Uo(s, u), - f = 0, - l = 0; - e === Ao - ? a > 0 && ((n = Ao), (f = a), (l = o.length)) - : e === Co - ? c > 0 && ((n = Co), (f = c), (l = u.length)) - : ((f = Math.max(a, c)), - (n = f > 0 ? (a > c ? Ao : Co) : null), - (l = n ? (n === Ao ? o.length : u.length) : 0)); - var d = n === Ao && Fo.test(r[jo + "Property"]); - return { - type: n, - timeout: f, - propCount: l, - hasTransform: d, - }; - } - function Uo(t, e) { - while (t.length < e.length) t = t.concat(t); - return Math.max.apply( - null, - e.map(function (e, n) { - return Bo(e) + Bo(t[n]); - }) - ); - } - function Bo(t) { - return 1e3 * Number(t.slice(0, -1).replace(",", ".")); - } - function Vo(t, e) { - var n = t.elm; - i(n._leaveCb) && - ((n._leaveCb.cancelled = !0), n._leaveCb()); - var o = Eo(t.data.transition); - if (!r(o) && !i(n._enterCb) && 1 === n.nodeType) { - var a = o.css, - s = o.type, - c = o.enterClass, - f = o.enterToClass, - l = o.enterActiveClass, - d = o.appearClass, - p = o.appearToClass, - h = o.appearActiveClass, - y = o.beforeEnter, - m = o.enter, - g = o.afterEnter, - b = o.enterCancelled, - _ = o.beforeAppear, - w = o.appear, - x = o.afterAppear, - k = o.appearCancelled, - S = o.duration, - E = In, - O = In.$vnode; - while (O && O.parent) (E = O.context), (O = O.parent); - var T = !E._isMounted || !t.isRootInsert; - if (!T || w || "" === w) { - var A = T && d ? d : c, - C = T && h ? h : l, - j = T && p ? p : f, - I = (T && _) || y, - M = T && "function" === typeof w ? w : m, - L = (T && x) || g, - P = (T && k) || b, - N = v(u(S) ? S.enter : S); - 0; - var R = !1 !== a && !et, - $ = Wo(M), - F = (n._enterCb = D(function () { - R && ($o(n, j), $o(n, C)), - F.cancelled - ? (R && $o(n, A), P && P(n)) - : L && L(n), - (n._enterCb = null); - })); - t.data.show || - xe(t, "insert", function () { - var e = n.parentNode, - r = - e && - e._pending && - e._pending[t.key]; - r && - r.tag === t.tag && - r.elm._leaveCb && - r.elm._leaveCb(), - M && M(n, F); - }), - I && I(n), - R && - (Ro(n, A), - Ro(n, C), - No(function () { - $o(n, A), - F.cancelled || - (Ro(n, j), - $ || - (qo(N) - ? setTimeout(F, N) - : Do(n, s, F))); - })), - t.data.show && (e && e(), M && M(n, F)), - R || $ || F(); - } - } - } - function Ho(t, e) { - var n = t.elm; - i(n._enterCb) && - ((n._enterCb.cancelled = !0), n._enterCb()); - var o = Eo(t.data.transition); - if (r(o) || 1 !== n.nodeType) return e(); - if (!i(n._leaveCb)) { - var a = o.css, - s = o.type, - c = o.leaveClass, - f = o.leaveToClass, - l = o.leaveActiveClass, - d = o.beforeLeave, - p = o.leave, - h = o.afterLeave, - y = o.leaveCancelled, - m = o.delayLeave, - g = o.duration, - b = !1 !== a && !et, - _ = Wo(p), - w = v(u(g) ? g.leave : g); - 0; - var x = (n._leaveCb = D(function () { - n.parentNode && - n.parentNode._pending && - (n.parentNode._pending[t.key] = null), - b && ($o(n, f), $o(n, l)), - x.cancelled - ? (b && $o(n, c), y && y(n)) - : (e(), h && h(n)), - (n._leaveCb = null); - })); - m ? m(k) : k(); - } - function k() { - x.cancelled || - (!t.data.show && - n.parentNode && - ((n.parentNode._pending || - (n.parentNode._pending = {}))[t.key] = t), - d && d(n), - b && - (Ro(n, c), - Ro(n, l), - No(function () { - $o(n, c), - x.cancelled || - (Ro(n, f), - _ || - (qo(w) - ? setTimeout(x, w) - : Do(n, s, x))); - })), - p && p(n, x), - b || _ || x()); - } - } - function qo(t) { - return "number" === typeof t && !isNaN(t); - } - function Wo(t) { - if (r(t)) return !1; - var e = t.fns; - return i(e) - ? Wo(Array.isArray(e) ? e[0] : e) - : (t._length || t.length) > 1; - } - function Go(t, e) { - !0 !== e.data.show && Vo(e); - } - var Zo = K - ? { - create: Go, - activate: Go, - remove: function (t, e) { - !0 !== t.data.show ? Ho(t, e) : e(); - }, - } - : {}, - Jo = [Hi, Gi, ro, uo, wo, Zo], - Ko = Jo.concat(zi), - Xo = Mi({ nodeOps: Si, modules: Ko }); - et && - document.addEventListener("selectionchange", function () { - var t = document.activeElement; - t && t.vmodel && oa(t, "input"); - }); - var Yo = { - inserted: function (t, e, n, r) { - "select" === n.tag - ? (r.elm && !r.elm._vOptions - ? xe(n, "postpatch", function () { - Yo.componentUpdated(t, e, n); - }) - : Qo(t, e, n.context), - (t._vOptions = [].map.call(t.options, na))) - : ("textarea" === n.tag || fi(t.type)) && - ((t._vModifiers = e.modifiers), - e.modifiers.lazy || - (t.addEventListener("compositionstart", ra), - t.addEventListener("compositionend", ia), - t.addEventListener("change", ia), - et && (t.vmodel = !0))); - }, - componentUpdated: function (t, e, n) { - if ("select" === n.tag) { - Qo(t, e, n.context); - var r = t._vOptions, - i = (t._vOptions = [].map.call(t.options, na)); - if ( - i.some(function (t, e) { - return !R(t, r[e]); - }) - ) { - var o = t.multiple - ? e.value.some(function (t) { - return ea(t, i); - }) - : e.value !== e.oldValue && ea(e.value, i); - o && oa(t, "change"); - } - } - }, - }; - function Qo(t, e, n) { - ta(t, e, n), - (tt || nt) && - setTimeout(function () { - ta(t, e, n); - }, 0); - } - function ta(t, e, n) { - var r = e.value, - i = t.multiple; - if (!i || Array.isArray(r)) { - for (var o, a, s = 0, u = t.options.length; s < u; s++) - if (((a = t.options[s]), i)) - (o = $(r, na(a)) > -1), - a.selected !== o && (a.selected = o); - else if (R(na(a), r)) - return void ( - t.selectedIndex !== s && - (t.selectedIndex = s) - ); - i || (t.selectedIndex = -1); - } - } - function ea(t, e) { - return e.every(function (e) { - return !R(e, t); - }); - } - function na(t) { - return "_value" in t ? t._value : t.value; - } - function ra(t) { - t.target.composing = !0; - } - function ia(t) { - t.target.composing && - ((t.target.composing = !1), oa(t.target, "input")); - } - function oa(t, e) { - var n = document.createEvent("HTMLEvents"); - n.initEvent(e, !0, !0), t.dispatchEvent(n); - } - function aa(t) { - return !t.componentInstance || (t.data && t.data.transition) - ? t - : aa(t.componentInstance._vnode); - } - var sa = { - bind: function (t, e, n) { - var r = e.value; - n = aa(n); - var i = n.data && n.data.transition, - o = (t.__vOriginalDisplay = - "none" === t.style.display - ? "" - : t.style.display); - r && i - ? ((n.data.show = !0), - Vo(n, function () { - t.style.display = o; - })) - : (t.style.display = r ? o : "none"); - }, - update: function (t, e, n) { - var r = e.value, - i = e.oldValue; - if (!r !== !i) { - n = aa(n); - var o = n.data && n.data.transition; - o - ? ((n.data.show = !0), - r - ? Vo(n, function () { - t.style.display = - t.__vOriginalDisplay; - }) - : Ho(n, function () { - t.style.display = "none"; - })) - : (t.style.display = r - ? t.__vOriginalDisplay - : "none"); - } - }, - unbind: function (t, e, n, r, i) { - i || (t.style.display = t.__vOriginalDisplay); - }, - }, - ua = { model: Yo, show: sa }, - ca = { - name: String, - appear: Boolean, - css: Boolean, - mode: String, - type: String, - enterClass: String, - leaveClass: String, - enterToClass: String, - leaveToClass: String, - enterActiveClass: String, - leaveActiveClass: String, - appearClass: String, - appearActiveClass: String, - appearToClass: String, - duration: [Number, String, Object], - }; - function fa(t) { - var e = t && t.componentOptions; - return e && e.Ctor.options.abstract - ? fa(Sn(e.children)) - : t; - } - function la(t) { - var e = {}, - n = t.$options; - for (var r in n.propsData) e[r] = t[r]; - var i = n._parentListeners; - for (var o in i) e[k(o)] = i[o]; - return e; - } - function da(t, e) { - if (/\d-keep-alive$/.test(e.tag)) - return t("keep-alive", { - props: e.componentOptions.propsData, - }); - } - function pa(t) { - while ((t = t.parent)) if (t.data.transition) return !0; - } - function ha(t, e) { - return e.key === t.key && e.tag === t.tag; - } - var va = function (t) { - return t.tag || Pe(t); - }, - ya = function (t) { - return "show" === t.name; - }, - ma = { - name: "transition", - props: ca, - abstract: !0, - render: function (t) { - var e = this, - n = this.$slots.default; - if (n && ((n = n.filter(va)), n.length)) { - 0; - var r = this.mode; - 0; - var i = n[0]; - if (pa(this.$vnode)) return i; - var o = fa(i); - if (!o) return i; - if (this._leaving) return da(t, i); - var a = "__transition-" + this._uid + "-"; - o.key = - null == o.key - ? o.isComment - ? a + "comment" - : a + o.tag - : s(o.key) - ? 0 === String(o.key).indexOf(a) - ? o.key - : a + o.key - : o.key; - var u = ((o.data || (o.data = {})).transition = - la(this)), - c = this._vnode, - f = fa(c); - if ( - (o.data.directives && - o.data.directives.some(ya) && - (o.data.show = !0), - f && - f.data && - !ha(o, f) && - !Pe(f) && - (!f.componentInstance || - !f.componentInstance._vnode - .isComment)) - ) { - var l = (f.data.transition = I({}, u)); - if ("out-in" === r) - return ( - (this._leaving = !0), - xe(l, "afterLeave", function () { - (e._leaving = !1), - e.$forceUpdate(); - }), - da(t, i) - ); - if ("in-out" === r) { - if (Pe(o)) return c; - var d, - p = function () { - d(); - }; - xe(u, "afterEnter", p), - xe(u, "enterCancelled", p), - xe(l, "delayLeave", function (t) { - d = t; - }); - } - } - return i; - } - }, - }, - ga = I({ tag: String, moveClass: String }, ca); - delete ga.mode; - var ba = { - props: ga, - beforeMount: function () { - var t = this, - e = this._update; - this._update = function (n, r) { - var i = Mn(t); - t.__patch__(t._vnode, t.kept, !1, !0), - (t._vnode = t.kept), - i(), - e.call(t, n, r); - }; - }, - render: function (t) { - for ( - var e = this.tag || this.$vnode.data.tag || "span", - n = Object.create(null), - r = (this.prevChildren = this.children), - i = this.$slots.default || [], - o = (this.children = []), - a = la(this), - s = 0; - s < i.length; - s++ - ) { - var u = i[s]; - if (u.tag) - if ( - null != u.key && - 0 !== String(u.key).indexOf("__vlist") - ) - o.push(u), - (n[u.key] = u), - ((u.data || (u.data = {})).transition = - a); - else; - } - if (r) { - for (var c = [], f = [], l = 0; l < r.length; l++) { - var d = r[l]; - (d.data.transition = a), - (d.data.pos = - d.elm.getBoundingClientRect()), - n[d.key] ? c.push(d) : f.push(d); - } - (this.kept = t(e, null, c)), (this.removed = f); - } - return t(e, null, o); - }, - updated: function () { - var t = this.prevChildren, - e = this.moveClass || (this.name || "v") + "-move"; - t.length && - this.hasMove(t[0].elm, e) && - (t.forEach(_a), - t.forEach(wa), - t.forEach(xa), - (this._reflow = document.body.offsetHeight), - t.forEach(function (t) { - if (t.data.moved) { - var n = t.elm, - r = n.style; - Ro(n, e), - (r.transform = - r.WebkitTransform = - r.transitionDuration = - ""), - n.addEventListener( - Io, - (n._moveCb = function t(r) { - (r && r.target !== n) || - (r && - !/transform$/.test( - r.propertyName - )) || - (n.removeEventListener( - Io, - t - ), - (n._moveCb = null), - $o(n, e)); - }) - ); - } - })); - }, - methods: { - hasMove: function (t, e) { - if (!To) return !1; - if (this._hasMove) return this._hasMove; - var n = t.cloneNode(); - t._transitionClasses && - t._transitionClasses.forEach(function (t) { - So(n, t); - }), - ko(n, e), - (n.style.display = "none"), - this.$el.appendChild(n); - var r = zo(n); - return ( - this.$el.removeChild(n), - (this._hasMove = r.hasTransform) - ); - }, - }, - }; - function _a(t) { - t.elm._moveCb && t.elm._moveCb(), - t.elm._enterCb && t.elm._enterCb(); - } - function wa(t) { - t.data.newPos = t.elm.getBoundingClientRect(); - } - function xa(t) { - var e = t.data.pos, - n = t.data.newPos, - r = e.left - n.left, - i = e.top - n.top; - if (r || i) { - t.data.moved = !0; - var o = t.elm.style; - (o.transform = o.WebkitTransform = - "translate(" + r + "px," + i + "px)"), - (o.transitionDuration = "0s"); - } - } - var ka = { Transition: ma, TransitionGroup: ba }; - (Sr.config.mustUseProp = Ur), - (Sr.config.isReservedTag = ai), - (Sr.config.isReservedAttr = Fr), - (Sr.config.getTagNamespace = si), - (Sr.config.isUnknownElement = ci), - I(Sr.options.directives, ua), - I(Sr.options.components, ka), - (Sr.prototype.__patch__ = K ? Xo : L), - (Sr.prototype.$mount = function (t, e) { - return (t = t && K ? li(t) : void 0), Nn(this, t, e); - }), - K && - setTimeout(function () { - B.devtools && ct && ct.emit("init", Sr); - }, 0), - (e["a"] = Sr); - }.call(this, n("c8ba"))); - }, - "2b4c": function (t, e, n) { - var r = n("5537")("wks"), - i = n("ca5a"), - o = n("7726").Symbol, - a = "function" == typeof o, - s = (t.exports = function (t) { - return ( - r[t] || - (r[t] = (a && o[t]) || (a ? o : i)("Symbol." + t)) - ); - }); - s.store = r; - }, - "2d00": function (t, e) { - t.exports = !1; - }, - "2d7d": function (t, e, n) { - t.exports = n("b347"); - }, - "2d95": function (t, e) { - var n = {}.toString; - t.exports = function (t) { - return n.call(t).slice(8, -1); - }; - }, - "2ea1": function (t, e, n) { - var r = n("6f8a"); - t.exports = function (t, e) { - if (!r(t)) return t; - var n, i; - if ( - e && - "function" == typeof (n = t.toString) && - !r((i = n.call(t))) - ) - return i; - if ("function" == typeof (n = t.valueOf) && !r((i = n.call(t)))) - return i; - if ( - !e && - "function" == typeof (n = t.toString) && - !r((i = n.call(t))) - ) - return i; - throw TypeError("Can't convert object to primitive value"); - }; - }, - "308d": function (t, e, n) { - "use strict"; - var r = n("67bb"), - i = n.n(r), - o = n("5d58"), - a = n.n(o); - function s(t) { - return ( - (s = - "function" == typeof i.a && "symbol" == typeof a.a - ? function (t) { - return typeof t; - } - : function (t) { - return t && - "function" == typeof i.a && - t.constructor === i.a && - t !== i.a.prototype - ? "symbol" - : typeof t; - }), - s(t) - ); - } - function u(t) { - if (void 0 === t) - throw new ReferenceError( - "this hasn't been initialised - super() hasn't been called" - ); - return t; - } - function c(t, e) { - if (e && ("object" === s(e) || "function" === typeof e)) - return e; - if (void 0 !== e) - throw new TypeError( - "Derived constructors may only return object or undefined" - ); - return u(t); - } - n.d(e, "a", function () { - return c; - }); - }, - "31c2": function (t, e) { - e.f = Object.getOwnPropertySymbols; - }, - "31f4": function (t, e) { - t.exports = function (t, e, n) { - var r = void 0 === n; - switch (e.length) { - case 0: - return r ? t() : t.call(n); - case 1: - return r ? t(e[0]) : t.call(n, e[0]); - case 2: - return r ? t(e[0], e[1]) : t.call(n, e[0], e[1]); - case 3: - return r - ? t(e[0], e[1], e[2]) - : t.call(n, e[0], e[1], e[2]); - case 4: - return r - ? t(e[0], e[1], e[2], e[3]) - : t.call(n, e[0], e[1], e[2], e[3]); - } - return t.apply(n, e); - }; - }, - "32e9": function (t, e, n) { - var r = n("86cc"), - i = n("4630"); - t.exports = n("9e1e") - ? function (t, e, n) { - return r.f(t, e, i(1, n)); - } - : function (t, e, n) { - return (t[e] = n), t; - }; - }, - 3360: function (t, e, n) { - "use strict"; - Object.defineProperty(e, "__esModule", { value: !0 }), - (e.default = void 0); - var r = n("78ef"), - i = function () { - for ( - var t = arguments.length, e = new Array(t), n = 0; - n < t; - n++ - ) - e[n] = arguments[n]; - return (0, r.withParams)({ type: "and" }, function () { - for ( - var t = this, - n = arguments.length, - r = new Array(n), - i = 0; - i < n; - i++ - ) - r[i] = arguments[i]; - return ( - e.length > 0 && - e.reduce(function (e, n) { - return e && n.apply(t, r); - }, !0) - ); - }); - }; - e.default = i; - }, - "33a4": function (t, e, n) { - var r = n("84f2"), - i = n("2b4c")("iterator"), - o = Array.prototype; - t.exports = function (t) { - return void 0 !== t && (r.Array === t || o[i] === t); - }; - }, - "376c": function (t, e, n) { - "use strict"; - (function (t) { - n.d(e, "a", function () { - return r; - }); - //! TrackJS JavaScript error monitoring agent. - //! COPYRIGHT (c) 2021 ALL RIGHTS RESERVED - //! See License at https://trackjs.com/terms/ - var r = (function (e, n, r) { - var i = function (t, e) { - (this.config = t), - (this.onError = e), - t.enabled && this.watch(); - }; - i.prototype = { - watch: function () { - h.forEach( - ["EventTarget", "Node", "XMLHttpRequest"], - function (t) { - h.has( - e, - t + ".prototype.addEventListener" - ) && - h.hasOwn( - e[t].prototype, - "addEventListener" - ) && - this.wrapEventTarget(e[t].prototype); - }, - this - ), - this.wrapTimer("setTimeout"), - this.wrapTimer("setInterval"); - }, - wrap: function (t) { - function e() { - try { - return t.apply(this, arguments); - } catch (e) { - throw ( - (i.onError("catch", e, { - bindTime: n, - bindStack: r, - }), - h.wrapError(e)) - ); - } - } - var n, - r, - i = this; - try { - if ( - !h.isFunction(t) || - h.hasOwn(t, "__trackjs__") - ) - return t; - if (h.hasOwn(t, "__trackjs_state__")) - return t.__trackjs_state__; - } catch (a) { - return t; - } - if (i.config.bindStack) - try { - throw Error(); - } catch (a) { - (r = a.stack), (n = h.isoNow()); - } - for (var o in t) h.hasOwn(t, o) && (e[o] = t[o]); - return ( - (e.prototype = t.prototype), - (e.__trackjs__ = !0), - (t.__trackjs_state__ = e) - ); - }, - wrapEventTarget: function (t) { - var n = this; - h.has(t, "addEventListener.call") && - h.has(t, "removeEventListener.call") && - (h.patch(t, "addEventListener", function (t) { - return function (r, i, o, a) { - try { - h.has(i, "handleEvent") && - (i.handleEvent = n.wrap( - i.handleEvent - )); - } catch (e) {} - return t.call(this, r, n.wrap(i), o, a); - }; - }), - h.patch(t, "removeEventListener", function (t) { - return function (e, n, r, i) { - try { - n = n && (n.__trackjs_state__ || n); - } catch (o) {} - return t.call(this, e, n, r, i); - }; - })); - }, - wrapTimer: function (t) { - var n = this; - h.patch(e, t, function (t) { - return function (e, r) { - var i = - Array.prototype.slice.call( - arguments - ), - o = i[0]; - return ( - h.isFunction(o) && (i[0] = n.wrap(o)), - h.has(t, "apply") - ? t.apply(this, i) - : t(i[0], i[1]) - ); - }; - }); - }, - }; - var o = function (t) { - this.initCurrent(t) || - console.warn("[TrackJS] invalid config"); - }; - o.prototype = { - current: {}, - initOnly: { - application: !0, - cookie: !0, - enabled: !0, - token: !0, - callback: { enabled: !0 }, - console: { enabled: !0 }, - navigation: { enabled: !0 }, - network: { enabled: !0, fetch: !0 }, - visitor: { enabled: !0 }, - window: { enabled: !0, promise: !0 }, - }, - defaults: { - application: "", - cookie: !1, - dedupe: !0, - dependencies: !0, - enabled: !0, - forwardingDomain: "", - errorURL: "https://capture.trackjs.com/capture", - errorNoSSLURL: "http://capture.trackjs.com/capture", - faultURL: "https://usage.trackjs.com/fault.gif", - usageURL: "https://usage.trackjs.com/usage.gif", - onError: function () { - return !0; - }, - serialize: function (t) { - function e(t) { - var e = "<" + t.tagName.toLowerCase(); - t = t.attributes || []; - for (var n = 0; n < t.length; n++) - e += - " " + - t[n].name + - '="' + - t[n].value + - '"'; - return e + ">"; - } - if ("" === t) return "Empty String"; - if (t === r) return "undefined"; - if ( - h.isString(t) || - h.isNumber(t) || - h.isBoolean(t) || - h.isFunction(t) - ) - return "" + t; - if (h.isElement(t)) return e(t); - if ("symbol" === typeof t) - return Symbol.prototype.toString.call(t); - var n; - try { - n = JSON.stringify(t, function (t, n) { - return n === r - ? "undefined" - : h.isNumber(n) && isNaN(n) - ? "NaN" - : h.isError(n) - ? { - name: n.name, - message: n.message, - stack: n.stack, - } - : h.isElement(n) - ? e(n) - : n; - }); - } catch (o) { - for (var i in ((n = ""), t)) - if (t.hasOwnProperty(i)) - try { - n += - ',"' + - i + - '":"' + - t[i] + - '"'; - } catch (a) {} - n = n - ? "{" + n.replace(",", "") + "}" - : "Unserializable Object"; - } - return n - .replace(/"undefined"/g, "undefined") - .replace(/"NaN"/g, "NaN"); - }, - sessionId: "", - token: "", - userId: "", - version: "", - callback: { enabled: !0, bindStack: !1 }, - console: { - enabled: !0, - display: !0, - error: !0, - warn: !1, - watch: [ - "log", - "debug", - "info", - "warn", - "error", - ], - }, - navigation: { enabled: !0 }, - network: { enabled: !0, error: !0, fetch: !0 }, - visitor: { enabled: !0 }, - window: { enabled: !0, promise: !0 }, - }, - initCurrent: function (t) { - return ( - this.removeEmpty(t), - this.validate( - t, - this.defaults, - "[TrackJS] config", - {} - ) - ? ((this.current = h.defaultsDeep( - {}, - t, - this.defaults - )), - !0) - : ((this.current = h.defaultsDeep( - {}, - this.defaults - )), - !1) - ); - }, - setCurrent: function (t) { - return ( - !!this.validate( - t, - this.defaults, - "[TrackJS] config", - this.initOnly - ) && - ((this.current = h.defaultsDeep( - {}, - t, - this.current - )), - !0) - ); - }, - removeEmpty: function (t) { - for (var e in t) - t.hasOwnProperty(e) && - t[e] === r && - delete t[e]; - }, - validate: function (t, e, n, r) { - var i = !0; - for (var o in ((n = n || ""), (r = r || {}), t)) - if (t.hasOwnProperty(o)) - if (e.hasOwnProperty(o)) { - var a = typeof e[o]; - a !== typeof t[o] - ? (console.warn( - n + - "." + - o + - ": property must be type " + - a + - "." - ), - (i = !1)) - : "[object Array]" !== - Object.prototype.toString.call( - t[o] - ) || - this.validateArray( - t[o], - e[o], - n + "." + o - ) - ? "[object Object]" === - Object.prototype.toString.call( - t[o] - ) - ? (i = this.validate( - t[o], - e[o], - n + "." + o, - r[o] - )) - : r.hasOwnProperty(o) && - (console.warn( - n + - "." + - o + - ": property cannot be set after load." - ), - (i = !1)) - : (i = !1); - } else - console.warn( - n + - "." + - o + - ": property not supported." - ), - (i = !1); - return i; - }, - validateArray: function (t, e, n) { - var r = !0; - n = n || ""; - for (var i = 0; i < t.length; i++) - h.contains(e, t[i]) || - (console.warn( - n + - "[" + - i + - "]: invalid value: " + - t[i] + - "." - ), - (r = !1)); - return r; - }, - }; - var a = function (t, e, n, r, i, o, a) { - (this.util = t), - (this.log = e), - (this.onError = n), - (this.onFault = r), - (this.serialize = i), - a.enabled && - (o.console = this.wrapConsoleObject( - o.console, - a - )); - }; - a.prototype = { - wrapConsoleObject: function (t, e) { - t = t || {}; - var n, - r = t.log || function () {}, - i = this; - for (n = 0; n < e.watch.length; n++) - (function (n) { - var o = t[n] || r; - t[n] = function () { - try { - var r = - Array.prototype.slice.call( - arguments - ); - if ( - (i.log.add("c", { - timestamp: i.util.isoNow(), - severity: n, - message: i.serialize( - 1 === r.length - ? r[0] - : r - ), - }), - e[n]) - ) - if ( - h.isError(r[0]) && - 1 === r.length - ) - i.onError("console", r[0]); - else - try { - throw Error( - i.serialize( - 1 === r.length - ? r[0] - : r - ) - ); - } catch (a) { - i.onError("console", a); - } - e.display && - (i.util.hasFunction(o, "apply") - ? o.apply(t, r) - : o(r[0])); - } catch (a) { - i.onFault(a); - } - }; - })(e.watch[n]); - return t; - }, - report: function () { - return this.log.all("c"); - }, - }; - var s = function (t, e, n, r, i) { - (this.config = t), - (this.util = e), - (this.log = n), - (this.window = r), - (this.document = i), - (this.correlationId = this.token = null), - this.initialize(); - }; - s.prototype = { - initialize: function () { - (this.token = this.getCustomerToken()), - (this.correlationId = this.getCorrelationId()); - }, - getCustomerToken: function () { - if (this.config.current.token) - return this.config.current.token; - var t = - this.document.getElementsByTagName("script"); - return t[t.length - 1].getAttribute("data-token"); - }, - getCorrelationId: function () { - var t; - if (!this.config.current.cookie) - return this.util.uuid(); - try { - (t = this.document.cookie.replace( - /(?:(?:^|.*;\s*)TrackJS\s*\=\s*([^;]*).*$)|^.*$/, - "$1" - )), - t || - ((t = this.util.uuid()), - (this.document.cookie = - "TrackJS=" + - t + - "; expires=Fri, 31 Dec 9999 23:59:59 GMT; path=/")); - } catch (e) { - t = this.util.uuid(); - } - return t; - }, - report: function () { - return { - application: this.config.current.application, - correlationId: this.correlationId, - sessionId: this.config.current.sessionId, - token: this.token, - userId: this.config.current.userId, - version: this.config.current.version, - }; - }, - }; - var u = function (t) { - (this.config = t), - (this.loadedOn = new Date().getTime()), - (this.originalUrl = h.getLocation()), - (this.referrer = h.isBrowser ? n.referrer : ""); - }; - u.prototype = { - discoverDependencies: function () { - var t = { TrackJS: "3.10.1" }; - for (var n in (e.jQuery && - e.jQuery.fn && - e.jQuery.fn.jquery && - (t.jQuery = e.jQuery.fn.jquery), - e.jQuery && - e.jQuery.ui && - e.jQuery.ui.version && - (t.jQueryUI = e.jQuery.ui.version), - e.angular && - e.angular.version && - e.angular.version.full && - (t.angular = e.angular.version.full), - e)) - if ( - "_trackJs" !== n && - "_trackJS" !== n && - "_trackjs" !== n && - "webkitStorageInfo" !== n && - "webkitIndexedDB" !== n && - "top" !== n && - "parent" !== n && - "frameElement" !== n - ) - try { - if (e[n]) { - var r = - e[n].version || - e[n].Version || - e[n].VERSION; - "string" === typeof r && (t[n] = r); - } - } catch (i) {} - return ( - t.TrackJS && t.trackJs && delete t.trackJs, t - ); - }, - report: function () { - return { - age: new Date().getTime() - this.loadedOn, - dependencies: this.config.current.dependencies - ? this.discoverDependencies() - : { trackJs: "3.10.1" }, - originalUrl: this.originalUrl, - referrer: this.referrer, - userAgent: e.navigator.userAgent, - viewportHeight: h.isBrowser - ? e.document.documentElement.clientHeight - : 0, - viewportWidth: h.isBrowser - ? e.document.documentElement.clientWidth - : 0, - }; - }, - }; - var c = function (t) { - (this.util = t), - (this.appender = []), - (this.maxLength = 30); - }; - c.prototype = { - all: function (t) { - var e, - n, - r = []; - for (n = 0; n < this.appender.length; n++) - (e = this.appender[n]) && - e.category === t && - r.push(e.value); - return r; - }, - clear: function () { - this.appender.length = 0; - }, - truncate: function () { - this.appender.length > this.maxLength && - (this.appender = this.appender.slice( - Math.max( - this.appender.length - this.maxLength, - 0 - ) - )); - }, - add: function (t, e) { - var n = this.util.uuid(); - return ( - this.appender.push({ - key: n, - category: t, - value: e, - }), - this.truncate(), - n - ); - }, - get: function (t, e) { - var n, r; - for (r = 0; r < this.appender.length; r++) - if ( - ((n = this.appender[r]), - n.category === t && n.key === e) - ) - return n.value; - return !1; - }, - }; - var f = function (t) { - var e = {}; - return { - addMetadata: function (t, n) { - e[t] = n; - }, - removeMetadata: function (t) { - delete e[t]; - }, - report: function () { - var n, - r = []; - for (n in e) - e.hasOwnProperty(n) && - r.push({ key: n, value: t(e[n]) }); - return r; - }, - store: e, - }; - }, - l = function (t, e) { - (this.log = t), - (this.options = e), - e.enabled && this.watch(); - }; - l.prototype = { - isCompatible: function (t) { - return ( - (t = t || e), - !h.has(t, "chrome.app.runtime") && - h.has(t, "addEventListener") && - h.has(t, "history.pushState") - ); - }, - record: function (t, e, n) { - this.log.add("h", { - type: t, - from: h.truncate(e, 250), - to: h.truncate(n, 250), - on: h.isoNow(), - }); - }, - report: function () { - return this.log.all("h"); - }, - watch: function () { - if (this.isCompatible()) { - var t = this, - n = h.getLocationURL().relative; - e.addEventListener( - "popstate", - function () { - var e = h.getLocationURL().relative; - t.record("popState", n, e), (n = e); - }, - !0 - ), - h.forEach( - ["pushState", "replaceState"], - function (e) { - h.patch(history, e, function (r) { - return function () { - n = - h.getLocationURL() - .relative; - var i = r.apply( - this, - arguments - ), - o = - h.getLocationURL() - .relative; - return ( - t.record(e, n, o), - (n = o), - i - ); - }; - }); - } - ); - } - }, - }; - var d = function (t, e, n, r, i, o) { - (this.util = t), - (this.log = e), - (this.onError = n), - (this.onFault = r), - (this.window = i), - (this.options = o), - o.enabled && this.initialize(i); - }; - d.prototype = { - initialize: function (t) { - t.XMLHttpRequest && - this.util.hasFunction( - t.XMLHttpRequest.prototype.open, - "apply" - ) && - this.watchNetworkObject(t.XMLHttpRequest), - t.XDomainRequest && - this.util.hasFunction( - t.XDomainRequest.prototype.open, - "apply" - ) && - this.watchNetworkObject(t.XDomainRequest), - this.options.fetch && - h.isWrappableFunction(t.fetch) && - this.watchFetch(); - }, - escapeUrl: function (t) { - return ("" + t) - .replace(/ /gi, "%20") - .replace(/\t/gi, "%09"); - }, - watchFetch: function () { - var t = this, - n = this.log, - r = this.options, - i = this.onError; - h.patch(e, "fetch", function (o) { - return function (a, s) { - if (s && s.__trackjs__) - return o.apply(e, arguments); - var u; - try { - throw Error(); - } catch (d) { - u = d.stack; - } - var c = a instanceof Request ? a.url : a, - f = - a instanceof Request - ? a.method - : (s || {}).method || "GET", - l = - ((c = t.escapeUrl(c)), - o.apply(e, arguments)); - return ( - (l.__trackjs_state__ = n.add("n", { - type: "fetch", - startedOn: h.isoNow(), - method: f, - url: h.truncate(c, 2e3), - })), - l - .then(function (t) { - var e = n.get( - "n", - l.__trackjs_state__ - ); - if (e) { - h.defaults(e, { - completedOn: h.isoNow(), - statusCode: t.status, - statusText: - t.statusText, - }); - var o = t.headers.get( - "trackjs-correlation-id" - ); - o && - (e.requestCorrelationId = - o), - r.error && - 400 <= t.status && - ((e = Error( - e.statusCode + - " : " + - e.method + - " " + - e.url - )), - (e.stack = u), - i("ajax", e)); - } - return t; - }) - ["catch"](function (t) { - t = t || {}; - var e = n.get( - "n", - l.__trackjs_state__ - ); - throw ( - (e && - (h.defaults(e, { - completedOn: - h.isoNow(), - statusCode: 0, - statusText: - t.toString(), - }), - r.error && - (i("ajax", { - name: t.name, - message: - (t.message || - "Failed") + - ": " + - e.method + - " " + - e.url, - stack: - t.stack || - u, - }), - (t.__trackjs_state__ = - !0))), - t) - ); - }) - ); - }; - }); - }, - watchNetworkObject: function (t) { - var e = this, - n = t.prototype.open, - r = t.prototype.send; - return ( - (t.prototype.open = function (t, r) { - var i = (r || "").toString(); - return ( - 0 > i.indexOf("localhost:0") && - ((i = e.escapeUrl(i)), - (this._trackJs = { - method: t, - url: i, - })), - n.apply(this, arguments) - ); - }), - (t.prototype.send = function () { - if (!this._trackJs) - try { - return r.apply(this, arguments); - } catch (t) { - return void e.onError("ajax", t); - } - try { - (this._trackJs.logId = e.log.add("n", { - type: "xhr", - startedOn: e.util.isoNow(), - method: this._trackJs.method, - url: h.truncate( - this._trackJs.url, - 2e3 - ), - })), - e.listenForNetworkComplete(this); - } catch (t) { - e.onFault(t); - } - return r.apply(this, arguments); - }), - t - ); - }, - listenForNetworkComplete: function (t) { - var e = this; - e.window.ProgressEvent && - t.addEventListener && - t.addEventListener( - "readystatechange", - function () { - 4 === t.readyState && - e.finalizeNetworkEvent(t); - }, - !0 - ), - t.addEventListener - ? t.addEventListener( - "load", - function () { - e.finalizeNetworkEvent(t), - e.checkNetworkFault(t); - }, - !0 - ) - : setTimeout(function () { - try { - var n = t.onload; - t.onload = function () { - e.finalizeNetworkEvent(t), - e.checkNetworkFault(t), - "function" === typeof n && - e.util.hasFunction( - n, - "apply" - ) && - n.apply(t, arguments); - }; - var r = t.onerror; - t.onerror = function () { - e.finalizeNetworkEvent(t), - e.checkNetworkFault(t), - "function" === - typeof oldOnError && - r.apply(t, arguments); - }; - } catch (h) { - e.onFault(h); - } - }, 0); - }, - finalizeNetworkEvent: function (t) { - if (t._trackJs) { - var e = this.log.get("n", t._trackJs.logId); - e && - ((e.completedOn = this.util.isoNow()), - t.getAllResponseHeaders && - t.getResponseHeader && - 0 <= - (t.getAllResponseHeaders() || "") - .toLowerCase() - .indexOf( - "trackjs-correlation-id" - ) && - (e.requestCorrelationId = - t.getResponseHeader( - "trackjs-correlation-id" - )), - (e.statusCode = - 1223 == t.status ? 204 : t.status), - (e.statusText = - 1223 == t.status - ? "No Content" - : t.statusText)); - } - }, - checkNetworkFault: function (t) { - if ( - this.options.error && - 400 <= t.status && - 1223 != t.status - ) { - var e = t._trackJs || {}; - this.onError( - "ajax", - t.status + " : " + e.method + " " + e.url - ); - } - }, - report: function () { - return this.log.all("n"); - }, - }; - var p = function (t, n) { - (this.util = t), - (this.config = n), - (this.disabled = !1), - (this.throttleStats = { - attemptCount: 0, - throttledCount: 0, - lastAttempt: new Date().getTime(), - }), - (e.JSON && e.JSON.stringify) || - (this.disabled = !0); - }; - p.prototype = { - errorEndpoint: function (t) { - var n = this.config.current, - r = n.errorURL; - return ( - h.isBrowser && - !h.testCrossdomainXhr() && - -1 === e.location.protocol.indexOf("https") - ? (r = n.errorNoSSLURL) - : n.forwardingDomain && - (r = - "https://" + - n.forwardingDomain + - "/capture"), - r + "?token=" + t + "&v=3.10.1" - ); - }, - usageEndpoint: function (t) { - var e = this.config.current, - n = e.usageURL; - return ( - e.forwardingDomain && - (n = - "https://" + - e.forwardingDomain + - "/usage.gif"), - this.appendObjectAsQuery(t, n) - ); - }, - trackerFaultEndpoint: function (t) { - var e = - (this.config || {}).current || - o.prototype.defaults, - n = e.faultURL; - return ( - e.forwardingDomain && - (n = - "https://" + - e.forwardingDomain + - "/fault.gif"), - this.appendObjectAsQuery(t, n) - ); - }, - appendObjectAsQuery: function (t, e) { - for (var n in ((e += "?"), t)) - t.hasOwnProperty(n) && - (e += - encodeURIComponent(n) + - "=" + - encodeURIComponent(t[n]) + - "&"); - return e; - }, - getCORSRequest: function (t, n) { - var r; - return ( - this.util.testCrossdomainXhr() - ? ((r = new e.XMLHttpRequest()), - r.open(t, n), - r.setRequestHeader( - "Content-Type", - "text/plain" - )) - : "undefined" !== typeof e.XDomainRequest - ? ((r = new e.XDomainRequest()), - r.open(t, n)) - : (r = null), - r - ); - }, - sendTrackerFault: function (t) { - this.throttle(t) || - (h.isBrowser - ? (n.createElement("img").src = - this.trackerFaultEndpoint(t)) - : fetch(this.trackerFaultEndpoint(t), { - mode: "no-cors", - __trackjs__: !0, - })); - }, - sendUsage: function (t) { - h.isBrowser - ? (n.createElement("img").src = - this.usageEndpoint(t)) - : fetch(this.usageEndpoint(t), { - mode: "no-cors", - __trackjs__: !0, - }); - }, - sendError: function (t, n) { - var i = this; - if (!this.disabled && !this.throttle(t)) - try { - if (h.isBrowser) { - var o = this.getCORSRequest( - "POST", - this.errorEndpoint(n) - ); - (o.onreadystatechange = function () { - 4 !== o.readyState || - h.contains( - [200, 202], - o.status - ) || - (i.disabled = !0); - }), - (o._trackJs = r), - o.send(e.JSON.stringify(t)); - } else if (h.isWorker) { - var a = { - method: "POST", - mode: "cors", - body: e.JSON.stringify(t), - __trackjs__: 1, - }; - fetch(this.errorEndpoint(n), a) - .then(function (t) { - t.ok || (i.disabled = !0); - }) - ["catch"](function (t) { - i.disabled = !0; - }); - } - } catch (s) { - throw ((this.disabled = !0), s); - } - }, - throttle: function (t) { - var e = new Date().getTime(); - if ( - (this.throttleStats.attemptCount++, - this.throttleStats.lastAttempt + 1e3 >= e) - ) { - if ( - ((this.throttleStats.lastAttempt = e), - 10 < this.throttleStats.attemptCount) - ) - return ( - this.throttleStats.throttledCount++, !0 - ); - } else - (t.throttled = - this.throttleStats.throttledCount), - (this.throttleStats.attemptCount = 0), - (this.throttleStats.lastAttempt = e), - (this.throttleStats.throttledCount = 0); - return !1; - }, - }; - var h = (function () { - function i(t, e, n, a) { - return ( - (n = n || !1), - (a = a || 0), - h.forEach(e, function (e) { - h.forEach(h.keys(e), function (s) { - null === e[s] || e[s] === r - ? (t[s] = e[s]) - : n && - 10 > a && - "[object Object]" === o(e[s]) - ? ((t[s] = t[s] || {}), - i(t[s], [e[s]], n, a + 1)) - : t.hasOwnProperty(s) || - (t[s] = e[s]); - }); - }), - t - ); - } - function o(t) { - return Object.prototype.toString.call(t); - } - return { - isBrowser: - "undefined" !== typeof e && - "undefined" !== typeof e.document, - isWorker: - "object" === typeof self && - self.constructor && - 0 <= - (self.constructor.name || "").indexOf( - "WorkerGlobalScope" - ), - isNode: - "undefined" !== typeof t && - null != t.versions && - null != t.versions.node, - addEventListenerSafe: function (t, e, n, r) { - t.addEventListener - ? t.addEventListener(e, n, r) - : t.attachEvent && - t.attachEvent("on" + e, n); - }, - afterDocumentLoad: function (t) { - if (h.isWorker) h.defer(t); - else { - var e = !1; - "complete" === n.readyState - ? h.defer(t) - : (h.addEventListenerSafe( - n, - "readystatechange", - function () { - "complete" !== - n.readyState || - e || - (h.defer(t), - (e = !0)); - } - ), - setTimeout(function () { - e || (h.defer(t), (e = !0)); - }, 1e4)); - } - }, - bind: function (t, e) { - return function () { - return t.apply( - e, - Array.prototype.slice.call( - arguments - ) - ); - }; - }, - contains: function (t, e) { - return 0 <= t.indexOf(e); - }, - defaults: function (t) { - return i( - t, - Array.prototype.slice.call( - arguments, - 1 - ), - !1 - ); - }, - defaultsDeep: function (t) { - return i( - t, - Array.prototype.slice.call( - arguments, - 1 - ), - !0 - ); - }, - defer: function (t, e) { - setTimeout(function () { - t.apply(e); - }); - }, - forEach: function (t, e, n) { - if (h.isArray(t)) { - if (t.forEach) return t.forEach(e, n); - for (var r = 0; r < t.length; ) - e.call(n, t[r], r, t), r++; - } - }, - getLocation: function () { - return e.location - .toString() - .replace(/ /g, "%20"); - }, - getLocationURL: function () { - return h.parseURL(h.getLocation()); - }, - has: function (t, e) { - try { - for ( - var n = e.split("."), r = t, i = 0; - i < n.length; - i++ - ) { - if (!r[n[i]]) return !1; - r = r[n[i]]; - } - return !0; - } catch (o) { - return !1; - } - }, - hasFunction: function (t, e) { - try { - return !!t[e]; - } catch (h) { - return !1; - } - }, - hasOwn: function (t, e) { - return Object.prototype.hasOwnProperty.call( - t, - e - ); - }, - isArray: function (t) { - return "[object Array]" === o(t); - }, - isBoolean: function (t) { - return ( - "boolean" === typeof t || - (h.isObject(t) && - "[object Boolean]" === o(t)) - ); - }, - isBrowserIE: function (t) { - t = t || e.navigator.userAgent; - var n = t.match(/Trident\/([\d.]+)/); - return n && "7.0" === n[1] - ? 11 - : !!(t = t.match(/MSIE ([\d.]+)/)) && - parseInt(t[1], 10); - }, - isBrowserSupported: function () { - var t = this.isBrowserIE(); - return !t || 8 <= t; - }, - isError: function (t) { - if (!h.isObject(t)) return !1; - var e = o(t); - return ( - "[object Error]" === e || - "[object DOMException]" === e || - (h.isString(t.name) && - h.isString(t.message)) - ); - }, - isElement: function (t) { - return h.isObject(t) && 1 === t.nodeType; - }, - isFunction: function (t) { - return !(!t || "function" !== typeof t); - }, - isNumber: function (t) { - return ( - "number" === typeof t || - (h.isObject(t) && - "[object Number]" === o(t)) - ); - }, - isObject: function (t) { - return !(!t || "object" !== typeof t); - }, - isString: function (t) { - return ( - "string" === typeof t || - (!h.isArray(t) && - h.isObject(t) && - "[object String]" === o(t)) - ); - }, - isWrappableFunction: function (t) { - return ( - this.isFunction(t) && - this.hasFunction(t, "apply") - ); - }, - isoNow: function () { - var t = new Date(); - return t.toISOString - ? t.toISOString() - : t.getUTCFullYear() + - "-" + - this.pad(t.getUTCMonth() + 1) + - "-" + - this.pad(t.getUTCDate()) + - "T" + - this.pad(t.getUTCHours()) + - ":" + - this.pad(t.getUTCMinutes()) + - ":" + - this.pad(t.getUTCSeconds()) + - "." + - String( - ( - t.getUTCMilliseconds() / - 1e3 - ).toFixed(3) - ).slice(2, 5) + - "Z"; - }, - keys: function (t) { - if (!h.isObject(t)) return []; - var e, - n = []; - for (e in t) - t.hasOwnProperty(e) && n.push(e); - return n; - }, - noop: function () {}, - pad: function (t) { - return ( - (t = String(t)), - 1 === t.length && (t = "0" + t), - t - ); - }, - parseURL: function (t) { - var e = t.match( - /^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$/ - ); - return e - ? ((e = { - protocol: e[2], - host: e[4], - path: e[5], - query: e[6], - hash: e[8], - }), - (e.origin = - (e.protocol || "") + - "://" + - (e.host || "")), - (e.relative = - (e.path || "") + - (e.query || "") + - (e.hash || "")), - (e.href = t), - e) - : {}; - }, - patch: function (t, e, n) { - t[e] = n(t[e] || h.noop); - }, - testCrossdomainXhr: function () { - return ( - h.isBrowser && - "withCredentials" in - new XMLHttpRequest() - ); - }, - truncate: function (t, e) { - if (((t = "" + t), t.length <= e)) return t; - var n = t.length - e; - return t.substr(0, e) + "...{" + n + "}"; - }, - tryGet: function (t, e) { - try { - return t[e]; - } catch (h) {} - }, - uuid: function () { - return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace( - /[xy]/g, - function (t) { - var e = (16 * Math.random()) | 0; - return ( - "x" == t ? e : (3 & e) | 8 - ).toString(16); - } - ); - }, - wrapError: function (t) { - var e = - t || Object.prototype.toString.call(t); - if (e && e.innerError) return t; - var n = Error( - "TrackJS Caught: " + (e.message || e) - ); - return ( - (n.description = - "TrackJS Caught: " + e.description), - (n.file = e.file), - (n.line = e.line || e.lineNumber), - (n.column = e.column || e.columnNumber), - (n.stack = e.stack), - (n.innerError = t), - n - ); - }, - }; - })(), - v = function (t, e, n, r, i, o) { - (this.util = t), - (this.log = e), - (this.onError = n), - (this.onFault = r), - (this.options = o), - (this.document = i), - t.isBrowser && o.enabled && this.initialize(i); - }; - v.prototype = { - initialize: function (t) { - var e = this.util.bind( - this.onDocumentClicked, - this - ), - n = this.util.bind(this.onInputChanged, this); - t.addEventListener - ? (t.addEventListener("click", e, !0), - t.addEventListener("blur", n, !0)) - : t.attachEvent && - (t.attachEvent("onclick", e), - t.attachEvent("onfocusout", n)); - }, - onDocumentClicked: function (t) { - try { - var e = this.getElementFromEvent(t); - e && - e.tagName && - (this.isDescribedElement(e, "a") || - this.isDescribedElement(e, "button") || - this.isDescribedElement(e, "input", [ - "button", - "submit", - ]) - ? this.writeVisitorEvent(e, "click") - : this.isDescribedElement(e, "input", [ - "checkbox", - "radio", - ]) && - this.writeVisitorEvent( - e, - "input", - e.value, - e.checked - )); - } catch (n) { - this.onFault(n); - } - }, - onInputChanged: function (t) { - try { - var e = this.getElementFromEvent(t); - e && - e.tagName && - (this.isDescribedElement(e, "textarea") - ? this.writeVisitorEvent( - e, - "input", - e.value - ) - : this.isDescribedElement( - e, - "select" - ) && - e.options && - e.options.length - ? this.onSelectInputChanged(e) - : this.isDescribedElement(e, "input") && - !this.isDescribedElement(e, "input", [ - "button", - "submit", - "hidden", - "checkbox", - "radio", - ]) && - this.writeVisitorEvent( - e, - "input", - e.value - )); - } catch (n) { - this.onFault(n); - } - }, - onSelectInputChanged: function (t) { - if (t.multiple) - for (var e = 0; e < t.options.length; e++) - t.options[e].selected && - this.writeVisitorEvent( - t, - "input", - t.options[e].value - ); - else - 0 <= t.selectedIndex && - t.options[t.selectedIndex] && - this.writeVisitorEvent( - t, - "input", - t.options[t.selectedIndex].value - ); - }, - writeVisitorEvent: function (t, e, n, i) { - "password" === this.getElementType(t) && (n = r); - var o = this.getElementAttributes(t); - t.innerText && - (o.__trackjs_element_text = this.util.truncate( - t.innerText, - 500 - )), - this.log.add("v", { - timestamp: this.util.isoNow(), - action: e, - element: { - tag: t.tagName.toLowerCase(), - attributes: o, - value: this.getMetaValue(n, i), - }, - }); - }, - getElementFromEvent: function (t) { - return ( - t.target || - n.elementFromPoint(t.clientX, t.clientY) - ); - }, - isDescribedElement: function (t, e, n) { - if (t.tagName.toLowerCase() !== e.toLowerCase()) - return !1; - if (!n) return !0; - for ( - t = this.getElementType(t), e = 0; - e < n.length; - e++ - ) - if (n[e] === t) return !0; - return !1; - }, - getElementType: function (t) { - return (t.getAttribute("type") || "").toLowerCase(); - }, - getElementAttributes: function (t) { - for ( - var e = {}, - n = Math.min(t.attributes.length, 10), - r = 0; - r < n; - r++ - ) { - var i = t.attributes[r]; - h.contains( - ["data-value", "value"], - i.name.toLowerCase() - ) || (e[i.name] = h.truncate(i.value, 100)); - } - return e; - }, - getMetaValue: function (t, e) { - return t === r - ? r - : { - length: t.length, - pattern: this.matchInputPattern(t), - checked: e, - }; - }, - matchInputPattern: function (t) { - return "" === t - ? "empty" - : /^[a-z0-9!#$%&'*+=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test( - t - ) - ? "email" - : /^(0?[1-9]|[12][0-9]|3[01])[\/\-](0?[1-9]|1[012])[\/\-]\d{4}$/.test( - t - ) || - /^(\d{4}[\/\-](0?[1-9]|1[012])[\/\-]0?[1-9]|[12][0-9]|3[01])$/.test( - t - ) - ? "date" - : /^(?:(?:\+?1\s*(?:[.-]\s*)?)?(?:\(\s*([2-9]1[02-9]|[2-9][02-8]1|[2-9][02-8][02-9])\s*\)|([2-9]1[02-9]|[2-9][02-8]1|[2-9][02-8][02-9]))\s*(?:[.-]\s*)?)?([2-9]1[02-9]|[2-9][02-9]1|[2-9][02-9]{2})\s*(?:[.-]\s*)?([0-9]{4})(?:\s*(?:#|x\.?|ext\.?|extension)\s*(\d+))?$/.test( - t - ) - ? "usphone" - : /^\s*$/.test(t) - ? "whitespace" - : /^\d*$/.test(t) - ? "numeric" - : /^[a-zA-Z]*$/.test(t) - ? "alpha" - : /^[a-zA-Z0-9]*$/.test(t) - ? "alphanumeric" - : "characters"; - }, - report: function () { - return this.log.all("v"); - }, - }; - var y = function (t, e, n, r, i) { - (this.onError = t), - (this.onFault = e), - (this.serialize = n), - i.enabled && this.watchWindowErrors(r), - i.promise && this.watchPromiseErrors(r); - }; - y.prototype = { - watchPromiseErrors: function (t) { - var e = this; - t.addEventListener - ? t.addEventListener( - "unhandledrejection", - function (t) { - try { - t = t || {}; - var n = t.detail - ? h.tryGet(t.detail, "reason") - : h.tryGet(t, "reason"); - if ( - n !== r && - null !== n && - !n.__trackjs_state__ - ) { - if (!h.isError(n)) - try { - throw Error( - e.serialize(n) - ); - } catch (i) { - n = i; - } - e.onError("promise", n); - } - } catch (i) { - e.onFault(i); - } - } - ) - : h.patch( - t, - "onunhandledrejection", - function (t) { - return function (n) { - e.onError("promise", n), - t.apply(this, arguments); - }; - } - ); - }, - watchWindowErrors: function (t) { - var e = this; - h.patch(t, "onerror", function (t) { - return function (n, i, o, a, s) { - try { - if (h.isError(s)) - return ( - e.onError("window", s), - void t.apply(this, arguments) - ); - s = s || {}; - var u = { - message: - s.message || e.serialize(n), - name: s.name || "Error", - line: - s.line || - parseInt(o, 10) || - null, - column: - s.column || - parseInt(a, 10) || - null, - stack: s.stack || null, - }; - "[object Event]" !== - Object.prototype.toString.call(n) || - i - ? (u.file = - s.file || e.serialize(i)) - : (u.file = (n.target || {}).src), - e.onError("window", u); - } catch (r) { - e.onFault(r); - } - t.apply(this, arguments); - }; - }); - }, - }; - var m = function () { - (this.hasInstalled = !1), - (this.hasEnabled = !0), - (this.window = e), - (this.document = n), - (this.util = h), - (this.install = h.bind(this.install, this)), - (this.onError = h.bind(this.onError, this)), - (this.onFault = h.bind(this.onFault, this)), - (this.serialize = h.bind(this.serialize, this)), - (this.log = new c(h)), - (this.metadata = new f(this.serialize)); - var t = e && (e._trackJs || e._trackJS || e._trackjs); - t && this.install(t); - }; - return ( - (m.prototype = { - install: function (t) { - try { - if (h.isNode) - return ( - this.warn( - "monitoring disabled in node" - ), - !1 - ); - if (!h.has(t, "token")) - return this.warn("missing token"), !1; - if (this.hasInstalled) - return ( - this.warn("already installed"), !1 - ); - if ( - ((this.config = new o(t)), - (this.transmitter = new p( - this.util, - this.config - )), - (this.environment = new u(this.config)), - (this.customer = new s( - this.config, - this.util, - this.log, - this.window, - this.document - )), - !this.config.current.enabled) - ) - return (this.hasEnabled = !1); - if ( - ((this.windowConsoleWatcher = new a( - this.util, - this.log, - this.onError, - this.onFault, - this.serialize, - this.window, - this.config.current.console - )), - !this.util.isBrowserSupported()) - ) - return !1; - (this.callbackWatcher = new i( - this.config.current.callback, - this.onError, - this.onFault - )), - (this.visitorWatcher = new v( - this.util, - this.log, - this.onError, - this.onFault, - this.document, - this.config.current.visitor - )), - (this.navigationWatcher = new l( - this.log, - this.config.current.navigation - )), - (this.networkWatcher = new d( - this.util, - this.log, - this.onError, - this.onFault, - this.window, - this.config.current.network - )), - (this.windowWatcher = new y( - this.onError, - this.onFault, - this.serialize, - this.window, - this.config.current.window - )); - var e = this; - return ( - h.afterDocumentLoad(function () { - e.transmitter.sendUsage({ - token: e.customer.token, - correlationId: - e.customer.correlationId, - application: - e.config.current - .application, - x: e.util.uuid(), - }); - }), - (this.hasInstalled = !0) - ); - } catch (n) { - return this.onFault(n), !1; - } - }, - pub: function () { - var t = this, - n = { - addMetadata: this.metadata.addMetadata, - attempt: function (n, r) { - try { - var i = - Array.prototype.slice.call( - arguments, - 2 - ); - return n.apply(r || this, i); - } catch (e) { - throw ( - (t.onError("catch", e), - h.wrapError(e)) - ); - } - }, - configure: function (e) { - return !t.hasInstalled && - t.hasEnabled - ? (t.warn( - "agent must be installed" - ), - !1) - : t.config.setCurrent(e); - }, - hash: "fb090f9249a14e8440f317f57bd82ec8d6ea32a4", - isInstalled: function () { - return t.hasInstalled; - }, - install: this.install, - removeMetadata: - this.metadata.removeMetadata, - track: function (e) { - if (!t.hasInstalled && t.hasEnabled) - t.warn( - "agent must be installed" - ); - else { - var n = h.isError(e) - ? e.message - : t.serialize(e); - if (((e = e || {}), !e.stack)) - try { - throw Error(n); - } catch (r) { - e = r; - } - t.onError("direct", e); - } - }, - version: "3.10.1", - watch: function (n, r) { - return function () { - try { - var i = - Array.prototype.slice.call( - arguments, - 0 - ); - return n.apply( - r || this, - i - ); - } catch (e) { - throw ( - (t.onError("catch", e), - h.wrapError(e)) - ); - } - }; - }, - watchAll: function (t) { - var e, - n = Array.prototype.slice.call( - arguments, - 1 - ); - for (e in t) - "function" !== typeof t[e] || - h.contains(n, e) || - (t[e] = this.watch( - t[e], - t - )); - return t; - }, - }; - return ( - new a( - h, - t.log, - t.onError, - t.onFault, - t.serialize, - n, - o.prototype.defaults.console - ), - n - ); - }, - onError: (function () { - var t, - n = !1; - return function (r, i, o) { - if ( - this.hasInstalled && - this.hasEnabled && - h.isBrowserSupported() - ) - try { - if ( - ((o = o || { - bindStack: null, - bindTime: null, - force: !1, - }), - (i && h.isError(i)) || - (i = { - name: "Error", - message: this.serialize( - i, - o.force - ), - }), - -1 === - i.message.indexOf( - "TrackJS Caught" - )) - ) - if ( - n && - -1 !== - i.message.indexOf( - "Script error" - ) - ) - n = !1; - else { - var a = h.defaultsDeep( - {}, - { - agentPlatform: - h.isBrowser - ? "browser" - : "worker", - bindStack: - o.bindStack, - bindTime: - o.bindTime, - column: - i.column || - i.columnNumber, - console: - this.windowConsoleWatcher.report(), - customer: - this.customer.report(), - entry: r, - environment: - this.environment.report(), - file: - i.file || - i.fileName, - line: - i.line || - i.lineNumber, - message: i.message, - metadata: - this.metadata.report(), - nav: this.navigationWatcher.report(), - network: - this.networkWatcher.report(), - url: ( - e.location || "" - ).toString(), - stack: i.stack, - timestamp: - this.util.isoNow(), - visitor: - this.visitorWatcher.report(), - version: "3.10.1", - } - ); - if (!o.force) - try { - if ( - !this.config.current.onError( - a, - i - ) - ) - return; - } catch (c) { - a.console.push({ - timestamp: - this.util.isoNow(), - severity: - "error", - message: - c.message, - }); - var s = this; - setTimeout( - function () { - s.onError( - "catch", - c, - { - force: !0, - } - ); - }, - 0 - ); - } - if ( - this.config.current - .dedupe - ) { - var u = ( - a.message + a.stack - ).substr(0, 1e4); - if (u === t) return; - t = u; - } - (function () { - function t() { - var t = 0; - return ( - h.forEach( - a.console, - function ( - e - ) { - t += ( - e.message || - "" - ) - .length; - } - ), - 8e4 <= t - ); - } - for ( - var e = 0; - t() && - e < - a.console - .length; - - ) - (a.console[ - e - ].message = h.truncate( - a.console[e] - .message, - 1e3 - )), - e++; - })(), - this.log.clear(), - setTimeout(function () { - n = !1; - }), - (n = !0), - this.transmitter.sendError( - a, - this.customer.token - ); - } - } catch (c) { - this.onFault(c); - } - }; - })(), - onFault: function (t) { - var e = this.transmitter || new p(); - (t = t || {}), - (t = { - token: (this.customer || {}).token, - file: t.file || t.fileName, - msg: t.message || "unknown", - stack: (t.stack || "unknown").substr( - 0, - 1e3 - ), - url: this.window.location, - v: "3.10.1", - h: "fb090f9249a14e8440f317f57bd82ec8d6ea32a4", - x: this.util.uuid(), - }), - e.sendTrackerFault(t); - }, - serialize: function (t, e) { - if ( - this.hasInstalled && - this.config.current.serialize && - !e - ) - try { - return this.config.current.serialize(t); - } catch (h) { - this.onError("catch", h, { force: !0 }); - } - return o.prototype.defaults.serialize(t); - }, - warn: function (t) { - h.has(e, "console.warn") && - e.console.warn("TrackJS: " + t); - }, - }), - new m().pub() - ); - })( - "undefined" === typeof self ? void 0 : self, - "undefined" === typeof document ? void 0 : document - ); - }.call(this, n("f28c"))); - }, - "38fd": function (t, e, n) { - var r = n("69a8"), - i = n("4bf8"), - o = n("613b")("IE_PROTO"), - a = Object.prototype; - t.exports = - Object.getPrototypeOf || - function (t) { - return ( - (t = i(t)), - r(t, o) - ? t[o] - : "function" == typeof t.constructor && - t instanceof t.constructor - ? t.constructor.prototype - : t instanceof Object - ? a - : null - ); - }; - }, - 3904: function (t, e, n) { - var r = n("8ce0"); - t.exports = function (t, e, n) { - for (var i in e) n && t[i] ? (t[i] = e[i]) : r(t, i, e[i]); - return t; - }; - }, - "3a54": function (t, e, n) { - "use strict"; - Object.defineProperty(e, "__esModule", { value: !0 }), - (e.default = void 0); - var r = n("78ef"), - i = (0, r.regex)("alphaNum", /^[a-zA-Z0-9]*$/); - e.default = i; - }, - "3adc": function (t, e, n) { - var r = n("0f89"), - i = n("a47f"), - o = n("2ea1"), - a = Object.defineProperty; - e.f = n("7d95") - ? Object.defineProperty - : function (t, e, n) { - if ((r(t), (e = o(e, !0)), r(n), i)) - try { - return a(t, e, n); - } catch (s) {} - if ("get" in n || "set" in n) - throw TypeError("Accessors not supported!"); - return "value" in n && (t[e] = n.value), t; - }; - }, - "41a0": function (t, e, n) { - "use strict"; - var r = n("2aeb"), - i = n("4630"), - o = n("7f20"), - a = {}; - n("32e9")(a, n("2b4c")("iterator"), function () { - return this; - }), - (t.exports = function (t, e, n) { - (t.prototype = r(a, { next: i(1, n) })), - o(t, e + " Iterator"); - }); - }, - "436c": function (t, e, n) { - var r = n("1b55")("iterator"), - i = !1; - try { - var o = [7][r](); - (o["return"] = function () { - i = !0; - }), - Array.from(o, function () { - throw 2; - }); - } catch (a) {} - t.exports = function (t, e) { - if (!e && !i) return !1; - var n = !1; - try { - var o = [7], - s = o[r](); - (s.next = function () { - return { done: (n = !0) }; - }), - (o[r] = function () { - return s; - }), - t(o); - } catch (a) {} - return n; - }; - }, - "43c8": function (t, e) { - var n = {}.hasOwnProperty; - t.exports = function (t, e) { - return n.call(t, e); - }; - }, - "456d": function (t, e, n) { - var r = n("4bf8"), - i = n("0d58"); - n("5eda")("keys", function () { - return function (t) { - return i(r(t)); - }; - }); - }, - 4588: function (t, e) { - var n = Math.ceil, - r = Math.floor; - t.exports = function (t) { - return isNaN((t = +t)) ? 0 : (t > 0 ? r : n)(t); - }; - }, - "45b8": function (t, e, n) { - "use strict"; - Object.defineProperty(e, "__esModule", { value: !0 }), - (e.default = void 0); - var r = n("78ef"), - i = (0, r.regex)("numeric", /^[0-9]*$/); - e.default = i; - }, - 4630: function (t, e) { - t.exports = function (t, e) { - return { - enumerable: !(1 & t), - configurable: !(2 & t), - writable: !(4 & t), - value: e, - }; - }; - }, - "46bc": function (t, e, n) { - "use strict"; - Object.defineProperty(e, "__esModule", { value: !0 }), - (e.default = void 0); - var r = n("78ef"), - i = function (t) { - return (0, r.withParams)( - { type: "maxValue", max: t }, - function (e) { - return ( - !(0, r.req)(e) || - ((!/\s/.test(e) || e instanceof Date) && - +e <= +t) - ); - } - ); - }; - e.default = i; - }, - "4a59": function (t, e, n) { - var r = n("9b43"), - i = n("1fa8"), - o = n("33a4"), - a = n("cb7c"), - s = n("9def"), - u = n("27ee"), - c = {}, - f = {}; - e = t.exports = function (t, e, n, l, d) { - var p, - h, - v, - y, - m = d - ? function () { - return t; - } - : u(t), - g = r(n, l, e ? 2 : 1), - b = 0; - if ("function" != typeof m) - throw TypeError(t + " is not iterable!"); - if (o(m)) { - for (p = s(t.length); p > b; b++) - if ( - ((y = e ? g(a((h = t[b]))[0], h[1]) : g(t[b])), - y === c || y === f) - ) - return y; - } else - for (v = m.call(t); !(h = v.next()).done; ) - if (((y = i(v, g, h.value, e)), y === c || y === f)) - return y; - }; - (e.BREAK = c), (e.RETURN = f); - }, - "4aa6": function (t, e, n) { - t.exports = n("af7e"); - }, - "4bf8": function (t, e, n) { - var r = n("be13"); - t.exports = function (t) { - return Object(r(t)); - }; - }, - "4d16": function (t, e, n) { - t.exports = n("a438"); - }, - "4e2b": function (t, e, n) { - "use strict"; - n.d(e, "a", function () { - return u; - }); - var r = n("4aa6"), - i = n.n(r), - o = n("85f2"), - a = n.n(o), - s = n("54b6"); - function u(t, e) { - if ("function" !== typeof e && null !== e) - throw new TypeError( - "Super expression must either be null or a function" - ); - (t.prototype = i()(e && e.prototype, { - constructor: { value: t, writable: !0, configurable: !0 }, - })), - a()(t, "prototype", { writable: !1 }), - e && Object(s["a"])(t, e); - } - }, - "50e9": function (t, e, n) { - var r = n("d13f"); - r(r.S, "Object", { create: n("7108") }); - }, - 5147: function (t, e, n) { - var r = n("2b4c")("match"); - t.exports = function (t) { - var e = /./; - try { - "/./"[t](e); - } catch (n) { - try { - return (e[r] = !1), !"/./"[t](e); - } catch (i) {} - } - return !0; - }; - }, - "520a": function (t, e, n) { - "use strict"; - var r = n("0bfb"), - i = RegExp.prototype.exec, - o = String.prototype.replace, - a = i, - s = "lastIndex", - u = (function () { - var t = /a/, - e = /b*/g; - return ( - i.call(t, "a"), i.call(e, "a"), 0 !== t[s] || 0 !== e[s] - ); - })(), - c = void 0 !== /()??/.exec("")[1], - f = u || c; - f && - (a = function (t) { - var e, - n, - a, - f, - l = this; - return ( - c && - (n = new RegExp( - "^" + l.source + "$(?!\\s)", - r.call(l) - )), - u && (e = l[s]), - (a = i.call(l, t)), - u && a && (l[s] = l.global ? a.index + a[0].length : e), - c && - a && - a.length > 1 && - o.call(a[0], n, function () { - for (f = 1; f < arguments.length - 2; f++) - void 0 === arguments[f] && (a[f] = void 0); - }), - a - ); - }), - (t.exports = a); - }, - "52a7": function (t, e) { - e.f = {}.propertyIsEnumerable; - }, - "54b6": function (t, e, n) { - "use strict"; - n.d(e, "a", function () { - return o; - }); - var r = n("4d16"), - i = n.n(r); - function o(t, e) { - return ( - (o = - i.a || - function (t, e) { - return (t.__proto__ = e), t; - }), - o(t, e) - ); - } - }, - "551c": function (t, e, n) { - "use strict"; - var r, - i, - o, - a, - s = n("2d00"), - u = n("7726"), - c = n("9b43"), - f = n("23c6"), - l = n("5ca1"), - d = n("d3f4"), - p = n("d8e8"), - h = n("f605"), - v = n("4a59"), - y = n("ebd6"), - m = n("1991").set, - g = n("8079")(), - b = n("a5b8"), - _ = n("9c80"), - w = n("a25f"), - x = n("bcaa"), - k = "Promise", - S = u.TypeError, - E = u.process, - O = E && E.versions, - T = (O && O.v8) || "", - A = u[k], - C = "process" == f(E), - j = function () {}, - I = (i = b.f), - M = !!(function () { - try { - var t = A.resolve(1), - e = ((t.constructor = {})[n("2b4c")("species")] = - function (t) { - t(j, j); - }); - return ( - (C || "function" == typeof PromiseRejectionEvent) && - t.then(j) instanceof e && - 0 !== T.indexOf("6.6") && - -1 === w.indexOf("Chrome/66") - ); - } catch (r) {} - })(), - L = function (t) { - var e; - return !(!d(t) || "function" != typeof (e = t.then)) && e; - }, - P = function (t, e) { - if (!t._n) { - t._n = !0; - var n = t._c; - g(function () { - var r = t._v, - i = 1 == t._s, - o = 0, - a = function (e) { - var n, - o, - a, - s = i ? e.ok : e.fail, - u = e.resolve, - c = e.reject, - f = e.domain; - try { - s - ? (i || - (2 == t._h && $(t), - (t._h = 1)), - !0 === s - ? (n = r) - : (f && f.enter(), - (n = s(r)), - f && (f.exit(), (a = !0))), - n === e.promise - ? c(S("Promise-chain cycle")) - : (o = L(n)) - ? o.call(n, u, c) - : u(n)) - : c(r); - } catch (l) { - f && !a && f.exit(), c(l); - } - }; - while (n.length > o) a(n[o++]); - (t._c = []), (t._n = !1), e && !t._h && N(t); - }); - } - }, - N = function (t) { - m.call(u, function () { - var e, - n, - r, - i = t._v, - o = R(t); - if ( - (o && - ((e = _(function () { - C - ? E.emit("unhandledRejection", i, t) - : (n = u.onunhandledrejection) - ? n({ promise: t, reason: i }) - : (r = u.console) && - r.error && - r.error( - "Unhandled promise rejection", - i - ); - })), - (t._h = C || R(t) ? 2 : 1)), - (t._a = void 0), - o && e.e) - ) - throw e.v; - }); - }, - R = function (t) { - return 1 !== t._h && 0 === (t._a || t._c).length; - }, - $ = function (t) { - m.call(u, function () { - var e; - C - ? E.emit("rejectionHandled", t) - : (e = u.onrejectionhandled) && - e({ promise: t, reason: t._v }); - }); - }, - D = function (t) { - var e = this; - e._d || - ((e._d = !0), - (e = e._w || e), - (e._v = t), - (e._s = 2), - e._a || (e._a = e._c.slice()), - P(e, !0)); - }, - F = function (t) { - var e, - n = this; - if (!n._d) { - (n._d = !0), (n = n._w || n); - try { - if (n === t) - throw S("Promise can't be resolved itself"); - (e = L(t)) - ? g(function () { - var r = { _w: n, _d: !1 }; - try { - e.call(t, c(F, r, 1), c(D, r, 1)); - } catch (i) { - D.call(r, i); - } - }) - : ((n._v = t), (n._s = 1), P(n, !1)); - } catch (r) { - D.call({ _w: n, _d: !1 }, r); - } - } - }; - M || - ((A = function (t) { - h(this, A, k, "_h"), p(t), r.call(this); - try { - t(c(F, this, 1), c(D, this, 1)); - } catch (e) { - D.call(this, e); - } - }), - (r = function (t) { - (this._c = []), - (this._a = void 0), - (this._s = 0), - (this._d = !1), - (this._v = void 0), - (this._h = 0), - (this._n = !1); - }), - (r.prototype = n("dcbc")(A.prototype, { - then: function (t, e) { - var n = I(y(this, A)); - return ( - (n.ok = "function" != typeof t || t), - (n.fail = "function" == typeof e && e), - (n.domain = C ? E.domain : void 0), - this._c.push(n), - this._a && this._a.push(n), - this._s && P(this, !1), - n.promise - ); - }, - catch: function (t) { - return this.then(void 0, t); - }, - })), - (o = function () { - var t = new r(); - (this.promise = t), - (this.resolve = c(F, t, 1)), - (this.reject = c(D, t, 1)); - }), - (b.f = I = - function (t) { - return t === A || t === a ? new o(t) : i(t); - })), - l(l.G + l.W + l.F * !M, { Promise: A }), - n("7f20")(A, k), - n("7a56")(k), - (a = n("8378")[k]), - l(l.S + l.F * !M, k, { - reject: function (t) { - var e = I(this), - n = e.reject; - return n(t), e.promise; - }, - }), - l(l.S + l.F * (s || !M), k, { - resolve: function (t) { - return x(s && this === a ? A : this, t); - }, - }), - l( - l.S + - l.F * - !( - M && - n("5cc5")(function (t) { - A.all(t)["catch"](j); - }) - ), - k, - { - all: function (t) { - var e = this, - n = I(e), - r = n.resolve, - i = n.reject, - o = _(function () { - var n = [], - o = 0, - a = 1; - v(t, !1, function (t) { - var s = o++, - u = !1; - n.push(void 0), - a++, - e.resolve(t).then(function (t) { - u || - ((u = !0), - (n[s] = t), - --a || r(n)); - }, i); - }), - --a || r(n); - }); - return o.e && i(o.v), n.promise; - }, - race: function (t) { - var e = this, - n = I(e), - r = n.reject, - i = _(function () { - v(t, !1, function (t) { - e.resolve(t).then(n.resolve, r); - }); - }); - return i.e && r(i.v), n.promise; - }, - } - ); - }, - 5537: function (t, e, n) { - var r = n("8378"), - i = n("7726"), - o = "__core-js_shared__", - a = i[o] || (i[o] = {}); - (t.exports = function (t, e) { - return a[t] || (a[t] = void 0 !== e ? e : {}); - })("versions", []).push({ - version: r.version, - mode: n("2d00") ? "pure" : "global", - copyright: "© 2019 Denis Pushkarev (zloirock.ru)", - }); - }, - "560b": function (t, e, n) { - var r = n("bc25"), - i = n("9c93"), - o = n("c227"), - a = n("0f89"), - s = n("a5ab"), - u = n("f159"), - c = {}, - f = {}; - e = t.exports = function (t, e, n, l, d) { - var p, - h, - v, - y, - m = d - ? function () { - return t; - } - : u(t), - g = r(n, l, e ? 2 : 1), - b = 0; - if ("function" != typeof m) - throw TypeError(t + " is not iterable!"); - if (o(m)) { - for (p = s(t.length); p > b; b++) - if ( - ((y = e ? g(a((h = t[b]))[0], h[1]) : g(t[b])), - y === c || y === f) - ) - return y; - } else - for (v = m.call(t); !(h = v.next()).done; ) - if (((y = i(v, g, h.value, e)), y === c || y === f)) - return y; - }; - (e.BREAK = c), (e.RETURN = f); - }, - "565d": function (t, e, n) { - var r = n("6a9b"), - i = n("d876").f, - o = {}.toString, - a = - "object" == typeof window && - window && - Object.getOwnPropertyNames - ? Object.getOwnPropertyNames(window) - : [], - s = function (t) { - try { - return i(t); - } catch (e) { - return a.slice(); - } - }; - t.exports.f = function (t) { - return a && "[object Window]" == o.call(t) ? s(t) : i(r(t)); - }; - }, - "57f7": function (t, e, n) { - n("93c4"), n("6109"), (t.exports = n("a7d3").Array.from); - }, - 5927: function (t, e, n) { - n("93c4"), n("b42c"), (t.exports = n("fda1").f("iterator")); - }, - "5ca1": function (t, e, n) { - var r = n("7726"), - i = n("8378"), - o = n("32e9"), - a = n("2aba"), - s = n("9b43"), - u = "prototype", - c = function (t, e, n) { - var f, - l, - d, - p, - h = t & c.F, - v = t & c.G, - y = t & c.S, - m = t & c.P, - g = t & c.B, - b = v ? r : y ? r[e] || (r[e] = {}) : (r[e] || {})[u], - _ = v ? i : i[e] || (i[e] = {}), - w = _[u] || (_[u] = {}); - for (f in (v && (n = e), n)) - (l = !h && b && void 0 !== b[f]), - (d = (l ? b : n)[f]), - (p = - g && l - ? s(d, r) - : m && "function" == typeof d - ? s(Function.call, d) - : d), - b && a(b, f, d, t & c.U), - _[f] != d && o(_, f, p), - m && w[f] != d && (w[f] = d); - }; - (r.core = i), - (c.F = 1), - (c.G = 2), - (c.S = 4), - (c.P = 8), - (c.B = 16), - (c.W = 32), - (c.U = 64), - (c.R = 128), - (t.exports = c); - }, - "5cc5": function (t, e, n) { - var r = n("2b4c")("iterator"), - i = !1; - try { - var o = [7][r](); - (o["return"] = function () { - i = !0; - }), - Array.from(o, function () { - throw 2; - }); - } catch (a) {} - t.exports = function (t, e) { - if (!e && !i) return !1; - var n = !1; - try { - var o = [7], - s = o[r](); - (s.next = function () { - return { done: (n = !0) }; - }), - (o[r] = function () { - return s; - }), - t(o); - } catch (a) {} - return n; - }; - }, - "5ce7": function (t, e, n) { - "use strict"; - var r = n("7108"), - i = n("f845"), - o = n("c0d8"), - a = {}; - n("8ce0")(a, n("1b55")("iterator"), function () { - return this; - }), - (t.exports = function (t, e, n) { - (t.prototype = r(a, { next: i(1, n) })), - o(t, e + " Iterator"); - }); - }, - "5d58": function (t, e, n) { - t.exports = n("5927"); - }, - "5d75": function (t, e, n) { - "use strict"; - Object.defineProperty(e, "__esModule", { value: !0 }), - (e.default = void 0); - var r = n("78ef"), - i = - /(^$|^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$)/, - o = (0, r.regex)("email", i); - e.default = o; - }, - "5d8f": function (t, e, n) { - var r = n("7772")("keys"), - i = n("7b00"); - t.exports = function (t) { - return r[t] || (r[t] = i(t)); - }; - }, - "5db3": function (t, e, n) { - "use strict"; - Object.defineProperty(e, "__esModule", { value: !0 }), - (e.default = void 0); - var r = n("78ef"), - i = function (t) { - return (0, r.withParams)( - { type: "minLength", min: t }, - function (e) { - return !(0, r.req)(e) || (0, r.len)(e) >= t; - } - ); - }; - e.default = i; - }, - "5dbc": function (t, e, n) { - var r = n("d3f4"), - i = n("8b97").set; - t.exports = function (t, e, n) { - var o, - a = e.constructor; - return ( - a !== n && - "function" == typeof a && - (o = a.prototype) !== n.prototype && - r(o) && - i && - i(t, o), - t - ); - }; - }, - "5df3": function (t, e, n) { - "use strict"; - var r = n("02f4")(!0); - n("01f9")( - String, - "String", - function (t) { - (this._t = String(t)), (this._i = 0); - }, - function () { - var t, - e = this._t, - n = this._i; - return n >= e.length - ? { value: void 0, done: !0 } - : ((t = r(e, n)), - (this._i += t.length), - { value: t, done: !1 }); - } - ); - }, - "5eda": function (t, e, n) { - var r = n("5ca1"), - i = n("8378"), - o = n("79e5"); - t.exports = function (t, e) { - var n = (i.Object || {})[t] || Object[t], - a = {}; - (a[t] = e(n)), - r( - r.S + - r.F * - o(function () { - n(1); - }), - "Object", - a - ); - }; - }, - "5f1b": function (t, e, n) { - "use strict"; - var r = n("23c6"), - i = RegExp.prototype.exec; - t.exports = function (t, e) { - var n = t.exec; - if ("function" === typeof n) { - var o = n.call(t, e); - if ("object" !== typeof o) - throw new TypeError( - "RegExp exec method returned something other than an Object or null" - ); - return o; - } - if ("RegExp" !== r(t)) - throw new TypeError( - "RegExp#exec called on incompatible receiver" - ); - return i.call(t, e); - }; - }, - 6109: function (t, e, n) { - "use strict"; - var r = n("bc25"), - i = n("d13f"), - o = n("0185"), - a = n("9c93"), - s = n("c227"), - u = n("a5ab"), - c = n("b3ec"), - f = n("f159"); - i( - i.S + - i.F * - !n("436c")(function (t) { - Array.from(t); - }), - "Array", - { - from: function (t) { - var e, - n, - i, - l, - d = o(t), - p = "function" == typeof this ? this : Array, - h = arguments.length, - v = h > 1 ? arguments[1] : void 0, - y = void 0 !== v, - m = 0, - g = f(d); - if ( - (y && (v = r(v, h > 2 ? arguments[2] : void 0, 2)), - void 0 == g || (p == Array && s(g))) - ) - for (e = u(d.length), n = new p(e); e > m; m++) - c(n, m, y ? v(d[m], m) : d[m]); - else - for ( - l = g.call(d), n = new p(); - !(i = l.next()).done; - m++ - ) - c( - n, - m, - y ? a(l, v, [i.value, m], !0) : i.value - ); - return (n.length = m), n; - }, - } - ); - }, - "613b": function (t, e, n) { - var r = n("5537")("keys"), - i = n("ca5a"); - t.exports = function (t) { - return r[t] || (r[t] = i(t)); - }; - }, - 6235: function (t, e, n) { - "use strict"; - Object.defineProperty(e, "__esModule", { value: !0 }), - (e.default = void 0); - var r = n("78ef"), - i = (0, r.regex)("alpha", /^[a-zA-Z]*$/); - e.default = i; - }, - "626a": function (t, e, n) { - var r = n("2d95"); - t.exports = Object("z").propertyIsEnumerable(0) - ? Object - : function (t) { - return "String" == r(t) ? t.split("") : Object(t); - }; - }, - "626e": function (t, e, n) { - var r = n("d74e"), - i = n("f845"), - o = n("6a9b"), - a = n("2ea1"), - s = n("43c8"), - u = n("a47f"), - c = Object.getOwnPropertyDescriptor; - e.f = n("7d95") - ? c - : function (t, e) { - if (((t = o(t)), (e = a(e, !0)), u)) - try { - return c(t, e); - } catch (n) {} - if (s(t, e)) return i(!r.f.call(t, e), t[e]); - }; - }, - 6277: function (t, e, n) { - var r = n("7b00")("meta"), - i = n("6f8a"), - o = n("43c8"), - a = n("3adc").f, - s = 0, - u = - Object.isExtensible || - function () { - return !0; - }, - c = !n("d782")(function () { - return u(Object.preventExtensions({})); - }), - f = function (t) { - a(t, r, { value: { i: "O" + ++s, w: {} } }); - }, - l = function (t, e) { - if (!i(t)) - return "symbol" == typeof t - ? t - : ("string" == typeof t ? "S" : "P") + t; - if (!o(t, r)) { - if (!u(t)) return "F"; - if (!e) return "E"; - f(t); - } - return t[r].i; - }, - d = function (t, e) { - if (!o(t, r)) { - if (!u(t)) return !0; - if (!e) return !1; - f(t); - } - return t[r].w; - }, - p = function (t) { - return c && h.NEED && u(t) && !o(t, r) && f(t), t; - }, - h = (t.exports = { - KEY: r, - NEED: !1, - fastKey: l, - getWeak: d, - onFreeze: p, - }); - }, - 6417: function (t, e, n) { - "use strict"; - Object.defineProperty(e, "__esModule", { value: !0 }), - (e.default = void 0); - var r = n("78ef"), - i = function (t) { - return (0, r.withParams)({ type: "not" }, function (e, n) { - return !(0, r.req)(e) || !t.call(this, e, n); - }); - }; - e.default = i; - }, - 6494: function (t, e, n) { - var r = n("6f8a"), - i = n("0f89"), - o = function (t, e) { - if ((i(t), !r(e) && null !== e)) - throw TypeError(e + ": can't set as prototype!"); - }; - t.exports = { - set: - Object.setPrototypeOf || - ("__proto__" in {} - ? (function (t, e, r) { - try { - (r = n("bc25")( - Function.call, - n("626e").f(Object.prototype, "__proto__") - .set, - 2 - )), - r(t, []), - (e = !(t instanceof Array)); - } catch (i) { - e = !0; - } - return function (t, n) { - return ( - o(t, n), - e ? (t.__proto__ = n) : r(t, n), - t - ); - }; - })({}, !1) - : void 0), - check: o, - }; - }, - "67ab": function (t, e, n) { - var r = n("ca5a")("meta"), - i = n("d3f4"), - o = n("69a8"), - a = n("86cc").f, - s = 0, - u = - Object.isExtensible || - function () { - return !0; - }, - c = !n("79e5")(function () { - return u(Object.preventExtensions({})); - }), - f = function (t) { - a(t, r, { value: { i: "O" + ++s, w: {} } }); - }, - l = function (t, e) { - if (!i(t)) - return "symbol" == typeof t - ? t - : ("string" == typeof t ? "S" : "P") + t; - if (!o(t, r)) { - if (!u(t)) return "F"; - if (!e) return "E"; - f(t); - } - return t[r].i; - }, - d = function (t, e) { - if (!o(t, r)) { - if (!u(t)) return !0; - if (!e) return !1; - f(t); - } - return t[r].w; - }, - p = function (t) { - return c && h.NEED && u(t) && !o(t, r) && f(t), t; - }, - h = (t.exports = { - KEY: r, - NEED: !1, - fastKey: l, - getWeak: d, - onFreeze: p, - }); - }, - "67bb": function (t, e, n) { - t.exports = n("b258"); - }, - 6821: function (t, e, n) { - var r = n("626a"), - i = n("be13"); - t.exports = function (t) { - return r(i(t)); - }; - }, - "69a8": function (t, e) { - var n = {}.hasOwnProperty; - t.exports = function (t, e) { - return n.call(t, e); - }; - }, - "6a99": function (t, e, n) { - var r = n("d3f4"); - t.exports = function (t, e) { - if (!r(t)) return t; - var n, i; - if ( - e && - "function" == typeof (n = t.toString) && - !r((i = n.call(t))) - ) - return i; - if ("function" == typeof (n = t.valueOf) && !r((i = n.call(t)))) - return i; - if ( - !e && - "function" == typeof (n = t.toString) && - !r((i = n.call(t))) - ) - return i; - throw TypeError("Can't convert object to primitive value"); - }; - }, - "6a9b": function (t, e, n) { - var r = n("8bab"), - i = n("e5fa"); - t.exports = function (t) { - return r(i(t)); - }; - }, - "6bb5": function (t, e, n) { - "use strict"; - n.d(e, "a", function () { - return s; - }); - var r = n("4d16"), - i = n.n(r), - o = n("061b"), - a = n.n(o); - function s(t) { - return ( - (s = i.a - ? a.a - : function (t) { - return t.__proto__ || a()(t); - }), - s(t) - ); - } - }, - "6d93": function (t, e, n) { - "use strict"; - var r = - ("undefined" !== typeof globalThis && globalThis) || - ("undefined" !== typeof self && self) || - ("undefined" !== typeof r && r), - i = { - searchParams: "URLSearchParams" in r, - iterable: "Symbol" in r && "iterator" in Symbol, - blob: - "FileReader" in r && - "Blob" in r && - (function () { - try { - return new Blob(), !0; - } catch (t) { - return !1; - } - })(), - formData: "FormData" in r, - arrayBuffer: "ArrayBuffer" in r, - }; - function o(t) { - return t && DataView.prototype.isPrototypeOf(t); - } - if (i.arrayBuffer) - var a = [ - "[object Int8Array]", - "[object Uint8Array]", - "[object Uint8ClampedArray]", - "[object Int16Array]", - "[object Uint16Array]", - "[object Int32Array]", - "[object Uint32Array]", - "[object Float32Array]", - "[object Float64Array]", - ], - s = - ArrayBuffer.isView || - function (t) { - return ( - t && - a.indexOf(Object.prototype.toString.call(t)) > - -1 - ); - }; - function u(t) { - if ( - ("string" !== typeof t && (t = String(t)), - /[^a-z0-9\-#$%&'*+.^_`|~!]/i.test(t) || "" === t) - ) - throw new TypeError( - 'Invalid character in header field name: "' + t + '"' - ); - return t.toLowerCase(); - } - function c(t) { - return "string" !== typeof t && (t = String(t)), t; - } - function f(t) { - var e = { - next: function () { - var e = t.shift(); - return { done: void 0 === e, value: e }; - }, - }; - return ( - i.iterable && - (e[Symbol.iterator] = function () { - return e; - }), - e - ); - } - function l(t) { - (this.map = {}), - t instanceof l - ? t.forEach(function (t, e) { - this.append(e, t); - }, this) - : Array.isArray(t) - ? t.forEach(function (t) { - this.append(t[0], t[1]); - }, this) - : t && - Object.getOwnPropertyNames(t).forEach(function (e) { - this.append(e, t[e]); - }, this); - } - function d(t) { - if (t.bodyUsed) - return Promise.reject(new TypeError("Already read")); - t.bodyUsed = !0; - } - function p(t) { - return new Promise(function (e, n) { - (t.onload = function () { - e(t.result); - }), - (t.onerror = function () { - n(t.error); - }); - }); - } - function h(t) { - var e = new FileReader(), - n = p(e); - return e.readAsArrayBuffer(t), n; - } - function v(t) { - var e = new FileReader(), - n = p(e); - return e.readAsText(t), n; - } - function y(t) { - for ( - var e = new Uint8Array(t), n = new Array(e.length), r = 0; - r < e.length; - r++ - ) - n[r] = String.fromCharCode(e[r]); - return n.join(""); - } - function m(t) { - if (t.slice) return t.slice(0); - var e = new Uint8Array(t.byteLength); - return e.set(new Uint8Array(t)), e.buffer; - } - function g() { - return ( - (this.bodyUsed = !1), - (this._initBody = function (t) { - (this.bodyUsed = this.bodyUsed), - (this._bodyInit = t), - t - ? "string" === typeof t - ? (this._bodyText = t) - : i.blob && Blob.prototype.isPrototypeOf(t) - ? (this._bodyBlob = t) - : i.formData && - FormData.prototype.isPrototypeOf(t) - ? (this._bodyFormData = t) - : i.searchParams && - URLSearchParams.prototype.isPrototypeOf(t) - ? (this._bodyText = t.toString()) - : i.arrayBuffer && i.blob && o(t) - ? ((this._bodyArrayBuffer = m(t.buffer)), - (this._bodyInit = new Blob([ - this._bodyArrayBuffer, - ]))) - : i.arrayBuffer && - (ArrayBuffer.prototype.isPrototypeOf(t) || - s(t)) - ? (this._bodyArrayBuffer = m(t)) - : (this._bodyText = t = - Object.prototype.toString.call(t)) - : (this._bodyText = ""), - this.headers.get("content-type") || - ("string" === typeof t - ? this.headers.set( - "content-type", - "text/plain;charset=UTF-8" - ) - : this._bodyBlob && this._bodyBlob.type - ? this.headers.set( - "content-type", - this._bodyBlob.type - ) - : i.searchParams && - URLSearchParams.prototype.isPrototypeOf( - t - ) && - this.headers.set( - "content-type", - "application/x-www-form-urlencoded;charset=UTF-8" - )); - }), - i.blob && - ((this.blob = function () { - var t = d(this); - if (t) return t; - if (this._bodyBlob) - return Promise.resolve(this._bodyBlob); - if (this._bodyArrayBuffer) - return Promise.resolve( - new Blob([this._bodyArrayBuffer]) - ); - if (this._bodyFormData) - throw new Error( - "could not read FormData body as blob" - ); - return Promise.resolve(new Blob([this._bodyText])); - }), - (this.arrayBuffer = function () { - if (this._bodyArrayBuffer) { - var t = d(this); - return ( - t || - (ArrayBuffer.isView(this._bodyArrayBuffer) - ? Promise.resolve( - this._bodyArrayBuffer.buffer.slice( - this._bodyArrayBuffer - .byteOffset, - this._bodyArrayBuffer - .byteOffset + - this._bodyArrayBuffer - .byteLength - ) - ) - : Promise.resolve( - this._bodyArrayBuffer - )) - ); - } - return this.blob().then(h); - })), - (this.text = function () { - var t = d(this); - if (t) return t; - if (this._bodyBlob) return v(this._bodyBlob); - if (this._bodyArrayBuffer) - return Promise.resolve(y(this._bodyArrayBuffer)); - if (this._bodyFormData) - throw new Error( - "could not read FormData body as text" - ); - return Promise.resolve(this._bodyText); - }), - i.formData && - (this.formData = function () { - return this.text().then(x); - }), - (this.json = function () { - return this.text().then(JSON.parse); - }), - this - ); - } - (l.prototype.append = function (t, e) { - (t = u(t)), (e = c(e)); - var n = this.map[t]; - this.map[t] = n ? n + ", " + e : e; - }), - (l.prototype["delete"] = function (t) { - delete this.map[u(t)]; - }), - (l.prototype.get = function (t) { - return (t = u(t)), this.has(t) ? this.map[t] : null; - }), - (l.prototype.has = function (t) { - return this.map.hasOwnProperty(u(t)); - }), - (l.prototype.set = function (t, e) { - this.map[u(t)] = c(e); - }), - (l.prototype.forEach = function (t, e) { - for (var n in this.map) - this.map.hasOwnProperty(n) && - t.call(e, this.map[n], n, this); - }), - (l.prototype.keys = function () { - var t = []; - return ( - this.forEach(function (e, n) { - t.push(n); - }), - f(t) - ); - }), - (l.prototype.values = function () { - var t = []; - return ( - this.forEach(function (e) { - t.push(e); - }), - f(t) - ); - }), - (l.prototype.entries = function () { - var t = []; - return ( - this.forEach(function (e, n) { - t.push([n, e]); - }), - f(t) - ); - }), - i.iterable && - (l.prototype[Symbol.iterator] = l.prototype.entries); - var b = ["DELETE", "GET", "HEAD", "OPTIONS", "POST", "PUT"]; - function _(t) { - var e = t.toUpperCase(); - return b.indexOf(e) > -1 ? e : t; - } - function w(t, e) { - if (!(this instanceof w)) - throw new TypeError( - 'Please use the "new" operator, this DOM object constructor cannot be called as a function.' - ); - e = e || {}; - var n = e.body; - if (t instanceof w) { - if (t.bodyUsed) throw new TypeError("Already read"); - (this.url = t.url), - (this.credentials = t.credentials), - e.headers || (this.headers = new l(t.headers)), - (this.method = t.method), - (this.mode = t.mode), - (this.signal = t.signal), - n || - null == t._bodyInit || - ((n = t._bodyInit), (t.bodyUsed = !0)); - } else this.url = String(t); - if ( - ((this.credentials = - e.credentials || this.credentials || "same-origin"), - (!e.headers && this.headers) || - (this.headers = new l(e.headers)), - (this.method = _(e.method || this.method || "GET")), - (this.mode = e.mode || this.mode || null), - (this.signal = e.signal || this.signal), - (this.referrer = null), - ("GET" === this.method || "HEAD" === this.method) && n) - ) - throw new TypeError( - "Body not allowed for GET or HEAD requests" - ); - if ( - (this._initBody(n), - ("GET" === this.method || "HEAD" === this.method) && - ("no-store" === e.cache || "no-cache" === e.cache)) - ) { - var r = /([?&])_=[^&]*/; - if (r.test(this.url)) - this.url = this.url.replace( - r, - "$1_=" + new Date().getTime() - ); - else { - var i = /\?/; - this.url += - (i.test(this.url) ? "&" : "?") + - "_=" + - new Date().getTime(); - } - } - } - function x(t) { - var e = new FormData(); - return ( - t - .trim() - .split("&") - .forEach(function (t) { - if (t) { - var n = t.split("="), - r = n.shift().replace(/\+/g, " "), - i = n.join("=").replace(/\+/g, " "); - e.append( - decodeURIComponent(r), - decodeURIComponent(i) - ); - } - }), - e - ); - } - function k(t) { - var e = new l(), - n = t.replace(/\r?\n[\t ]+/g, " "); - return ( - n - .split("\r") - .map(function (t) { - return 0 === t.indexOf("\n") - ? t.substr(1, t.length) - : t; - }) - .forEach(function (t) { - var n = t.split(":"), - r = n.shift().trim(); - if (r) { - var i = n.join(":").trim(); - e.append(r, i); - } - }), - e - ); - } - function S(t, e) { - if (!(this instanceof S)) - throw new TypeError( - 'Please use the "new" operator, this DOM object constructor cannot be called as a function.' - ); - e || (e = {}), - (this.type = "default"), - (this.status = void 0 === e.status ? 200 : e.status), - (this.ok = this.status >= 200 && this.status < 300), - (this.statusText = - void 0 === e.statusText ? "" : "" + e.statusText), - (this.headers = new l(e.headers)), - (this.url = e.url || ""), - this._initBody(t); - } - (w.prototype.clone = function () { - return new w(this, { body: this._bodyInit }); - }), - g.call(w.prototype), - g.call(S.prototype), - (S.prototype.clone = function () { - return new S(this._bodyInit, { - status: this.status, - statusText: this.statusText, - headers: new l(this.headers), - url: this.url, - }); - }), - (S.error = function () { - var t = new S(null, { status: 0, statusText: "" }); - return (t.type = "error"), t; - }); - var E = [301, 302, 303, 307, 308]; - S.redirect = function (t, e) { - if (-1 === E.indexOf(e)) - throw new RangeError("Invalid status code"); - return new S(null, { status: e, headers: { location: t } }); - }; - var O = r.DOMException; - try { - new O(); - } catch (A) { - (O = function (t, e) { - (this.message = t), (this.name = e); - var n = Error(t); - this.stack = n.stack; - }), - (O.prototype = Object.create(Error.prototype)), - (O.prototype.constructor = O); - } - function T(t, e) { - return new Promise(function (n, o) { - var a = new w(t, e); - if (a.signal && a.signal.aborted) - return o(new O("Aborted", "AbortError")); - var s = new XMLHttpRequest(); - function u() { - s.abort(); - } - function f(t) { - try { - return "" === t && r.location.href - ? r.location.href - : t; - } catch (e) { - return t; - } - } - (s.onload = function () { - var t = { - status: s.status, - statusText: s.statusText, - headers: k(s.getAllResponseHeaders() || ""), - }; - t.url = - "responseURL" in s - ? s.responseURL - : t.headers.get("X-Request-URL"); - var e = "response" in s ? s.response : s.responseText; - setTimeout(function () { - n(new S(e, t)); - }, 0); - }), - (s.onerror = function () { - setTimeout(function () { - o(new TypeError("Network request failed")); - }, 0); - }), - (s.ontimeout = function () { - setTimeout(function () { - o(new TypeError("Network request failed")); - }, 0); - }), - (s.onabort = function () { - setTimeout(function () { - o(new O("Aborted", "AbortError")); - }, 0); - }), - s.open(a.method, f(a.url), !0), - "include" === a.credentials - ? (s.withCredentials = !0) - : "omit" === a.credentials && - (s.withCredentials = !1), - "responseType" in s && - (i.blob - ? (s.responseType = "blob") - : i.arrayBuffer && - a.headers.get("Content-Type") && - -1 !== - a.headers - .get("Content-Type") - .indexOf( - "application/octet-stream" - ) && - (s.responseType = "arraybuffer")), - !e || - "object" !== typeof e.headers || - e.headers instanceof l - ? a.headers.forEach(function (t, e) { - s.setRequestHeader(e, t); - }) - : Object.getOwnPropertyNames(e.headers).forEach( - function (t) { - s.setRequestHeader(t, c(e.headers[t])); - } - ), - a.signal && - (a.signal.addEventListener("abort", u), - (s.onreadystatechange = function () { - 4 === s.readyState && - a.signal.removeEventListener("abort", u); - })), - s.send( - "undefined" === typeof a._bodyInit - ? null - : a._bodyInit - ); - }); - } - (T.polyfill = !0), - r.fetch || - ((r.fetch = T), - (r.Headers = l), - (r.Request = w), - (r.Response = S)); - }, - "6e1f": function (t, e) { - var n = {}.toString; - t.exports = function (t) { - return n.call(t).slice(8, -1); - }; - }, - "6f8a": function (t, e) { - t.exports = function (t) { - return "object" === typeof t - ? null !== t - : "function" === typeof t; - }; - }, - 7017: function (t, e, n) { - n("85cd"), (t.exports = n("a7d3").Object.getPrototypeOf); - }, - 7108: function (t, e, n) { - var r = n("0f89"), - i = n("f568"), - o = n("0029"), - a = n("5d8f")("IE_PROTO"), - s = function () {}, - u = "prototype", - c = function () { - var t, - e = n("12fd")("iframe"), - r = o.length, - i = "<", - a = ">"; - (e.style.display = "none"), - n("103a").appendChild(e), - (e.src = "javascript:"), - (t = e.contentWindow.document), - t.open(), - t.write( - i + - "script" + - a + - "document.F=Object" + - i + - "/script" + - a - ), - t.close(), - (c = t.F); - while (r--) delete c[u][o[r]]; - return c(); - }; - t.exports = - Object.create || - function (t, e) { - var n; - return ( - null !== t - ? ((s[u] = r(t)), - (n = new s()), - (s[u] = null), - (n[a] = t)) - : (n = c()), - void 0 === e ? n : i(n, e) - ); - }; - }, - 7333: function (t, e, n) { - "use strict"; - var r = n("9e1e"), - i = n("0d58"), - o = n("2621"), - a = n("52a7"), - s = n("4bf8"), - u = n("626a"), - c = Object.assign; - t.exports = - !c || - n("79e5")(function () { - var t = {}, - e = {}, - n = Symbol(), - r = "abcdefghijklmnopqrst"; - return ( - (t[n] = 7), - r.split("").forEach(function (t) { - e[t] = t; - }), - 7 != c({}, t)[n] || Object.keys(c({}, e)).join("") != r - ); - }) - ? function (t, e) { - var n = s(t), - c = arguments.length, - f = 1, - l = o.f, - d = a.f; - while (c > f) { - var p, - h = u(arguments[f++]), - v = l ? i(h).concat(l(h)) : i(h), - y = v.length, - m = 0; - while (y > m) - (p = v[m++]), - (r && !d.call(h, p)) || (n[p] = h[p]); - } - return n; - } - : c; - }, - 7514: function (t, e, n) { - "use strict"; - var r = n("5ca1"), - i = n("0a49")(5), - o = "find", - a = !0; - o in [] && - Array(1)[o](function () { - a = !1; - }), - r(r.P + r.F * a, "Array", { - find: function (t) { - return i( - this, - t, - arguments.length > 1 ? arguments[1] : void 0 - ); - }, - }), - n("9c6c")(o); - }, - "75fc": function (t, e, n) { - "use strict"; - var r = n("a745"), - i = n.n(r); - function o(t, e) { - (null == e || e > t.length) && (e = t.length); - for (var n = 0, r = new Array(e); n < e; n++) r[n] = t[n]; - return r; - } - function a(t) { - if (i()(t)) return o(t); - } - var s = n("67bb"), - u = n.n(s), - c = n("5d58"), - f = n.n(c), - l = n("774e"), - d = n.n(l); - function p(t) { - if ( - ("undefined" !== typeof u.a && null != t[f.a]) || - null != t["@@iterator"] - ) - return d()(t); - } - function h(t, e) { - if (t) { - if ("string" === typeof t) return o(t, e); - var n = Object.prototype.toString.call(t).slice(8, -1); - return ( - "Object" === n && - t.constructor && - (n = t.constructor.name), - "Map" === n || "Set" === n - ? d()(t) - : "Arguments" === n || - /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n) - ? o(t, e) - : void 0 - ); - } - } - function v() { - throw new TypeError( - "Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method." - ); - } - function y(t) { - return a(t) || p(t) || h(t) || v(); - } - n.d(e, "a", function () { - return y; - }); - }, - 7633: function (t, e, n) { - var r = n("2695"), - i = n("0029"); - t.exports = - Object.keys || - function (t) { - return r(t, i); - }; - }, - 7726: function (t, e) { - var n = (t.exports = - "undefined" != typeof window && window.Math == Math - ? window - : "undefined" != typeof self && self.Math == Math - ? self - : Function("return this")()); - "number" == typeof __g && (__g = n); - }, - "772d": function (t, e, n) { - "use strict"; - Object.defineProperty(e, "__esModule", { value: !0 }), - (e.default = void 0); - var r = n("78ef"), - i = - /^(?:(?:https?|ftp):\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:[/?#]\S*)?$/i, - o = (0, r.regex)("url", i); - e.default = o; - }, - "774e": function (t, e, n) { - t.exports = n("57f7"); - }, - 7772: function (t, e, n) { - var r = n("a7d3"), - i = n("da3c"), - o = "__core-js_shared__", - a = i[o] || (i[o] = {}); - (t.exports = function (t, e) { - return a[t] || (a[t] = void 0 !== e ? e : {}); - })("versions", []).push({ - version: r.version, - mode: n("b457") ? "pure" : "global", - copyright: "© 2020 Denis Pushkarev (zloirock.ru)", - }); - }, - "77f1": function (t, e, n) { - var r = n("4588"), - i = Math.max, - o = Math.min; - t.exports = function (t, e) { - return (t = r(t)), t < 0 ? i(t + e, 0) : o(t, e); - }; - }, - "78ef": function (t, e, n) { - "use strict"; - Object.defineProperty(e, "__esModule", { value: !0 }), - Object.defineProperty(e, "withParams", { - enumerable: !0, - get: function () { - return r.default; - }, - }), - (e.regex = e.ref = e.len = e.req = void 0); - var r = i(n("8750")); - function i(t) { - return t && t.__esModule ? t : { default: t }; - } - function o(t) { - return ( - (o = - "function" === typeof Symbol && - "symbol" === typeof Symbol.iterator - ? function (t) { - return typeof t; - } - : function (t) { - return t && - "function" === typeof Symbol && - t.constructor === Symbol && - t !== Symbol.prototype - ? "symbol" - : typeof t; - }), - o(t) - ); - } - var a = function (t) { - if (Array.isArray(t)) return !!t.length; - if (void 0 === t || null === t) return !1; - if (!1 === t) return !0; - if (t instanceof Date) return !isNaN(t.getTime()); - if ("object" === o(t)) { - for (var e in t) return !0; - return !1; - } - return !!String(t).length; - }; - e.req = a; - var s = function (t) { - return Array.isArray(t) - ? t.length - : "object" === o(t) - ? Object.keys(t).length - : String(t).length; - }; - e.len = s; - var u = function (t, e, n) { - return "function" === typeof t ? t.call(e, n) : n[t]; - }; - e.ref = u; - var c = function (t, e) { - return (0, r.default)({ type: t }, function (t) { - return !a(t) || e.test(t); - }); - }; - e.regex = c; - }, - "79e5": function (t, e) { - t.exports = function (t) { - try { - return !!t(); - } catch (e) { - return !0; - } - }; - }, - "7a56": function (t, e, n) { - "use strict"; - var r = n("7726"), - i = n("86cc"), - o = n("9e1e"), - a = n("2b4c")("species"); - t.exports = function (t) { - var e = r[t]; - o && - e && - !e[a] && - i.f(e, a, { - configurable: !0, - get: function () { - return this; - }, - }); - }; - }, - "7b00": function (t, e) { - var n = 0, - r = Math.random(); - t.exports = function (t) { - return "Symbol(".concat( - void 0 === t ? "" : t, - ")_", - (++n + r).toString(36) - ); - }; - }, - "7d8a": function (t, e, n) { - var r = n("6e1f"), - i = n("1b55")("toStringTag"), - o = - "Arguments" == - r( - (function () { - return arguments; - })() - ), - a = function (t, e) { - try { - return t[e]; - } catch (n) {} - }; - t.exports = function (t) { - var e, n, s; - return void 0 === t - ? "Undefined" - : null === t - ? "Null" - : "string" == typeof (n = a((e = Object(t)), i)) - ? n - : o - ? r(e) - : "Object" == (s = r(e)) && "function" == typeof e.callee - ? "Arguments" - : s; - }; - }, - "7d95": function (t, e, n) { - t.exports = !n("d782")(function () { - return ( - 7 != - Object.defineProperty({}, "a", { - get: function () { - return 7; - }, - }).a - ); - }); - }, - "7f20": function (t, e, n) { - var r = n("86cc").f, - i = n("69a8"), - o = n("2b4c")("toStringTag"); - t.exports = function (t, e, n) { - t && - !i((t = n ? t : t.prototype), o) && - r(t, o, { configurable: !0, value: e }); - }; - }, - 8079: function (t, e, n) { - var r = n("7726"), - i = n("1991").set, - o = r.MutationObserver || r.WebKitMutationObserver, - a = r.process, - s = r.Promise, - u = "process" == n("2d95")(a); - t.exports = function () { - var t, - e, - n, - c = function () { - var r, i; - u && (r = a.domain) && r.exit(); - while (t) { - (i = t.fn), (t = t.next); - try { - i(); - } catch (o) { - throw (t ? n() : (e = void 0), o); - } - } - (e = void 0), r && r.enter(); - }; - if (u) - n = function () { - a.nextTick(c); - }; - else if (!o || (r.navigator && r.navigator.standalone)) - if (s && s.resolve) { - var f = s.resolve(void 0); - n = function () { - f.then(c); - }; - } else - n = function () { - i.call(r, c); - }; - else { - var l = !0, - d = document.createTextNode(""); - new o(c).observe(d, { characterData: !0 }), - (n = function () { - d.data = l = !l; - }); - } - return function (r) { - var i = { fn: r, next: void 0 }; - e && (e.next = i), t || ((t = i), n()), (e = i); - }; - }; - }, - 8378: function (t, e) { - var n = (t.exports = { version: "2.6.9" }); - "number" == typeof __e && (__e = n); - }, - "84f2": function (t, e) { - t.exports = {}; - }, - "852e": function (t, e, n) { - (function (e, n) { - t.exports = n(); - })(0, function () { - "use strict"; - function t(t) { - for (var e = 1; e < arguments.length; e++) { - var n = arguments[e]; - for (var r in n) t[r] = n[r]; - } - return t; - } - var e = { - read: function (t) { - return ( - '"' === t[0] && (t = t.slice(1, -1)), - t.replace(/(%[\dA-F]{2})+/gi, decodeURIComponent) - ); - }, - write: function (t) { - return encodeURIComponent(t).replace( - /%(2[346BF]|3[AC-F]|40|5[BDE]|60|7[BCD])/g, - decodeURIComponent - ); - }, - }; - function n(e, r) { - function i(n, i, o) { - if ("undefined" !== typeof document) { - (o = t({}, r, o)), - "number" === typeof o.expires && - (o.expires = new Date( - Date.now() + 864e5 * o.expires - )), - o.expires && - (o.expires = o.expires.toUTCString()), - (n = encodeURIComponent(n) - .replace( - /%(2[346B]|5E|60|7C)/g, - decodeURIComponent - ) - .replace(/[()]/g, escape)); - var a = ""; - for (var s in o) - o[s] && - ((a += "; " + s), - !0 !== o[s] && - (a += "=" + o[s].split(";")[0])); - return (document.cookie = - n + "=" + e.write(i, n) + a); - } - } - function o(t) { - if ( - "undefined" !== typeof document && - (!arguments.length || t) - ) { - for ( - var n = document.cookie - ? document.cookie.split("; ") - : [], - r = {}, - i = 0; - i < n.length; - i++ - ) { - var o = n[i].split("="), - a = o.slice(1).join("="); - try { - var s = decodeURIComponent(o[0]); - if (((r[s] = e.read(a, s)), t === s)) break; - } catch (u) {} - } - return t ? r[t] : r; - } - } - return Object.create( - { - set: i, - get: o, - remove: function (e, n) { - i(e, "", t({}, n, { expires: -1 })); - }, - withAttributes: function (e) { - return n( - this.converter, - t({}, this.attributes, e) - ); - }, - withConverter: function (e) { - return n( - t({}, this.converter, e), - this.attributes - ); - }, - }, - { - attributes: { value: Object.freeze(r) }, - converter: { value: Object.freeze(e) }, - } - ); - } - var r = n(e, { path: "/" }); - return r; - }); - }, - "85cd": function (t, e, n) { - var r = n("0185"), - i = n("ff0c"); - n("c165")("getPrototypeOf", function () { - return function (t) { - return i(r(t)); - }; - }); - }, - "85f2": function (t, e, n) { - t.exports = n("ec5b"); - }, - "86cc": function (t, e, n) { - var r = n("cb7c"), - i = n("c69a"), - o = n("6a99"), - a = Object.defineProperty; - e.f = n("9e1e") - ? Object.defineProperty - : function (t, e, n) { - if ((r(t), (e = o(e, !0)), r(n), i)) - try { - return a(t, e, n); - } catch (s) {} - if ("get" in n || "set" in n) - throw TypeError("Accessors not supported!"); - return "value" in n && (t[e] = n.value), t; - }; - }, - 8747: function (t, e, n) { - var r = n("d13f"); - r(r.P + r.R, "Map", { toJSON: n("034c")("Map") }); - }, - 8750: function (t, e, n) { - "use strict"; - Object.defineProperty(e, "__esModule", { value: !0 }), - (e.default = void 0); - var r = - "web" === - Object({ NODE_ENV: "production", BASE_URL: "/" }).BUILD - ? n("cb69").withParams - : n("0234").withParams, - i = r; - e.default = i; - }, - "8a77": function (t, e, n) { - n("111f")("Map"); - }, - "8b97": function (t, e, n) { - var r = n("d3f4"), - i = n("cb7c"), - o = function (t, e) { - if ((i(t), !r(e) && null !== e)) - throw TypeError(e + ": can't set as prototype!"); - }; - t.exports = { - set: - Object.setPrototypeOf || - ("__proto__" in {} - ? (function (t, e, r) { - try { - (r = n("9b43")( - Function.call, - n("11e9").f(Object.prototype, "__proto__") - .set, - 2 - )), - r(t, []), - (e = !(t instanceof Array)); - } catch (i) { - e = !0; - } - return function (t, n) { - return ( - o(t, n), - e ? (t.__proto__ = n) : r(t, n), - t - ); - }; - })({}, !1) - : void 0), - check: o, - }; - }, - "8bab": function (t, e, n) { - var r = n("6e1f"); - t.exports = Object("z").propertyIsEnumerable(0) - ? Object - : function (t) { - return "String" == r(t) ? t.split("") : Object(t); - }; - }, - "8ce0": function (t, e, n) { - var r = n("3adc"), - i = n("f845"); - t.exports = n("7d95") - ? function (t, e, n) { - return r.f(t, e, i(1, n)); - } - : function (t, e, n) { - return (t[e] = n), t; - }; - }, - "91d3": function (t, e, n) { - "use strict"; - Object.defineProperty(e, "__esModule", { value: !0 }), - (e.default = void 0); - var r = n("78ef"), - i = function () { - var t = - arguments.length > 0 && void 0 !== arguments[0] - ? arguments[0] - : ":"; - return (0, r.withParams)( - { type: "macAddress" }, - function (e) { - if (!(0, r.req)(e)) return !0; - if ("string" !== typeof e) return !1; - var n = - "string" === typeof t && "" !== t - ? e.split(t) - : 12 === e.length || 16 === e.length - ? e.match(/.{2}/g) - : null; - return ( - null !== n && - (6 === n.length || 8 === n.length) && - n.every(o) - ); - } - ); - }; - e.default = i; - var o = function (t) { - return t.toLowerCase().match(/^[0-9a-f]{2}$/); - }; - }, - "93c4": function (t, e, n) { - "use strict"; - var r = n("2a4e")(!0); - n("e4a9")( - String, - "String", - function (t) { - (this._t = String(t)), (this._i = 0); - }, - function () { - var t, - e = this._t, - n = this._i; - return n >= e.length - ? { value: void 0, done: !0 } - : ((t = r(e, n)), - (this._i += t.length), - { value: t, done: !1 }); - } - ); - }, - "9b43": function (t, e, n) { - var r = n("d8e8"); - t.exports = function (t, e, n) { - if ((r(t), void 0 === e)) return t; - switch (n) { - case 1: - return function (n) { - return t.call(e, n); - }; - case 2: - return function (n, r) { - return t.call(e, n, r); - }; - case 3: - return function (n, r, i) { - return t.call(e, n, r, i); - }; - } - return function () { - return t.apply(e, arguments); - }; - }; - }, - "9c6c": function (t, e, n) { - var r = n("2b4c")("unscopables"), - i = Array.prototype; - void 0 == i[r] && n("32e9")(i, r, {}), - (t.exports = function (t) { - i[r][t] = !0; - }); - }, - "9c80": function (t, e) { - t.exports = function (t) { - try { - return { e: !1, v: t() }; - } catch (e) { - return { e: !0, v: e }; - } - }; - }, - "9c93": function (t, e, n) { - var r = n("0f89"); - t.exports = function (t, e, n, i) { - try { - return i ? e(r(n)[0], n[1]) : e(n); - } catch (a) { - var o = t["return"]; - throw (void 0 !== o && r(o.call(t)), a); - } - }; - }, - "9def": function (t, e, n) { - var r = n("4588"), - i = Math.min; - t.exports = function (t) { - return t > 0 ? i(r(t), 9007199254740991) : 0; - }; - }, - "9e1e": function (t, e, n) { - t.exports = !n("79e5")(function () { - return ( - 7 != - Object.defineProperty({}, "a", { - get: function () { - return 7; - }, - }).a - ); - }); - }, - a25f: function (t, e, n) { - var r = n("7726"), - i = r.navigator; - t.exports = (i && i.userAgent) || ""; - }, - a438: function (t, e, n) { - n("07c8"), (t.exports = n("a7d3").Object.setPrototypeOf); - }, - a47f: function (t, e, n) { - t.exports = - !n("7d95") && - !n("d782")(function () { - return ( - 7 != - Object.defineProperty(n("12fd")("div"), "a", { - get: function () { - return 7; - }, - }).a - ); - }); - }, - a481: function (t, e, n) { - "use strict"; - var r = n("cb7c"), - i = n("4bf8"), - o = n("9def"), - a = n("4588"), - s = n("0390"), - u = n("5f1b"), - c = Math.max, - f = Math.min, - l = Math.floor, - d = /\$([$&`']|\d\d?|<[^>]*>)/g, - p = /\$([$&`']|\d\d?)/g, - h = function (t) { - return void 0 === t ? t : String(t); - }; - n("214f")("replace", 2, function (t, e, n, v) { - return [ - function (r, i) { - var o = t(this), - a = void 0 == r ? void 0 : r[e]; - return void 0 !== a - ? a.call(r, o, i) - : n.call(String(o), r, i); - }, - function (t, e) { - var i = v(n, t, this, e); - if (i.done) return i.value; - var l = r(t), - d = String(this), - p = "function" === typeof e; - p || (e = String(e)); - var m = l.global; - if (m) { - var g = l.unicode; - l.lastIndex = 0; - } - var b = []; - while (1) { - var _ = u(l, d); - if (null === _) break; - if ((b.push(_), !m)) break; - var w = String(_[0]); - "" === w && (l.lastIndex = s(d, o(l.lastIndex), g)); - } - for (var x = "", k = 0, S = 0; S < b.length; S++) { - _ = b[S]; - for ( - var E = String(_[0]), - O = c(f(a(_.index), d.length), 0), - T = [], - A = 1; - A < _.length; - A++ - ) - T.push(h(_[A])); - var C = _.groups; - if (p) { - var j = [E].concat(T, O, d); - void 0 !== C && j.push(C); - var I = String(e.apply(void 0, j)); - } else I = y(E, d, O, T, C, e); - O >= k && - ((x += d.slice(k, O) + I), (k = O + E.length)); - } - return x + d.slice(k); - }, - ]; - function y(t, e, r, o, a, s) { - var u = r + t.length, - c = o.length, - f = p; - return ( - void 0 !== a && ((a = i(a)), (f = d)), - n.call(s, f, function (n, i) { - var s; - switch (i.charAt(0)) { - case "$": - return "$"; - case "&": - return t; - case "`": - return e.slice(0, r); - case "'": - return e.slice(u); - case "<": - s = a[i.slice(1, -1)]; - break; - default: - var f = +i; - if (0 === f) return n; - if (f > c) { - var d = l(f / 10); - return 0 === d - ? n - : d <= c - ? void 0 === o[d - 1] - ? i.charAt(1) - : o[d - 1] + i.charAt(1) - : n; - } - s = o[f - 1]; - } - return void 0 === s ? "" : s; - }) - ); - } - }); - }, - a5ab: function (t, e, n) { - var r = n("a812"), - i = Math.min; - t.exports = function (t) { - return t > 0 ? i(r(t), 9007199254740991) : 0; - }; - }, - a5b2: function (t, e, n) { - t.exports = n("f4bb"); - }, - a5b8: function (t, e, n) { - "use strict"; - var r = n("d8e8"); - function i(t) { - var e, n; - (this.promise = new t(function (t, r) { - if (void 0 !== e || void 0 !== n) - throw TypeError("Bad Promise constructor"); - (e = t), (n = r); - })), - (this.resolve = r(e)), - (this.reject = r(n)); - } - t.exports.f = function (t) { - return new i(t); - }; - }, - a745: function (t, e, n) { - t.exports = n("d604"); - }, - a7d3: function (t, e) { - var n = (t.exports = { version: "2.6.12" }); - "number" == typeof __e && (__e = n); - }, - a812: function (t, e) { - var n = Math.ceil, - r = Math.floor; - t.exports = function (t) { - return isNaN((t = +t)) ? 0 : (t > 0 ? r : n)(t); - }; - }, - a925: function (t, e, n) { - "use strict"; - /*! - * vue-i18n v8.12.0 - * (c) 2019 kazuya kawaguchi - * Released under the MIT License. - */ var r = [ - "style", - "currency", - "currencyDisplay", - "useGrouping", - "minimumIntegerDigits", - "minimumFractionDigits", - "maximumFractionDigits", - "minimumSignificantDigits", - "maximumSignificantDigits", - "localeMatcher", - "formatMatcher", - ]; - function i(t, e) { - "undefined" !== typeof console && - (console.warn("[vue-i18n] " + t), - e && console.warn(e.stack)); - } - function o(t, e) { - "undefined" !== typeof console && - (console.error("[vue-i18n] " + t), - e && console.error(e.stack)); - } - function a(t) { - return null !== t && "object" === typeof t; - } - var s = Object.prototype.toString, - u = "[object Object]"; - function c(t) { - return s.call(t) === u; - } - function f(t) { - return null === t || void 0 === t; - } - function l() { - var t = [], - e = arguments.length; - while (e--) t[e] = arguments[e]; - var n = null, - r = null; - return ( - 1 === t.length - ? a(t[0]) || Array.isArray(t[0]) - ? (r = t[0]) - : "string" === typeof t[0] && (n = t[0]) - : 2 === t.length && - ("string" === typeof t[0] && (n = t[0]), - (a(t[1]) || Array.isArray(t[1])) && (r = t[1])), - { locale: n, params: r } - ); - } - function d(t) { - return JSON.parse(JSON.stringify(t)); - } - function p(t, e) { - if (t.length) { - var n = t.indexOf(e); - if (n > -1) return t.splice(n, 1); - } - } - var h = Object.prototype.hasOwnProperty; - function v(t, e) { - return h.call(t, e); - } - function y(t) { - for ( - var e = arguments, n = Object(t), r = 1; - r < arguments.length; - r++ - ) { - var i = e[r]; - if (void 0 !== i && null !== i) { - var o = void 0; - for (o in i) - v(i, o) && - (a(i[o]) - ? (n[o] = y(n[o], i[o])) - : (n[o] = i[o])); - } - } - return n; - } - function m(t, e) { - if (t === e) return !0; - var n = a(t), - r = a(e); - if (!n || !r) return !n && !r && String(t) === String(e); - try { - var i = Array.isArray(t), - o = Array.isArray(e); - if (i && o) - return ( - t.length === e.length && - t.every(function (t, n) { - return m(t, e[n]); - }) - ); - if (i || o) return !1; - var s = Object.keys(t), - u = Object.keys(e); - return ( - s.length === u.length && - s.every(function (n) { - return m(t[n], e[n]); - }) - ); - } catch (c) { - return !1; - } - } - function g(t) { - t.prototype.hasOwnProperty("$i18n") || - Object.defineProperty(t.prototype, "$i18n", { - get: function () { - return this._i18n; - }, - }), - (t.prototype.$t = function (t) { - var e = [], - n = arguments.length - 1; - while (n-- > 0) e[n] = arguments[n + 1]; - var r = this.$i18n; - return r._t.apply( - r, - [t, r.locale, r._getMessages(), this].concat(e) - ); - }), - (t.prototype.$tc = function (t, e) { - var n = [], - r = arguments.length - 2; - while (r-- > 0) n[r] = arguments[r + 2]; - var i = this.$i18n; - return i._tc.apply( - i, - [t, i.locale, i._getMessages(), this, e].concat(n) - ); - }), - (t.prototype.$te = function (t, e) { - var n = this.$i18n; - return n._te(t, n.locale, n._getMessages(), e); - }), - (t.prototype.$d = function (t) { - var e, - n = [], - r = arguments.length - 1; - while (r-- > 0) n[r] = arguments[r + 1]; - return (e = this.$i18n).d.apply(e, [t].concat(n)); - }), - (t.prototype.$n = function (t) { - var e, - n = [], - r = arguments.length - 1; - while (r-- > 0) n[r] = arguments[r + 1]; - return (e = this.$i18n).n.apply(e, [t].concat(n)); - }); - } - var b, - _ = { - beforeCreate: function () { - var t = this.$options; - if ( - ((t.i18n = t.i18n || (t.__i18n ? {} : null)), - t.i18n) - ) - if (t.i18n instanceof lt) { - if (t.__i18n) - try { - var e = {}; - t.__i18n.forEach(function (t) { - e = y(e, JSON.parse(t)); - }), - Object.keys(e).forEach(function ( - n - ) { - t.i18n.mergeLocaleMessage( - n, - e[n] - ); - }); - } catch (o) { - 0; - } - (this._i18n = t.i18n), - (this._i18nWatcher = - this._i18n.watchI18nData()); - } else if (c(t.i18n)) { - if ( - (this.$root && - this.$root.$i18n && - this.$root.$i18n instanceof lt && - ((t.i18n.root = this.$root), - (t.i18n.formatter = - this.$root.$i18n.formatter), - (t.i18n.fallbackLocale = - this.$root.$i18n.fallbackLocale), - (t.i18n.silentTranslationWarn = - this.$root.$i18n.silentTranslationWarn), - (t.i18n.silentFallbackWarn = - this.$root.$i18n.silentFallbackWarn), - (t.i18n.pluralizationRules = - this.$root.$i18n.pluralizationRules), - (t.i18n.preserveDirectiveContent = - this.$root.$i18n.preserveDirectiveContent)), - t.__i18n) - ) - try { - var n = {}; - t.__i18n.forEach(function (t) { - n = y(n, JSON.parse(t)); - }), - (t.i18n.messages = n); - } catch (o) { - 0; - } - var r = t.i18n, - i = r.sharedMessages; - i && - c(i) && - (t.i18n.messages = y(t.i18n.messages, i)), - (this._i18n = new lt(t.i18n)), - (this._i18nWatcher = - this._i18n.watchI18nData()), - (void 0 === t.i18n.sync || t.i18n.sync) && - (this._localeWatcher = - this.$i18n.watchLocale()); - } else 0; - else - this.$root && - this.$root.$i18n && - this.$root.$i18n instanceof lt - ? (this._i18n = this.$root.$i18n) - : t.parent && - t.parent.$i18n && - t.parent.$i18n instanceof lt && - (this._i18n = t.parent.$i18n); - }, - beforeMount: function () { - var t = this.$options; - (t.i18n = t.i18n || (t.__i18n ? {} : null)), - t.i18n - ? (t.i18n instanceof lt || c(t.i18n)) && - (this._i18n.subscribeDataChanging(this), - (this._subscribing = !0)) - : ((this.$root && - this.$root.$i18n && - this.$root.$i18n instanceof lt) || - (t.parent && - t.parent.$i18n && - t.parent.$i18n instanceof lt)) && - (this._i18n.subscribeDataChanging(this), - (this._subscribing = !0)); - }, - beforeDestroy: function () { - if (this._i18n) { - var t = this; - this.$nextTick(function () { - t._subscribing && - (t._i18n.unsubscribeDataChanging(t), - delete t._subscribing), - t._i18nWatcher && - (t._i18nWatcher(), - t._i18n.destroyVM(), - delete t._i18nWatcher), - t._localeWatcher && - (t._localeWatcher(), - delete t._localeWatcher), - (t._i18n = null); - }); - } - }, - }, - w = { - name: "i18n", - functional: !0, - props: { - tag: { type: String, default: "span" }, - path: { type: String, required: !0 }, - locale: { type: String }, - places: { type: [Array, Object] }, - }, - render: function (t, e) { - var n = e.props, - r = e.data, - i = e.children, - o = e.parent, - a = o.$i18n; - if ( - ((i = (i || []).filter(function (t) { - return t.tag || (t.text = t.text.trim()); - })), - !a) - ) - return i; - var s = n.path, - u = n.locale, - c = {}, - f = n.places || {}, - l = - (Array.isArray(f) - ? f.length - : Object.keys(f).length, - i.every(function (t) { - if (t.data && t.data.attrs) { - var e = t.data.attrs.place; - return ( - "undefined" !== typeof e && "" !== e - ); - } - })); - return ( - Array.isArray(f) - ? f.forEach(function (t, e) { - c[e] = t; - }) - : Object.keys(f).forEach(function (t) { - c[t] = f[t]; - }), - i.forEach(function (t, e) { - var n = l ? "" + t.data.attrs.place : "" + e; - c[n] = t; - }), - t(n.tag, r, a.i(s, u, c)) - ); - }, - }, - x = { - name: "i18n-n", - functional: !0, - props: { - tag: { type: String, default: "span" }, - value: { type: Number, required: !0 }, - format: { type: [String, Object] }, - locale: { type: String }, - }, - render: function (t, e) { - var n = e.props, - i = e.parent, - o = e.data, - s = i.$i18n; - if (!s) return null; - var u = null, - c = null; - "string" === typeof n.format - ? (u = n.format) - : a(n.format) && - (n.format.key && (u = n.format.key), - (c = Object.keys(n.format).reduce(function ( - t, - e - ) { - var i; - return r.includes(e) - ? Object.assign( - {}, - t, - ((i = {}), (i[e] = n.format[e]), i) - ) - : t; - }, - null))); - var f = n.locale || s.locale, - l = s._ntp(n.value, f, u, c), - d = l.map(function (t, e) { - var n, - r = o.scopedSlots && o.scopedSlots[t.type]; - return r - ? r( - ((n = {}), - (n[t.type] = t.value), - (n.index = e), - (n.parts = l), - n) - ) - : t.value; - }); - return t( - n.tag, - { - attrs: o.attrs, - class: o["class"], - staticClass: o.staticClass, - }, - d - ); - }, - }; - function k(t, e, n) { - O(t, n) && A(t, e, n); - } - function S(t, e, n, r) { - if (O(t, n)) { - var i = n.context.$i18n; - (T(t, n) && - m(e.value, e.oldValue) && - m(t._localeMessage, i.getLocaleMessage(i.locale))) || - A(t, e, n); - } - } - function E(t, e, n, r) { - var o = n.context; - if (o) { - var a = n.context.$i18n || {}; - e.modifiers.preserve || - a.preserveDirectiveContent || - (t.textContent = ""), - (t._vt = void 0), - delete t["_vt"], - (t._locale = void 0), - delete t["_locale"], - (t._localeMessage = void 0), - delete t["_localeMessage"]; - } else i("Vue instance does not exists in VNode context"); - } - function O(t, e) { - var n = e.context; - return n - ? !!n.$i18n || - (i( - "VueI18n instance does not exists in Vue instance" - ), - !1) - : (i("Vue instance does not exists in VNode context"), !1); - } - function T(t, e) { - var n = e.context; - return t._locale === n.$i18n.locale; - } - function A(t, e, n) { - var r, - o, - a = e.value, - s = C(a), - u = s.path, - c = s.locale, - f = s.args, - l = s.choice; - if (u || c || f) - if (u) { - var d = n.context; - (t._vt = t.textContent = - l - ? (r = d.$i18n).tc.apply( - r, - [u, l].concat(j(c, f)) - ) - : (o = d.$i18n).t.apply( - o, - [u].concat(j(c, f)) - )), - (t._locale = d.$i18n.locale), - (t._localeMessage = d.$i18n.getLocaleMessage( - d.$i18n.locale - )); - } else i("`path` is required in v-t directive"); - else i("value type not supported"); - } - function C(t) { - var e, n, r, i; - return ( - "string" === typeof t - ? (e = t) - : c(t) && - ((e = t.path), - (n = t.locale), - (r = t.args), - (i = t.choice)), - { path: e, locale: n, args: r, choice: i } - ); - } - function j(t, e) { - var n = []; - return ( - t && n.push(t), - e && (Array.isArray(e) || c(e)) && n.push(e), - n - ); - } - function I(t) { - (I.installed = !0), (b = t); - b.version && Number(b.version.split(".")[0]); - g(b), - b.mixin(_), - b.directive("t", { bind: k, update: S, unbind: E }), - b.component(w.name, w), - b.component(x.name, x); - var e = b.config.optionMergeStrategies; - e.i18n = function (t, e) { - return void 0 === e ? t : e; - }; - } - var M = function () { - this._caches = Object.create(null); - }; - M.prototype.interpolate = function (t, e) { - if (!e) return [t]; - var n = this._caches[t]; - return n || ((n = N(t)), (this._caches[t] = n)), R(n, e); - }; - var L = /^(?:\d)+/, - P = /^(?:\w)+/; - function N(t) { - var e = [], - n = 0, - r = ""; - while (n < t.length) { - var i = t[n++]; - if ("{" === i) { - r && e.push({ type: "text", value: r }), (r = ""); - var o = ""; - i = t[n++]; - while (void 0 !== i && "}" !== i) - (o += i), (i = t[n++]); - var a = "}" === i, - s = L.test(o) - ? "list" - : a && P.test(o) - ? "named" - : "unknown"; - e.push({ value: o, type: s }); - } else "%" === i ? "{" !== t[n] && (r += i) : (r += i); - } - return r && e.push({ type: "text", value: r }), e; - } - function R(t, e) { - var n = [], - r = 0, - i = Array.isArray(e) ? "list" : a(e) ? "named" : "unknown"; - if ("unknown" === i) return n; - while (r < t.length) { - var o = t[r]; - switch (o.type) { - case "text": - n.push(o.value); - break; - case "list": - n.push(e[parseInt(o.value, 10)]); - break; - case "named": - "named" === i && n.push(e[o.value]); - break; - case "unknown": - 0; - break; - } - r++; - } - return n; - } - var $ = 0, - D = 1, - F = 2, - z = 3, - U = 0, - B = 1, - V = 2, - H = 3, - q = 4, - W = 5, - G = 6, - Z = 7, - J = 8, - K = []; - (K[U] = { ws: [U], ident: [H, $], "[": [q], eof: [Z] }), - (K[B] = { ws: [B], ".": [V], "[": [q], eof: [Z] }), - (K[V] = { ws: [V], ident: [H, $], 0: [H, $], number: [H, $] }), - (K[H] = { - ident: [H, $], - 0: [H, $], - number: [H, $], - ws: [B, D], - ".": [V, D], - "[": [q, D], - eof: [Z, D], - }), - (K[q] = { - "'": [W, $], - '"': [G, $], - "[": [q, F], - "]": [B, z], - eof: J, - else: [q, $], - }), - (K[W] = { "'": [q, $], eof: J, else: [W, $] }), - (K[G] = { '"': [q, $], eof: J, else: [G, $] }); - var X = /^\s?(?:true|false|-?[\d.]+|'[^']*'|"[^"]*")\s?$/; - function Y(t) { - return X.test(t); - } - function Q(t) { - var e = t.charCodeAt(0), - n = t.charCodeAt(t.length - 1); - return e !== n || (34 !== e && 39 !== e) ? t : t.slice(1, -1); - } - function tt(t) { - if (void 0 === t || null === t) return "eof"; - var e = t.charCodeAt(0); - switch (e) { - case 91: - case 93: - case 46: - case 34: - case 39: - return t; - case 95: - case 36: - case 45: - return "ident"; - case 9: - case 10: - case 13: - case 160: - case 65279: - case 8232: - case 8233: - return "ws"; - } - return "ident"; - } - function et(t) { - var e = t.trim(); - return ( - ("0" !== t.charAt(0) || !isNaN(t)) && - (Y(e) ? Q(e) : "*" + e) - ); - } - function nt(t) { - var e, - n, - r, - i, - o, - a, - s, - u = [], - c = -1, - f = U, - l = 0, - d = []; - function p() { - var e = t[c + 1]; - if ((f === W && "'" === e) || (f === G && '"' === e)) - return c++, (r = "\\" + e), d[$](), !0; - } - (d[D] = function () { - void 0 !== n && (u.push(n), (n = void 0)); - }), - (d[$] = function () { - void 0 === n ? (n = r) : (n += r); - }), - (d[F] = function () { - d[$](), l++; - }), - (d[z] = function () { - if (l > 0) l--, (f = q), d[$](); - else { - if (((l = 0), (n = et(n)), !1 === n)) return !1; - d[D](); - } - }); - while (null !== f) - if ((c++, (e = t[c]), "\\" !== e || !p())) { - if ( - ((i = tt(e)), - (s = K[f]), - (o = s[i] || s["else"] || J), - o === J) - ) - return; - if ( - ((f = o[0]), - (a = d[o[1]]), - a && - ((r = o[2]), - (r = void 0 === r ? e : r), - !1 === a())) - ) - return; - if (f === Z) return u; - } - } - var rt = function () { - this._cache = Object.create(null); - }; - (rt.prototype.parsePath = function (t) { - var e = this._cache[t]; - return e || ((e = nt(t)), e && (this._cache[t] = e)), e || []; - }), - (rt.prototype.getPathValue = function (t, e) { - if (!a(t)) return null; - var n = this.parsePath(e); - if (0 === n.length) return null; - var r = n.length, - i = t, - o = 0; - while (o < r) { - var s = i[n[o]]; - if (void 0 === s) return null; - (i = s), o++; - } - return i; - }); - var it, - ot = /<\/?[\w\s="/.':;#-\/]+>/, - at = /(?:@(?:\.[a-z]+)?:(?:[\w\-_|.]+|\([\w\-_|.]+\)))/g, - st = /^@(?:\.([a-z]+))?:/, - ut = /[()]/g, - ct = { - upper: function (t) { - return t.toLocaleUpperCase(); - }, - lower: function (t) { - return t.toLocaleLowerCase(); - }, - }, - ft = new M(), - lt = function (t) { - var e = this; - void 0 === t && (t = {}), - !b && - "undefined" !== typeof window && - window.Vue && - I(window.Vue); - var n = t.locale || "en-US", - r = t.fallbackLocale || "en-US", - i = t.messages || {}, - o = t.dateTimeFormats || {}, - a = t.numberFormats || {}; - (this._vm = null), - (this._formatter = t.formatter || ft), - (this._missing = t.missing || null), - (this._root = t.root || null), - (this._sync = void 0 === t.sync || !!t.sync), - (this._fallbackRoot = - void 0 === t.fallbackRoot || !!t.fallbackRoot), - (this._silentTranslationWarn = - void 0 !== t.silentTranslationWarn && - !!t.silentTranslationWarn), - (this._silentFallbackWarn = - void 0 !== t.silentFallbackWarn && - !!t.silentFallbackWarn), - (this._dateTimeFormatters = {}), - (this._numberFormatters = {}), - (this._path = new rt()), - (this._dataListeners = []), - (this._preserveDirectiveContent = - void 0 !== t.preserveDirectiveContent && - !!t.preserveDirectiveContent), - (this.pluralizationRules = t.pluralizationRules || {}), - (this._warnHtmlInMessage = - t.warnHtmlInMessage || "off"), - (this._exist = function (t, n) { - return ( - !(!t || !n) && - (!f(e._path.getPathValue(t, n)) || !!t[n]) - ); - }), - ("warn" !== this._warnHtmlInMessage && - "error" !== this._warnHtmlInMessage) || - Object.keys(i).forEach(function (t) { - e._checkLocaleMessage( - t, - e._warnHtmlInMessage, - i[t] - ); - }), - this._initVM({ - locale: n, - fallbackLocale: r, - messages: i, - dateTimeFormats: o, - numberFormats: a, - }); - }, - dt = { - vm: { configurable: !0 }, - messages: { configurable: !0 }, - dateTimeFormats: { configurable: !0 }, - numberFormats: { configurable: !0 }, - availableLocales: { configurable: !0 }, - locale: { configurable: !0 }, - fallbackLocale: { configurable: !0 }, - missing: { configurable: !0 }, - formatter: { configurable: !0 }, - silentTranslationWarn: { configurable: !0 }, - silentFallbackWarn: { configurable: !0 }, - preserveDirectiveContent: { configurable: !0 }, - warnHtmlInMessage: { configurable: !0 }, - }; - (lt.prototype._checkLocaleMessage = function (t, e, n) { - var r = [], - a = function (t, e, n, r) { - if (c(n)) - Object.keys(n).forEach(function (i) { - var o = n[i]; - c(o) - ? (r.push(i), - r.push("."), - a(t, e, o, r), - r.pop(), - r.pop()) - : (r.push(i), a(t, e, o, r), r.pop()); - }); - else if (Array.isArray(n)) - n.forEach(function (n, i) { - c(n) - ? (r.push("[" + i + "]"), - r.push("."), - a(t, e, n, r), - r.pop(), - r.pop()) - : (r.push("[" + i + "]"), - a(t, e, n, r), - r.pop()); - }); - else if ("string" === typeof n) { - var s = ot.test(n); - if (s) { - var u = - "Detected HTML in message '" + - n + - "' of keypath '" + - r.join("") + - "' at '" + - e + - "'. Consider component interpolation with '' to avoid XSS. See https://bit.ly/2ZqJzkp"; - "warn" === t ? i(u) : "error" === t && o(u); - } - } - }; - a(e, t, n, r); - }), - (lt.prototype._initVM = function (t) { - var e = b.config.silent; - (b.config.silent = !0), - (this._vm = new b({ data: t })), - (b.config.silent = e); - }), - (lt.prototype.destroyVM = function () { - this._vm.$destroy(); - }), - (lt.prototype.subscribeDataChanging = function (t) { - this._dataListeners.push(t); - }), - (lt.prototype.unsubscribeDataChanging = function (t) { - p(this._dataListeners, t); - }), - (lt.prototype.watchI18nData = function () { - var t = this; - return this._vm.$watch( - "$data", - function () { - var e = t._dataListeners.length; - while (e--) - b.nextTick(function () { - t._dataListeners[e] && - t._dataListeners[e].$forceUpdate(); - }); - }, - { deep: !0 } - ); - }), - (lt.prototype.watchLocale = function () { - if (!this._sync || !this._root) return null; - var t = this._vm; - return this._root.$i18n.vm.$watch( - "locale", - function (e) { - t.$set(t, "locale", e), t.$forceUpdate(); - }, - { immediate: !0 } - ); - }), - (dt.vm.get = function () { - return this._vm; - }), - (dt.messages.get = function () { - return d(this._getMessages()); - }), - (dt.dateTimeFormats.get = function () { - return d(this._getDateTimeFormats()); - }), - (dt.numberFormats.get = function () { - return d(this._getNumberFormats()); - }), - (dt.availableLocales.get = function () { - return Object.keys(this.messages).sort(); - }), - (dt.locale.get = function () { - return this._vm.locale; - }), - (dt.locale.set = function (t) { - this._vm.$set(this._vm, "locale", t); - }), - (dt.fallbackLocale.get = function () { - return this._vm.fallbackLocale; - }), - (dt.fallbackLocale.set = function (t) { - this._vm.$set(this._vm, "fallbackLocale", t); - }), - (dt.missing.get = function () { - return this._missing; - }), - (dt.missing.set = function (t) { - this._missing = t; - }), - (dt.formatter.get = function () { - return this._formatter; - }), - (dt.formatter.set = function (t) { - this._formatter = t; - }), - (dt.silentTranslationWarn.get = function () { - return this._silentTranslationWarn; - }), - (dt.silentTranslationWarn.set = function (t) { - this._silentTranslationWarn = t; - }), - (dt.silentFallbackWarn.get = function () { - return this._silentFallbackWarn; - }), - (dt.silentFallbackWarn.set = function (t) { - this._silentFallbackWarn = t; - }), - (dt.preserveDirectiveContent.get = function () { - return this._preserveDirectiveContent; - }), - (dt.preserveDirectiveContent.set = function (t) { - this._preserveDirectiveContent = t; - }), - (dt.warnHtmlInMessage.get = function () { - return this._warnHtmlInMessage; - }), - (dt.warnHtmlInMessage.set = function (t) { - var e = this, - n = this._warnHtmlInMessage; - if ( - ((this._warnHtmlInMessage = t), - n !== t && ("warn" === t || "error" === t)) - ) { - var r = this._getMessages(); - Object.keys(r).forEach(function (t) { - e._checkLocaleMessage( - t, - e._warnHtmlInMessage, - r[t] - ); - }); - } - }), - (lt.prototype._getMessages = function () { - return this._vm.messages; - }), - (lt.prototype._getDateTimeFormats = function () { - return this._vm.dateTimeFormats; - }), - (lt.prototype._getNumberFormats = function () { - return this._vm.numberFormats; - }), - (lt.prototype._warnDefault = function (t, e, n, r, i) { - if (!f(n)) return n; - if (this._missing) { - var o = this._missing.apply(null, [t, e, r, i]); - if ("string" === typeof o) return o; - } else 0; - return e; - }), - (lt.prototype._isFallbackRoot = function (t) { - return !t && !f(this._root) && this._fallbackRoot; - }), - (lt.prototype._isSilentFallback = function (t) { - return ( - this._silentFallbackWarn && - (this._isFallbackRoot() || t !== this.fallbackLocale) - ); - }), - (lt.prototype._interpolate = function (t, e, n, r, i, o, a) { - if (!e) return null; - var s, - u = this._path.getPathValue(e, n); - if (Array.isArray(u) || c(u)) return u; - if (f(u)) { - if (!c(e)) return null; - if (((s = e[n]), "string" !== typeof s)) return null; - } else { - if ("string" !== typeof u) return null; - s = u; - } - return ( - (s.indexOf("@:") >= 0 || s.indexOf("@.") >= 0) && - (s = this._link(t, e, s, r, "raw", o, a)), - this._render(s, i, o, n) - ); - }), - (lt.prototype._link = function (t, e, n, r, i, o, a) { - var s = n, - u = s.match(at); - for (var c in u) - if (u.hasOwnProperty(c)) { - var f = u[c], - l = f.match(st), - d = l[0], - p = l[1], - h = f.replace(d, "").replace(ut, ""); - if (a.includes(h)) return s; - a.push(h); - var v = this._interpolate( - t, - e, - h, - r, - "raw" === i ? "string" : i, - "raw" === i ? void 0 : o, - a - ); - if (this._isFallbackRoot(v)) { - if (!this._root) - throw Error("unexpected error"); - var y = this._root.$i18n; - v = y._translate( - y._getMessages(), - y.locale, - y.fallbackLocale, - h, - r, - i, - o - ); - } - (v = this._warnDefault( - t, - h, - v, - r, - Array.isArray(o) ? o : [o] - )), - ct.hasOwnProperty(p) && (v = ct[p](v)), - a.pop(), - (s = v ? s.replace(f, v) : s); - } - return s; - }), - (lt.prototype._render = function (t, e, n, r) { - var i = this._formatter.interpolate(t, n, r); - return ( - i || (i = ft.interpolate(t, n, r)), - "string" === e ? i.join("") : i - ); - }), - (lt.prototype._translate = function (t, e, n, r, i, o, a) { - var s = this._interpolate(e, t[e], r, i, o, a, [r]); - return f(s) - ? ((s = this._interpolate(n, t[n], r, i, o, a, [r])), - f(s) ? null : s) - : s; - }), - (lt.prototype._t = function (t, e, n, r) { - var i, - o = [], - a = arguments.length - 4; - while (a-- > 0) o[a] = arguments[a + 4]; - if (!t) return ""; - var s = l.apply(void 0, o), - u = s.locale || e, - c = this._translate( - n, - u, - this.fallbackLocale, - t, - r, - "string", - s.params - ); - if (this._isFallbackRoot(c)) { - if (!this._root) throw Error("unexpected error"); - return (i = this._root).$t.apply(i, [t].concat(o)); - } - return this._warnDefault(u, t, c, r, o); - }), - (lt.prototype.t = function (t) { - var e, - n = [], - r = arguments.length - 1; - while (r-- > 0) n[r] = arguments[r + 1]; - return (e = this)._t.apply( - e, - [t, this.locale, this._getMessages(), null].concat(n) - ); - }), - (lt.prototype._i = function (t, e, n, r, i) { - var o = this._translate( - n, - e, - this.fallbackLocale, - t, - r, - "raw", - i - ); - if (this._isFallbackRoot(o)) { - if (!this._root) throw Error("unexpected error"); - return this._root.$i18n.i(t, e, i); - } - return this._warnDefault(e, t, o, r, [i]); - }), - (lt.prototype.i = function (t, e, n) { - return t - ? ("string" !== typeof e && (e = this.locale), - this._i(t, e, this._getMessages(), null, n)) - : ""; - }), - (lt.prototype._tc = function (t, e, n, r, i) { - var o, - a = [], - s = arguments.length - 5; - while (s-- > 0) a[s] = arguments[s + 5]; - if (!t) return ""; - void 0 === i && (i = 1); - var u = { count: i, n: i }, - c = l.apply(void 0, a); - return ( - (c.params = Object.assign(u, c.params)), - (a = - null === c.locale - ? [c.params] - : [c.locale, c.params]), - this.fetchChoice( - (o = this)._t.apply(o, [t, e, n, r].concat(a)), - i - ) - ); - }), - (lt.prototype.fetchChoice = function (t, e) { - if (!t && "string" !== typeof t) return null; - var n = t.split("|"); - return ( - (e = this.getChoiceIndex(e, n.length)), - n[e] ? n[e].trim() : t - ); - }), - (lt.prototype.getChoiceIndex = function (t, e) { - var n = function (t, e) { - return ( - (t = Math.abs(t)), - 2 === e - ? t - ? t > 1 - ? 1 - : 0 - : 1 - : t - ? Math.min(t, 2) - : 0 - ); - }; - return this.locale in this.pluralizationRules - ? this.pluralizationRules[this.locale].apply(this, [ - t, - e, - ]) - : n(t, e); - }), - (lt.prototype.tc = function (t, e) { - var n, - r = [], - i = arguments.length - 2; - while (i-- > 0) r[i] = arguments[i + 2]; - return (n = this)._tc.apply( - n, - [t, this.locale, this._getMessages(), null, e].concat(r) - ); - }), - (lt.prototype._te = function (t, e, n) { - var r = [], - i = arguments.length - 3; - while (i-- > 0) r[i] = arguments[i + 3]; - var o = l.apply(void 0, r).locale || e; - return this._exist(n[o], t); - }), - (lt.prototype.te = function (t, e) { - return this._te(t, this.locale, this._getMessages(), e); - }), - (lt.prototype.getLocaleMessage = function (t) { - return d(this._vm.messages[t] || {}); - }), - (lt.prototype.setLocaleMessage = function (t, e) { - (("warn" !== this._warnHtmlInMessage && - "error" !== this._warnHtmlInMessage) || - (this._checkLocaleMessage( - t, - this._warnHtmlInMessage, - e - ), - "error" !== this._warnHtmlInMessage)) && - this._vm.$set(this._vm.messages, t, e); - }), - (lt.prototype.mergeLocaleMessage = function (t, e) { - (("warn" !== this._warnHtmlInMessage && - "error" !== this._warnHtmlInMessage) || - (this._checkLocaleMessage( - t, - this._warnHtmlInMessage, - e - ), - "error" !== this._warnHtmlInMessage)) && - this._vm.$set( - this._vm.messages, - t, - y(this._vm.messages[t] || {}, e) - ); - }), - (lt.prototype.getDateTimeFormat = function (t) { - return d(this._vm.dateTimeFormats[t] || {}); - }), - (lt.prototype.setDateTimeFormat = function (t, e) { - this._vm.$set(this._vm.dateTimeFormats, t, e); - }), - (lt.prototype.mergeDateTimeFormat = function (t, e) { - this._vm.$set( - this._vm.dateTimeFormats, - t, - y(this._vm.dateTimeFormats[t] || {}, e) - ); - }), - (lt.prototype._localizeDateTime = function (t, e, n, r, i) { - var o = e, - a = r[o]; - if ( - ((f(a) || f(a[i])) && ((o = n), (a = r[o])), - f(a) || f(a[i])) - ) - return null; - var s = a[i], - u = o + "__" + i, - c = this._dateTimeFormatters[u]; - return ( - c || - (c = this._dateTimeFormatters[u] = - new Intl.DateTimeFormat(o, s)), - c.format(t) - ); - }), - (lt.prototype._d = function (t, e, n) { - if (!n) return new Intl.DateTimeFormat(e).format(t); - var r = this._localizeDateTime( - t, - e, - this.fallbackLocale, - this._getDateTimeFormats(), - n - ); - if (this._isFallbackRoot(r)) { - if (!this._root) throw Error("unexpected error"); - return this._root.$i18n.d(t, n, e); - } - return r || ""; - }), - (lt.prototype.d = function (t) { - var e = [], - n = arguments.length - 1; - while (n-- > 0) e[n] = arguments[n + 1]; - var r = this.locale, - i = null; - return ( - 1 === e.length - ? "string" === typeof e[0] - ? (i = e[0]) - : a(e[0]) && - (e[0].locale && (r = e[0].locale), - e[0].key && (i = e[0].key)) - : 2 === e.length && - ("string" === typeof e[0] && (i = e[0]), - "string" === typeof e[1] && (r = e[1])), - this._d(t, r, i) - ); - }), - (lt.prototype.getNumberFormat = function (t) { - return d(this._vm.numberFormats[t] || {}); - }), - (lt.prototype.setNumberFormat = function (t, e) { - this._vm.$set(this._vm.numberFormats, t, e); - }), - (lt.prototype.mergeNumberFormat = function (t, e) { - this._vm.$set( - this._vm.numberFormats, - t, - y(this._vm.numberFormats[t] || {}, e) - ); - }), - (lt.prototype._getNumberFormatter = function ( - t, - e, - n, - r, - i, - o - ) { - var a = e, - s = r[a]; - if ( - ((f(s) || f(s[i])) && ((a = n), (s = r[a])), - f(s) || f(s[i])) - ) - return null; - var u, - c = s[i]; - if (o) - u = new Intl.NumberFormat(a, Object.assign({}, c, o)); - else { - var l = a + "__" + i; - (u = this._numberFormatters[l]), - u || - (u = this._numberFormatters[l] = - new Intl.NumberFormat(a, c)); - } - return u; - }), - (lt.prototype._n = function (t, e, n, r) { - if (!lt.availabilities.numberFormat) return ""; - if (!n) { - var i = r - ? new Intl.NumberFormat(e, r) - : new Intl.NumberFormat(e); - return i.format(t); - } - var o = this._getNumberFormatter( - t, - e, - this.fallbackLocale, - this._getNumberFormats(), - n, - r - ), - a = o && o.format(t); - if (this._isFallbackRoot(a)) { - if (!this._root) throw Error("unexpected error"); - return this._root.$i18n.n( - t, - Object.assign({}, { key: n, locale: e }, r) - ); - } - return a || ""; - }), - (lt.prototype.n = function (t) { - var e = [], - n = arguments.length - 1; - while (n-- > 0) e[n] = arguments[n + 1]; - var i = this.locale, - o = null, - s = null; - return ( - 1 === e.length - ? "string" === typeof e[0] - ? (o = e[0]) - : a(e[0]) && - (e[0].locale && (i = e[0].locale), - e[0].key && (o = e[0].key), - (s = Object.keys(e[0]).reduce(function ( - t, - n - ) { - var i; - return r.includes(n) - ? Object.assign( - {}, - t, - ((i = {}), (i[n] = e[0][n]), i) - ) - : t; - }, - null))) - : 2 === e.length && - ("string" === typeof e[0] && (o = e[0]), - "string" === typeof e[1] && (i = e[1])), - this._n(t, i, o, s) - ); - }), - (lt.prototype._ntp = function (t, e, n, r) { - if (!lt.availabilities.numberFormat) return []; - if (!n) { - var i = r - ? new Intl.NumberFormat(e, r) - : new Intl.NumberFormat(e); - return i.formatToParts(t); - } - var o = this._getNumberFormatter( - t, - e, - this.fallbackLocale, - this._getNumberFormats(), - n, - r - ), - a = o && o.formatToParts(t); - if (this._isFallbackRoot(a)) { - if (!this._root) throw Error("unexpected error"); - return this._root.$i18n._ntp(t, e, n, r); - } - return a || []; - }), - Object.defineProperties(lt.prototype, dt), - Object.defineProperty(lt, "availabilities", { - get: function () { - if (!it) { - var t = "undefined" !== typeof Intl; - it = { - dateTimeFormat: - t && - "undefined" !== typeof Intl.DateTimeFormat, - numberFormat: - t && - "undefined" !== typeof Intl.NumberFormat, - }; - } - return it; - }, - }), - (lt.install = I), - (lt.version = "8.12.0"), - (e["a"] = lt); - }, - aa82: function (t, e, n) { - "use strict"; - Object.defineProperty(e, "__esModule", { value: !0 }), - (e.default = void 0); - var r = n("78ef"), - i = function (t) { - return (0, r.withParams)( - { type: "requiredIf", prop: t }, - function (e, n) { - return !(0, r.ref)(t, this, n) || (0, r.req)(e); - } - ); - }; - e.default = i; - }, - aae3: function (t, e, n) { - var r = n("d3f4"), - i = n("2d95"), - o = n("2b4c")("match"); - t.exports = function (t) { - var e; - return r(t) && (void 0 !== (e = t[o]) ? !!e : "RegExp" == i(t)); - }; - }, - ac6a: function (t, e, n) { - for ( - var r = n("cadf"), - i = n("0d58"), - o = n("2aba"), - a = n("7726"), - s = n("32e9"), - u = n("84f2"), - c = n("2b4c"), - f = c("iterator"), - l = c("toStringTag"), - d = u.Array, - p = { - CSSRuleList: !0, - CSSStyleDeclaration: !1, - CSSValueList: !1, - ClientRectList: !1, - DOMRectList: !1, - DOMStringList: !1, - DOMTokenList: !0, - DataTransferItemList: !1, - FileList: !1, - HTMLAllCollection: !1, - HTMLCollection: !1, - HTMLFormElement: !1, - HTMLSelectElement: !1, - MediaList: !0, - MimeTypeArray: !1, - NamedNodeMap: !1, - NodeList: !0, - PaintRequestList: !1, - Plugin: !1, - PluginArray: !1, - SVGLengthList: !1, - SVGNumberList: !1, - SVGPathSegList: !1, - SVGPointList: !1, - SVGStringList: !1, - SVGTransformList: !1, - SourceBufferList: !1, - StyleSheetList: !0, - TextTrackCueList: !1, - TextTrackList: !1, - TouchList: !1, - }, - h = i(p), - v = 0; - v < h.length; - v++ - ) { - var y, - m = h[v], - g = p[m], - b = a[m], - _ = b && b.prototype; - if ( - _ && - (_[f] || s(_, f, d), _[l] || s(_, l, m), (u[m] = d), g) - ) - for (y in r) _[y] || o(_, y, r[y], !0); - } - }, - adf3: function (t, e, n) { - var r = n("560b"); - t.exports = function (t, e) { - var n = []; - return r(t, !1, n.push, n, e), n; - }; - }, - af7e: function (t, e, n) { - n("50e9"); - var r = n("a7d3").Object; - t.exports = function (t, e) { - return r.create(t, e); - }; - }, - b0b4: function (t, e, n) { - "use strict"; - n.d(e, "a", function () { - return a; - }); - var r = n("85f2"), - i = n.n(r); - function o(t, e) { - for (var n = 0; n < e.length; n++) { - var r = e[n]; - (r.enumerable = r.enumerable || !1), - (r.configurable = !0), - "value" in r && (r.writable = !0), - i()(t, r.key, r); - } - } - function a(t, e, n) { - return ( - e && o(t.prototype, e), - n && o(t, n), - i()(t, "prototype", { writable: !1 }), - t - ); - } - }, - b0bc: function (t, e) { - t.exports = function (t, e, n, r) { - if (!(t instanceof e) || (void 0 !== r && r in t)) - throw TypeError(n + ": incorrect invocation!"); - return t; - }; - }, - b0c5: function (t, e, n) { - "use strict"; - var r = n("520a"); - n("5ca1")( - { target: "RegExp", proto: !0, forced: r !== /./.exec }, - { exec: r } - ); - }, - b22a: function (t, e) { - t.exports = {}; - }, - b258: function (t, e, n) { - n("d256"), - n("12fd9"), - n("d127"), - n("d24f"), - (t.exports = n("a7d3").Symbol); - }, - b311: function (t, e, n) { - /*! - * clipboard.js v2.0.10 - * https://clipboardjs.com/ - * - * Licensed MIT © Zeno Rocha - */ - (function (e, n) { - t.exports = n(); - })(0, function () { - return (function () { - var t = { - 686: function (t, e, n) { - "use strict"; - n.d(e, { - default: function () { - return I; - }, - }); - var r = n(279), - i = n.n(r), - o = n(370), - a = n.n(o), - s = n(817), - u = n.n(s); - function c(t) { - try { - return document.execCommand(t); - } catch (e) { - return !1; - } - } - var f = function (t) { - var e = u()(t); - return c("cut"), e; - }, - l = f; - function d(t) { - var e = - "rtl" === - document.documentElement.getAttribute( - "dir" - ), - n = document.createElement("textarea"); - (n.style.fontSize = "12pt"), - (n.style.border = "0"), - (n.style.padding = "0"), - (n.style.margin = "0"), - (n.style.position = "absolute"), - (n.style[e ? "right" : "left"] = - "-9999px"); - var r = - window.pageYOffset || - document.documentElement.scrollTop; - return ( - (n.style.top = "".concat(r, "px")), - n.setAttribute("readonly", ""), - (n.value = t), - n - ); - } - var p = function (t) { - var e = - arguments.length > 1 && - void 0 !== arguments[1] - ? arguments[1] - : { - container: - document.body, - }, - n = ""; - if ("string" === typeof t) { - var r = d(t); - e.container.appendChild(r), - (n = u()(r)), - c("copy"), - r.remove(); - } else (n = u()(t)), c("copy"); - return n; - }, - h = p; - function v(t) { - return ( - (v = - "function" === typeof Symbol && - "symbol" === typeof Symbol.iterator - ? function (t) { - return typeof t; - } - : function (t) { - return t && - "function" === - typeof Symbol && - t.constructor === - Symbol && - t !== Symbol.prototype - ? "symbol" - : typeof t; - }), - v(t) - ); - } - var y = function () { - var t = - arguments.length > 0 && - void 0 !== arguments[0] - ? arguments[0] - : {}, - e = t.action, - n = void 0 === e ? "copy" : e, - r = t.container, - i = t.target, - o = t.text; - if ("copy" !== n && "cut" !== n) - throw new Error( - 'Invalid "action" value, use either "copy" or "cut"' - ); - if (void 0 !== i) { - if ( - !i || - "object" !== v(i) || - 1 !== i.nodeType - ) - throw new Error( - 'Invalid "target" value, use a valid Element' - ); - if ( - "copy" === n && - i.hasAttribute("disabled") - ) - throw new Error( - 'Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute' - ); - if ( - "cut" === n && - (i.hasAttribute("readonly") || - i.hasAttribute("disabled")) - ) - throw new Error( - 'Invalid "target" attribute. You can\'t cut text from elements with "readonly" or "disabled" attributes' - ); - } - return o - ? h(o, { container: r }) - : i - ? "cut" === n - ? l(i) - : h(i, { container: r }) - : void 0; - }, - m = y; - function g(t) { - return ( - (g = - "function" === typeof Symbol && - "symbol" === typeof Symbol.iterator - ? function (t) { - return typeof t; - } - : function (t) { - return t && - "function" === - typeof Symbol && - t.constructor === - Symbol && - t !== Symbol.prototype - ? "symbol" - : typeof t; - }), - g(t) - ); - } - function b(t, e) { - if (!(t instanceof e)) - throw new TypeError( - "Cannot call a class as a function" - ); - } - function _(t, e) { - for (var n = 0; n < e.length; n++) { - var r = e[n]; - (r.enumerable = r.enumerable || !1), - (r.configurable = !0), - "value" in r && (r.writable = !0), - Object.defineProperty(t, r.key, r); - } - } - function w(t, e, n) { - return ( - e && _(t.prototype, e), n && _(t, n), t - ); - } - function x(t, e) { - if ("function" !== typeof e && null !== e) - throw new TypeError( - "Super expression must either be null or a function" - ); - (t.prototype = Object.create( - e && e.prototype, - { - constructor: { - value: t, - writable: !0, - configurable: !0, - }, - } - )), - e && k(t, e); - } - function k(t, e) { - return ( - (k = - Object.setPrototypeOf || - function (t, e) { - return (t.__proto__ = e), t; - }), - k(t, e) - ); - } - function S(t) { - var e = T(); - return function () { - var n, - r = A(t); - if (e) { - var i = A(this).constructor; - n = Reflect.construct( - r, - arguments, - i - ); - } else n = r.apply(this, arguments); - return E(this, n); - }; - } - function E(t, e) { - return !e || - ("object" !== g(e) && - "function" !== typeof e) - ? O(t) - : e; - } - function O(t) { - if (void 0 === t) - throw new ReferenceError( - "this hasn't been initialised - super() hasn't been called" - ); - return t; - } - function T() { - if ( - "undefined" === typeof Reflect || - !Reflect.construct - ) - return !1; - if (Reflect.construct.sham) return !1; - if ("function" === typeof Proxy) return !0; - try { - return ( - Date.prototype.toString.call( - Reflect.construct( - Date, - [], - function () {} - ) - ), - !0 - ); - } catch (t) { - return !1; - } - } - function A(t) { - return ( - (A = Object.setPrototypeOf - ? Object.getPrototypeOf - : function (t) { - return ( - t.__proto__ || - Object.getPrototypeOf(t) - ); - }), - A(t) - ); - } - function C(t, e) { - var n = "data-clipboard-".concat(t); - if (e.hasAttribute(n)) - return e.getAttribute(n); - } - var j = (function (t) { - x(n, t); - var e = S(n); - function n(t, r) { - var i; - return ( - b(this, n), - (i = e.call(this)), - i.resolveOptions(r), - i.listenClick(t), - i - ); - } - return ( - w( - n, - [ - { - key: "resolveOptions", - value: function () { - var t = - arguments.length > - 0 && - void 0 !== - arguments[0] - ? arguments[0] - : {}; - (this.action = - "function" === - typeof t.action - ? t.action - : this - .defaultAction), - (this.target = - "function" === - typeof t.target - ? t.target - : this - .defaultTarget), - (this.text = - "function" === - typeof t.text - ? t.text - : this - .defaultText), - (this.container = - "object" === - g( - t.container - ) - ? t.container - : document.body); - }, - }, - { - key: "listenClick", - value: function (t) { - var e = this; - this.listener = a()( - t, - "click", - function (t) { - return e.onClick( - t - ); - } - ); - }, - }, - { - key: "onClick", - value: function (t) { - var e = - t.delegateTarget || - t.currentTarget, - n = - this.action( - e - ) || "copy", - r = m({ - action: n, - container: - this - .container, - target: this.target( - e - ), - text: this.text( - e - ), - }); - this.emit( - r - ? "success" - : "error", - { - action: n, - text: r, - trigger: e, - clearSelection: - function () { - e && - e.focus(), - document.activeElement.blur(), - window - .getSelection() - .removeAllRanges(); - }, - } - ); - }, - }, - { - key: "defaultAction", - value: function (t) { - return C( - "action", - t - ); - }, - }, - { - key: "defaultTarget", - value: function (t) { - var e = C( - "target", - t - ); - if (e) - return document.querySelector( - e - ); - }, - }, - { - key: "defaultText", - value: function (t) { - return C("text", t); - }, - }, - { - key: "destroy", - value: function () { - this.listener.destroy(); - }, - }, - ], - [ - { - key: "copy", - value: function (t) { - var e = - arguments.length > - 1 && - void 0 !== - arguments[1] - ? arguments[1] - : { - container: - document.body, - }; - return h(t, e); - }, - }, - { - key: "cut", - value: function (t) { - return l(t); - }, - }, - { - key: "isSupported", - value: function () { - var t = - arguments.length > - 0 && - void 0 !== - arguments[0] - ? arguments[0] - : [ - "copy", - "cut", - ], - e = - "string" === - typeof t - ? [t] - : t, - n = - !!document.queryCommandSupported; - return ( - e.forEach( - function ( - t - ) { - n = - n && - !!document.queryCommandSupported( - t - ); - } - ), - n - ); - }, - }, - ] - ), - n - ); - })(i()), - I = j; - }, - 828: function (t) { - var e = 9; - if ( - "undefined" !== typeof Element && - !Element.prototype.matches - ) { - var n = Element.prototype; - n.matches = - n.matchesSelector || - n.mozMatchesSelector || - n.msMatchesSelector || - n.oMatchesSelector || - n.webkitMatchesSelector; - } - function r(t, n) { - while (t && t.nodeType !== e) { - if ( - "function" === typeof t.matches && - t.matches(n) - ) - return t; - t = t.parentNode; - } - } - t.exports = r; - }, - 438: function (t, e, n) { - var r = n(828); - function i(t, e, n, r, i) { - var o = a.apply(this, arguments); - return ( - t.addEventListener(n, o, i), - { - destroy: function () { - t.removeEventListener(n, o, i); - }, - } - ); - } - function o(t, e, n, r, o) { - return "function" === - typeof t.addEventListener - ? i.apply(null, arguments) - : "function" === typeof n - ? i - .bind(null, document) - .apply(null, arguments) - : ("string" === typeof t && - (t = - document.querySelectorAll(t)), - Array.prototype.map.call( - t, - function (t) { - return i(t, e, n, r, o); - } - )); - } - function a(t, e, n, i) { - return function (n) { - (n.delegateTarget = r(n.target, e)), - n.delegateTarget && i.call(t, n); - }; - } - t.exports = o; - }, - 879: function (t, e) { - (e.node = function (t) { - return ( - void 0 !== t && - t instanceof HTMLElement && - 1 === t.nodeType - ); - }), - (e.nodeList = function (t) { - var n = - Object.prototype.toString.call(t); - return ( - void 0 !== t && - ("[object NodeList]" === n || - "[object HTMLCollection]" === - n) && - "length" in t && - (0 === t.length || e.node(t[0])) - ); - }), - (e.string = function (t) { - return ( - "string" === typeof t || - t instanceof String - ); - }), - (e.fn = function (t) { - var e = - Object.prototype.toString.call(t); - return "[object Function]" === e; - }); - }, - 370: function (t, e, n) { - var r = n(879), - i = n(438); - function o(t, e, n) { - if (!t && !e && !n) - throw new Error( - "Missing required arguments" - ); - if (!r.string(e)) - throw new TypeError( - "Second argument must be a String" - ); - if (!r.fn(n)) - throw new TypeError( - "Third argument must be a Function" - ); - if (r.node(t)) return a(t, e, n); - if (r.nodeList(t)) return s(t, e, n); - if (r.string(t)) return u(t, e, n); - throw new TypeError( - "First argument must be a String, HTMLElement, HTMLCollection, or NodeList" - ); - } - function a(t, e, n) { - return ( - t.addEventListener(e, n), - { - destroy: function () { - t.removeEventListener(e, n); - }, - } - ); - } - function s(t, e, n) { - return ( - Array.prototype.forEach.call( - t, - function (t) { - t.addEventListener(e, n); - } - ), - { - destroy: function () { - Array.prototype.forEach.call( - t, - function (t) { - t.removeEventListener( - e, - n - ); - } - ); - }, - } - ); - } - function u(t, e, n) { - return i(document.body, t, e, n); - } - t.exports = o; - }, - 817: function (t) { - function e(t) { - var e; - if ("SELECT" === t.nodeName) - t.focus(), (e = t.value); - else if ( - "INPUT" === t.nodeName || - "TEXTAREA" === t.nodeName - ) { - var n = t.hasAttribute("readonly"); - n || t.setAttribute("readonly", ""), - t.select(), - t.setSelectionRange( - 0, - t.value.length - ), - n || t.removeAttribute("readonly"), - (e = t.value); - } else { - t.hasAttribute("contenteditable") && - t.focus(); - var r = window.getSelection(), - i = document.createRange(); - i.selectNodeContents(t), - r.removeAllRanges(), - r.addRange(i), - (e = r.toString()); - } - return e; - } - t.exports = e; - }, - 279: function (t) { - function e() {} - (e.prototype = { - on: function (t, e, n) { - var r = this.e || (this.e = {}); - return ( - (r[t] || (r[t] = [])).push({ - fn: e, - ctx: n, - }), - this - ); - }, - once: function (t, e, n) { - var r = this; - function i() { - r.off(t, i), e.apply(n, arguments); - } - return (i._ = e), this.on(t, i, n); - }, - emit: function (t) { - var e = [].slice.call(arguments, 1), - n = ( - (this.e || (this.e = {}))[t] || - [] - ).slice(), - r = 0, - i = n.length; - for (r; r < i; r++) - n[r].fn.apply(n[r].ctx, e); - return this; - }, - off: function (t, e) { - var n = this.e || (this.e = {}), - r = n[t], - i = []; - if (r && e) - for ( - var o = 0, a = r.length; - o < a; - o++ - ) - r[o].fn !== e && - r[o].fn._ !== e && - i.push(r[o]); - return ( - i.length ? (n[t] = i) : delete n[t], - this - ); - }, - }), - (t.exports = e), - (t.exports.TinyEmitter = e); - }, - }, - e = {}; - function n(r) { - if (e[r]) return e[r].exports; - var i = (e[r] = { exports: {} }); - return t[r](i, i.exports, n), i.exports; - } - return ( - (function () { - n.n = function (t) { - var e = - t && t.__esModule - ? function () { - return t["default"]; - } - : function () { - return t; - }; - return n.d(e, { a: e }), e; - }; - })(), - (function () { - n.d = function (t, e) { - for (var r in e) - n.o(e, r) && - !n.o(t, r) && - Object.defineProperty(t, r, { - enumerable: !0, - get: e[r], - }); - }; - })(), - (function () { - n.o = function (t, e) { - return Object.prototype.hasOwnProperty.call( - t, - e - ); - }; - })(), - n(686) - ); - })().default; - }); - }, - b347: function (t, e, n) { - n("12fd9"), - n("93c4"), - n("b42c"), - n("edb9"), - n("8747"), - n("8a77"), - n("261e"), - (t.exports = n("a7d3").Map); - }, - b39a: function (t, e, n) { - var r = n("d3f4"); - t.exports = function (t, e) { - if (!r(t) || t._t !== e) - throw TypeError( - "Incompatible receiver, " + e + " required!" - ); - return t; - }; - }, - b3e7: function (t, e) { - t.exports = function () {}; - }, - b3ec: function (t, e, n) { - "use strict"; - var r = n("3adc"), - i = n("f845"); - t.exports = function (t, e, n) { - e in t ? r.f(t, e, i(0, n)) : (t[e] = n); - }; - }, - b42c: function (t, e, n) { - n("fa54"); - for ( - var r = n("da3c"), - i = n("8ce0"), - o = n("b22a"), - a = n("1b55")("toStringTag"), - s = - "CSSRuleList,CSSStyleDeclaration,CSSValueList,ClientRectList,DOMRectList,DOMStringList,DOMTokenList,DataTransferItemList,FileList,HTMLAllCollection,HTMLCollection,HTMLFormElement,HTMLSelectElement,MediaList,MimeTypeArray,NamedNodeMap,NodeList,PaintRequestList,Plugin,PluginArray,SVGLengthList,SVGNumberList,SVGPathSegList,SVGPointList,SVGStringList,SVGTransformList,SourceBufferList,StyleSheetList,TextTrackCueList,TextTrackList,TouchList".split( - "," - ), - u = 0; - u < s.length; - u++ - ) { - var c = s[u], - f = r[c], - l = f && f.prototype; - l && !l[a] && i(l, a, c), (o[c] = o.Array); - } - }, - b457: function (t, e) { - t.exports = !0; - }, - b5aa: function (t, e, n) { - var r = n("6e1f"); - t.exports = - Array.isArray || - function (t) { - return "Array" == r(t); - }; - }, - b5ae: function (t, e, n) { - "use strict"; - Object.defineProperty(e, "__esModule", { value: !0 }), - Object.defineProperty(e, "alpha", { - enumerable: !0, - get: function () { - return r.default; - }, - }), - Object.defineProperty(e, "alphaNum", { - enumerable: !0, - get: function () { - return i.default; - }, - }), - Object.defineProperty(e, "numeric", { - enumerable: !0, - get: function () { - return o.default; - }, - }), - Object.defineProperty(e, "between", { - enumerable: !0, - get: function () { - return a.default; - }, - }), - Object.defineProperty(e, "email", { - enumerable: !0, - get: function () { - return s.default; - }, - }), - Object.defineProperty(e, "ipAddress", { - enumerable: !0, - get: function () { - return u.default; - }, - }), - Object.defineProperty(e, "macAddress", { - enumerable: !0, - get: function () { - return c.default; - }, - }), - Object.defineProperty(e, "maxLength", { - enumerable: !0, - get: function () { - return f.default; - }, - }), - Object.defineProperty(e, "minLength", { - enumerable: !0, - get: function () { - return l.default; - }, - }), - Object.defineProperty(e, "required", { - enumerable: !0, - get: function () { - return d.default; - }, - }), - Object.defineProperty(e, "requiredIf", { - enumerable: !0, - get: function () { - return p.default; - }, - }), - Object.defineProperty(e, "requiredUnless", { - enumerable: !0, - get: function () { - return h.default; - }, - }), - Object.defineProperty(e, "sameAs", { - enumerable: !0, - get: function () { - return v.default; - }, - }), - Object.defineProperty(e, "url", { - enumerable: !0, - get: function () { - return y.default; - }, - }), - Object.defineProperty(e, "or", { - enumerable: !0, - get: function () { - return m.default; - }, - }), - Object.defineProperty(e, "and", { - enumerable: !0, - get: function () { - return g.default; - }, - }), - Object.defineProperty(e, "not", { - enumerable: !0, - get: function () { - return b.default; - }, - }), - Object.defineProperty(e, "minValue", { - enumerable: !0, - get: function () { - return _.default; - }, - }), - Object.defineProperty(e, "maxValue", { - enumerable: !0, - get: function () { - return w.default; - }, - }), - Object.defineProperty(e, "integer", { - enumerable: !0, - get: function () { - return x.default; - }, - }), - Object.defineProperty(e, "decimal", { - enumerable: !0, - get: function () { - return k.default; - }, - }), - (e.helpers = void 0); - var r = O(n("6235")), - i = O(n("3a54")), - o = O(n("45b8")), - a = O(n("ec11")), - s = O(n("5d75")), - u = O(n("c99d")), - c = O(n("91d3")), - f = O(n("2a12")), - l = O(n("5db3")), - d = O(n("d4f4")), - p = O(n("aa82")), - h = O(n("e652")), - v = O(n("b6cb")), - y = O(n("772d")), - m = O(n("d294")), - g = O(n("3360")), - b = O(n("6417")), - _ = O(n("eb66")), - w = O(n("46bc")), - x = O(n("1331e")), - k = O(n("c301")), - S = E(n("78ef")); - function E(t) { - if (t && t.__esModule) return t; - var e = {}; - if (null != t) - for (var n in t) - if (Object.prototype.hasOwnProperty.call(t, n)) { - var r = - Object.defineProperty && - Object.getOwnPropertyDescriptor - ? Object.getOwnPropertyDescriptor(t, n) - : {}; - r.get || r.set - ? Object.defineProperty(e, n, r) - : (e[n] = t[n]); - } - return (e.default = t), e; - } - function O(t) { - return t && t.__esModule ? t : { default: t }; - } - e.helpers = S; - }, - b6cb: function (t, e, n) { - "use strict"; - Object.defineProperty(e, "__esModule", { value: !0 }), - (e.default = void 0); - var r = n("78ef"), - i = function (t) { - return (0, r.withParams)( - { type: "sameAs", eq: t }, - function (e, n) { - return e === (0, r.ref)(t, this, n); - } - ); - }; - e.default = i; - }, - bc25: function (t, e, n) { - var r = n("f2fe"); - t.exports = function (t, e, n) { - if ((r(t), void 0 === e)) return t; - switch (n) { - case 1: - return function (n) { - return t.call(e, n); - }; - case 2: - return function (n, r) { - return t.call(e, n, r); - }; - case 3: - return function (n, r, i) { - return t.call(e, n, r, i); - }; - } - return function () { - return t.apply(e, arguments); - }; - }; - }, - bcaa: function (t, e, n) { - var r = n("cb7c"), - i = n("d3f4"), - o = n("a5b8"); - t.exports = function (t, e) { - if ((r(t), i(e) && e.constructor === t)) return e; - var n = o.f(t), - a = n.resolve; - return a(e), n.promise; - }; - }, - be13: function (t, e) { - t.exports = function (t) { - if (void 0 == t) throw TypeError("Can't call method on " + t); - return t; - }; - }, - bf06: function (t, e, n) { - var r = n("02d7"); - t.exports = function (t, e) { - return new (r(t))(e); - }; - }, - c0d8: function (t, e, n) { - var r = n("3adc").f, - i = n("43c8"), - o = n("1b55")("toStringTag"); - t.exports = function (t, e, n) { - t && - !i((t = n ? t : t.prototype), o) && - r(t, o, { configurable: !0, value: e }); - }; - }, - c165: function (t, e, n) { - var r = n("d13f"), - i = n("a7d3"), - o = n("d782"); - t.exports = function (t, e) { - var n = (i.Object || {})[t] || Object[t], - a = {}; - (a[t] = e(n)), - r( - r.S + - r.F * - o(function () { - n(1); - }), - "Object", - a - ); - }; - }, - c227: function (t, e, n) { - var r = n("b22a"), - i = n("1b55")("iterator"), - o = Array.prototype; - t.exports = function (t) { - return void 0 !== t && (r.Array === t || o[i] === t); - }; - }, - c26b: function (t, e, n) { - "use strict"; - var r = n("86cc").f, - i = n("2aeb"), - o = n("dcbc"), - a = n("9b43"), - s = n("f605"), - u = n("4a59"), - c = n("01f9"), - f = n("d53b"), - l = n("7a56"), - d = n("9e1e"), - p = n("67ab").fastKey, - h = n("b39a"), - v = d ? "_s" : "size", - y = function (t, e) { - var n, - r = p(e); - if ("F" !== r) return t._i[r]; - for (n = t._f; n; n = n.n) if (n.k == e) return n; - }; - t.exports = { - getConstructor: function (t, e, n, c) { - var f = t(function (t, r) { - s(t, f, e, "_i"), - (t._t = e), - (t._i = i(null)), - (t._f = void 0), - (t._l = void 0), - (t[v] = 0), - void 0 != r && u(r, n, t[c], t); - }); - return ( - o(f.prototype, { - clear: function () { - for ( - var t = h(this, e), n = t._i, r = t._f; - r; - r = r.n - ) - (r.r = !0), - r.p && (r.p = r.p.n = void 0), - delete n[r.i]; - (t._f = t._l = void 0), (t[v] = 0); - }, - delete: function (t) { - var n = h(this, e), - r = y(n, t); - if (r) { - var i = r.n, - o = r.p; - delete n._i[r.i], - (r.r = !0), - o && (o.n = i), - i && (i.p = o), - n._f == r && (n._f = i), - n._l == r && (n._l = o), - n[v]--; - } - return !!r; - }, - forEach: function (t) { - h(this, e); - var n, - r = a( - t, - arguments.length > 1 - ? arguments[1] - : void 0, - 3 - ); - while ((n = n ? n.n : this._f)) { - r(n.v, n.k, this); - while (n && n.r) n = n.p; - } - }, - has: function (t) { - return !!y(h(this, e), t); - }, - }), - d && - r(f.prototype, "size", { - get: function () { - return h(this, e)[v]; - }, - }), - f - ); - }, - def: function (t, e, n) { - var r, - i, - o = y(t, e); - return ( - o - ? (o.v = n) - : ((t._l = o = - { - i: (i = p(e, !0)), - k: e, - v: n, - p: (r = t._l), - n: void 0, - r: !1, - }), - t._f || (t._f = o), - r && (r.n = o), - t[v]++, - "F" !== i && (t._i[i] = o)), - t - ); - }, - getEntry: y, - setStrong: function (t, e, n) { - c( - t, - e, - function (t, n) { - (this._t = h(t, e)), - (this._k = n), - (this._l = void 0); - }, - function () { - var t = this, - e = t._k, - n = t._l; - while (n && n.r) n = n.p; - return t._t && (t._l = n = n ? n.n : t._t._f) - ? f( - 0, - "keys" == e - ? n.k - : "values" == e - ? n.v - : [n.k, n.v] - ) - : ((t._t = void 0), f(1)); - }, - n ? "entries" : "values", - !n, - !0 - ), - l(e); - }, - }; - }, - c301: function (t, e, n) { - "use strict"; - Object.defineProperty(e, "__esModule", { value: !0 }), - (e.default = void 0); - var r = n("78ef"), - i = (0, r.regex)("decimal", /^[-]?\d*(\.\d+)?$/); - e.default = i; - }, - c366: function (t, e, n) { - var r = n("6821"), - i = n("9def"), - o = n("77f1"); - t.exports = function (t) { - return function (e, n, a) { - var s, - u = r(e), - c = i(u.length), - f = o(a, c); - if (t && n != n) { - while (c > f) if (((s = u[f++]), s != s)) return !0; - } else - for (; c > f; f++) - if ((t || f in u) && u[f] === n) return t || f || 0; - return !t && -1; - }; - }; - }, - c437: function (t, e, n) { - var r, - i, - o = n("e1f4"), - a = n("2366"), - s = 0, - u = 0; - function c(t, e, n) { - var c = (e && n) || 0, - f = e || []; - t = t || {}; - var l = t.node || r, - d = void 0 !== t.clockseq ? t.clockseq : i; - if (null == l || null == d) { - var p = o(); - null == l && - (l = r = [1 | p[0], p[1], p[2], p[3], p[4], p[5]]), - null == d && (d = i = 16383 & ((p[6] << 8) | p[7])); - } - var h = void 0 !== t.msecs ? t.msecs : new Date().getTime(), - v = void 0 !== t.nsecs ? t.nsecs : u + 1, - y = h - s + (v - u) / 1e4; - if ( - (y < 0 && void 0 === t.clockseq && (d = (d + 1) & 16383), - (y < 0 || h > s) && void 0 === t.nsecs && (v = 0), - v >= 1e4) - ) - throw new Error( - "uuid.v1(): Can't create more than 10M uuids/sec" - ); - (s = h), (u = v), (i = d), (h += 122192928e5); - var m = (1e4 * (268435455 & h) + v) % 4294967296; - (f[c++] = (m >>> 24) & 255), - (f[c++] = (m >>> 16) & 255), - (f[c++] = (m >>> 8) & 255), - (f[c++] = 255 & m); - var g = ((h / 4294967296) * 1e4) & 268435455; - (f[c++] = (g >>> 8) & 255), - (f[c++] = 255 & g), - (f[c++] = ((g >>> 24) & 15) | 16), - (f[c++] = (g >>> 16) & 255), - (f[c++] = (d >>> 8) | 128), - (f[c++] = 255 & d); - for (var b = 0; b < 6; ++b) f[c + b] = l[b]; - return e || a(f); - } - t.exports = c; - }, - c64e: function (t, e, n) { - var r = n("e1f4"), - i = n("2366"); - function o(t, e, n) { - var o = (e && n) || 0; - "string" == typeof t && - ((e = "binary" === t ? new Array(16) : null), (t = null)), - (t = t || {}); - var a = t.random || (t.rng || r)(); - if (((a[6] = (15 & a[6]) | 64), (a[8] = (63 & a[8]) | 128), e)) - for (var s = 0; s < 16; ++s) e[o + s] = a[s]; - return e || i(a); - } - t.exports = o; - }, - c69a: function (t, e, n) { - t.exports = - !n("9e1e") && - !n("79e5")(function () { - return ( - 7 != - Object.defineProperty(n("230e")("div"), "a", { - get: function () { - return 7; - }, - }).a - ); - }); - }, - c8ba: function (t, e) { - var n; - n = (function () { - return this; - })(); - try { - n = n || new Function("return this")(); - } catch (r) { - "object" === typeof window && (n = window); - } - t.exports = n; - }, - c99d: function (t, e, n) { - "use strict"; - Object.defineProperty(e, "__esModule", { value: !0 }), - (e.default = void 0); - var r = n("78ef"), - i = (0, r.withParams)({ type: "ipAddress" }, function (t) { - if (!(0, r.req)(t)) return !0; - if ("string" !== typeof t) return !1; - var e = t.split("."); - return 4 === e.length && e.every(o); - }); - e.default = i; - var o = function (t) { - if (t.length > 3 || 0 === t.length) return !1; - if ("0" === t[0] && "0" !== t) return !1; - if (!t.match(/^\d+$/)) return !1; - var e = 0 | +t; - return e >= 0 && e <= 255; - }; - }, - ca5a: function (t, e) { - var n = 0, - r = Math.random(); - t.exports = function (t) { - return "Symbol(".concat( - void 0 === t ? "" : t, - ")_", - (++n + r).toString(36) - ); - }; - }, - cadf: function (t, e, n) { - "use strict"; - var r = n("9c6c"), - i = n("d53b"), - o = n("84f2"), - a = n("6821"); - (t.exports = n("01f9")( - Array, - "Array", - function (t, e) { - (this._t = a(t)), (this._i = 0), (this._k = e); - }, - function () { - var t = this._t, - e = this._k, - n = this._i++; - return !t || n >= t.length - ? ((this._t = void 0), i(1)) - : i( - 0, - "keys" == e ? n : "values" == e ? t[n] : [n, t[n]] - ); - }, - "values" - )), - (o.Arguments = o.Array), - r("keys"), - r("values"), - r("entries"); - }, - cb69: function (t, e, n) { - "use strict"; - (function (t) { - function n(t) { - return ( - (n = - "function" === typeof Symbol && - "symbol" === typeof Symbol.iterator - ? function (t) { - return typeof t; - } - : function (t) { - return t && - "function" === typeof Symbol && - t.constructor === Symbol && - t !== Symbol.prototype - ? "symbol" - : typeof t; - }), - n(t) - ); - } - Object.defineProperty(e, "__esModule", { value: !0 }), - (e.withParams = void 0); - var r = - "undefined" !== typeof window - ? window - : "undefined" !== typeof t - ? t - : {}, - i = function (t, e) { - return "object" === n(t) && void 0 !== e - ? e - : t(function () {}); - }, - o = r.vuelidate ? r.vuelidate.withParams : i; - e.withParams = o; - }.call(this, n("c8ba"))); - }, - cb7c: function (t, e, n) { - var r = n("d3f4"); - t.exports = function (t) { - if (!r(t)) throw TypeError(t + " is not an object!"); - return t; - }; - }, - cd1c: function (t, e, n) { - var r = n("e853"); - t.exports = function (t, e) { - return new (r(t))(e); - }; - }, - ce10: function (t, e, n) { - var r = n("69a8"), - i = n("6821"), - o = n("c366")(!1), - a = n("613b")("IE_PROTO"); - t.exports = function (t, e) { - var n, - s = i(t), - u = 0, - c = []; - for (n in s) n != a && r(s, n) && c.push(n); - while (e.length > u) - r(s, (n = e[u++])) && (~o(c, n) || c.push(n)); - return c; - }; - }, - d127: function (t, e, n) { - n("0a0a")("asyncIterator"); - }, - d13f: function (t, e, n) { - var r = n("da3c"), - i = n("a7d3"), - o = n("bc25"), - a = n("8ce0"), - s = n("43c8"), - u = "prototype", - c = function (t, e, n) { - var f, - l, - d, - p = t & c.F, - h = t & c.G, - v = t & c.S, - y = t & c.P, - m = t & c.B, - g = t & c.W, - b = h ? i : i[e] || (i[e] = {}), - _ = b[u], - w = h ? r : v ? r[e] : (r[e] || {})[u]; - for (f in (h && (n = e), n)) - (l = !p && w && void 0 !== w[f]), - (l && s(b, f)) || - ((d = l ? w[f] : n[f]), - (b[f] = - h && "function" != typeof w[f] - ? n[f] - : m && l - ? o(d, r) - : g && w[f] == d - ? (function (t) { - var e = function (e, n, r) { - if (this instanceof t) { - switch ( - arguments.length - ) { - case 0: - return new t(); - case 1: - return new t(e); - case 2: - return new t( - e, - n - ); - } - return new t(e, n, r); - } - return t.apply( - this, - arguments - ); - }; - return (e[u] = t[u]), e; - })(d) - : y && "function" == typeof d - ? o(Function.call, d) - : d), - y && - (((b.virtual || (b.virtual = {}))[f] = d), - t & c.R && _ && !_[f] && a(_, f, d))); - }; - (c.F = 1), - (c.G = 2), - (c.S = 4), - (c.P = 8), - (c.B = 16), - (c.W = 32), - (c.U = 64), - (c.R = 128), - (t.exports = c); - }, - d225: function (t, e, n) { - "use strict"; - function r(t, e) { - if (!(t instanceof e)) - throw new TypeError("Cannot call a class as a function"); - } - n.d(e, "a", function () { - return r; - }); - }, - d24f: function (t, e, n) { - n("0a0a")("observable"); - }, - d256: function (t, e, n) { - "use strict"; - var r = n("da3c"), - i = n("43c8"), - o = n("7d95"), - a = n("d13f"), - s = n("2312"), - u = n("6277").KEY, - c = n("d782"), - f = n("7772"), - l = n("c0d8"), - d = n("7b00"), - p = n("1b55"), - h = n("fda1"), - v = n("0a0a"), - y = n("d2d6"), - m = n("b5aa"), - g = n("0f89"), - b = n("6f8a"), - _ = n("0185"), - w = n("6a9b"), - x = n("2ea1"), - k = n("f845"), - S = n("7108"), - E = n("565d"), - O = n("626e"), - T = n("31c2"), - A = n("3adc"), - C = n("7633"), - j = O.f, - I = A.f, - M = E.f, - L = r.Symbol, - P = r.JSON, - N = P && P.stringify, - R = "prototype", - $ = p("_hidden"), - D = p("toPrimitive"), - F = {}.propertyIsEnumerable, - z = f("symbol-registry"), - U = f("symbols"), - B = f("op-symbols"), - V = Object[R], - H = "function" == typeof L && !!T.f, - q = r.QObject, - W = !q || !q[R] || !q[R].findChild, - G = - o && - c(function () { - return ( - 7 != - S( - I({}, "a", { - get: function () { - return I(this, "a", { value: 7 }).a; - }, - }) - ).a - ); - }) - ? function (t, e, n) { - var r = j(V, e); - r && delete V[e], - I(t, e, n), - r && t !== V && I(V, e, r); - } - : I, - Z = function (t) { - var e = (U[t] = S(L[R])); - return (e._k = t), e; - }, - J = - H && "symbol" == typeof L.iterator - ? function (t) { - return "symbol" == typeof t; - } - : function (t) { - return t instanceof L; - }, - K = function (t, e, n) { - return ( - t === V && K(B, e, n), - g(t), - (e = x(e, !0)), - g(n), - i(U, e) - ? (n.enumerable - ? (i(t, $) && t[$][e] && (t[$][e] = !1), - (n = S(n, { enumerable: k(0, !1) }))) - : (i(t, $) || I(t, $, k(1, {})), - (t[$][e] = !0)), - G(t, e, n)) - : I(t, e, n) - ); - }, - X = function (t, e) { - g(t); - var n, - r = y((e = w(e))), - i = 0, - o = r.length; - while (o > i) K(t, (n = r[i++]), e[n]); - return t; - }, - Y = function (t, e) { - return void 0 === e ? S(t) : X(S(t), e); - }, - Q = function (t) { - var e = F.call(this, (t = x(t, !0))); - return ( - !(this === V && i(U, t) && !i(B, t)) && - (!( - e || - !i(this, t) || - !i(U, t) || - (i(this, $) && this[$][t]) - ) || - e) - ); - }, - tt = function (t, e) { - if ( - ((t = w(t)), - (e = x(e, !0)), - t !== V || !i(U, e) || i(B, e)) - ) { - var n = j(t, e); - return ( - !n || - !i(U, e) || - (i(t, $) && t[$][e]) || - (n.enumerable = !0), - n - ); - } - }, - et = function (t) { - var e, - n = M(w(t)), - r = [], - o = 0; - while (n.length > o) - i(U, (e = n[o++])) || e == $ || e == u || r.push(e); - return r; - }, - nt = function (t) { - var e, - n = t === V, - r = M(n ? B : w(t)), - o = [], - a = 0; - while (r.length > a) - !i(U, (e = r[a++])) || (n && !i(V, e)) || o.push(U[e]); - return o; - }; - H || - ((L = function () { - if (this instanceof L) - throw TypeError("Symbol is not a constructor!"); - var t = d(arguments.length > 0 ? arguments[0] : void 0), - e = function (n) { - this === V && e.call(B, n), - i(this, $) && - i(this[$], t) && - (this[$][t] = !1), - G(this, t, k(1, n)); - }; - return ( - o && W && G(V, t, { configurable: !0, set: e }), Z(t) - ); - }), - s(L[R], "toString", function () { - return this._k; - }), - (O.f = tt), - (A.f = K), - (n("d876").f = E.f = et), - (n("d74e").f = Q), - (T.f = nt), - o && !n("b457") && s(V, "propertyIsEnumerable", Q, !0), - (h.f = function (t) { - return Z(p(t)); - })), - a(a.G + a.W + a.F * !H, { Symbol: L }); - for ( - var rt = - "hasInstance,isConcatSpreadable,iterator,match,replace,search,species,split,toPrimitive,toStringTag,unscopables".split( - "," - ), - it = 0; - rt.length > it; - - ) - p(rt[it++]); - for (var ot = C(p.store), at = 0; ot.length > at; ) v(ot[at++]); - a(a.S + a.F * !H, "Symbol", { - for: function (t) { - return i(z, (t += "")) ? z[t] : (z[t] = L(t)); - }, - keyFor: function (t) { - if (!J(t)) throw TypeError(t + " is not a symbol!"); - for (var e in z) if (z[e] === t) return e; - }, - useSetter: function () { - W = !0; - }, - useSimple: function () { - W = !1; - }, - }), - a(a.S + a.F * !H, "Object", { - create: Y, - defineProperty: K, - defineProperties: X, - getOwnPropertyDescriptor: tt, - getOwnPropertyNames: et, - getOwnPropertySymbols: nt, - }); - var st = c(function () { - T.f(1); - }); - a(a.S + a.F * st, "Object", { - getOwnPropertySymbols: function (t) { - return T.f(_(t)); - }, - }), - P && - a( - a.S + - a.F * - (!H || - c(function () { - var t = L(); - return ( - "[null]" != N([t]) || - "{}" != N({ a: t }) || - "{}" != N(Object(t)) - ); - })), - "JSON", - { - stringify: function (t) { - var e, - n, - r = [t], - i = 1; - while (arguments.length > i) - r.push(arguments[i++]); - if ( - ((n = e = r[1]), - (b(e) || void 0 !== t) && !J(t)) - ) - return ( - m(e) || - (e = function (t, e) { - if ( - ("function" == typeof n && - (e = n.call( - this, - t, - e - )), - !J(e)) - ) - return e; - }), - (r[1] = e), - N.apply(P, r) - ); - }, - } - ), - L[R][D] || n("8ce0")(L[R], D, L[R].valueOf), - l(L, "Symbol"), - l(Math, "Math", !0), - l(r.JSON, "JSON", !0); - }, - d294: function (t, e, n) { - "use strict"; - Object.defineProperty(e, "__esModule", { value: !0 }), - (e.default = void 0); - var r = n("78ef"), - i = function () { - for ( - var t = arguments.length, e = new Array(t), n = 0; - n < t; - n++ - ) - e[n] = arguments[n]; - return (0, r.withParams)({ type: "or" }, function () { - for ( - var t = this, - n = arguments.length, - r = new Array(n), - i = 0; - i < n; - i++ - ) - r[i] = arguments[i]; - return ( - e.length > 0 && - e.reduce(function (e, n) { - return e || n.apply(t, r); - }, !1) - ); - }); - }; - e.default = i; - }, - d2c8: function (t, e, n) { - var r = n("aae3"), - i = n("be13"); - t.exports = function (t, e, n) { - if (r(e)) - throw TypeError("String#" + n + " doesn't accept regex!"); - return String(i(t)); - }; - }, - d2d6: function (t, e, n) { - var r = n("7633"), - i = n("31c2"), - o = n("d74e"); - t.exports = function (t) { - var e = r(t), - n = i.f; - if (n) { - var a, - s = n(t), - u = o.f, - c = 0; - while (s.length > c) u.call(t, (a = s[c++])) && e.push(a); - } - return e; - }; - }, - d3f4: function (t, e) { - t.exports = function (t) { - return "object" === typeof t - ? null !== t - : "function" === typeof t; - }; - }, - d4f4: function (t, e, n) { - "use strict"; - Object.defineProperty(e, "__esModule", { value: !0 }), - (e.default = void 0); - var r = n("78ef"), - i = (0, r.withParams)({ type: "required" }, r.req); - e.default = i; - }, - d53b: function (t, e) { - t.exports = function (t, e) { - return { value: e, done: !!t }; - }; - }, - d604: function (t, e, n) { - n("1938"), (t.exports = n("a7d3").Array.isArray); - }, - d74e: function (t, e) { - e.f = {}.propertyIsEnumerable; - }, - d782: function (t, e) { - t.exports = function (t) { - try { - return !!t(); - } catch (e) { - return !0; - } - }; - }, - d876: function (t, e, n) { - var r = n("2695"), - i = n("0029").concat("length", "prototype"); - e.f = - Object.getOwnPropertyNames || - function (t) { - return r(t, i); - }; - }, - d8e8: function (t, e) { - t.exports = function (t) { - if ("function" != typeof t) - throw TypeError(t + " is not a function!"); - return t; - }; - }, - da3c: function (t, e) { - var n = (t.exports = - "undefined" != typeof window && window.Math == Math - ? window - : "undefined" != typeof self && self.Math == Math - ? self - : Function("return this")()); - "number" == typeof __g && (__g = n); - }, - dcbc: function (t, e, n) { - var r = n("2aba"); - t.exports = function (t, e, n) { - for (var i in e) r(t, i, e[i], n); - return t; - }; - }, - e0b8: function (t, e, n) { - "use strict"; - var r = n("7726"), - i = n("5ca1"), - o = n("2aba"), - a = n("dcbc"), - s = n("67ab"), - u = n("4a59"), - c = n("f605"), - f = n("d3f4"), - l = n("79e5"), - d = n("5cc5"), - p = n("7f20"), - h = n("5dbc"); - t.exports = function (t, e, n, v, y, m) { - var g = r[t], - b = g, - _ = y ? "set" : "add", - w = b && b.prototype, - x = {}, - k = function (t) { - var e = w[t]; - o( - w, - t, - "delete" == t || "has" == t - ? function (t) { - return ( - !(m && !f(t)) && - e.call(this, 0 === t ? 0 : t) - ); - } - : "get" == t - ? function (t) { - return m && !f(t) - ? void 0 - : e.call(this, 0 === t ? 0 : t); - } - : "add" == t - ? function (t) { - return ( - e.call(this, 0 === t ? 0 : t), this - ); - } - : function (t, n) { - return ( - e.call(this, 0 === t ? 0 : t, n), this - ); - } - ); - }; - if ( - "function" == typeof b && - (m || - (w.forEach && - !l(function () { - new b().entries().next(); - }))) - ) { - var S = new b(), - E = S[_](m ? {} : -0, 1) != S, - O = l(function () { - S.has(1); - }), - T = d(function (t) { - new b(t); - }), - A = - !m && - l(function () { - var t = new b(), - e = 5; - while (e--) t[_](e, e); - return !t.has(-0); - }); - T || - ((b = e(function (e, n) { - c(e, b, t); - var r = h(new g(), e, b); - return void 0 != n && u(n, y, r[_], r), r; - })), - (b.prototype = w), - (w.constructor = b)), - (O || A) && (k("delete"), k("has"), y && k("get")), - (A || E) && k(_), - m && w.clear && delete w.clear; - } else - (b = v.getConstructor(e, t, y, _)), - a(b.prototype, n), - (s.NEED = !0); - return ( - p(b, t), - (x[t] = b), - i(i.G + i.W + i.F * (b != g), x), - m || v.setStrong(b, t, y), - b - ); - }; - }, - e11e: function (t, e) { - t.exports = - "constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split( - "," - ); - }, - e1f4: function (t, e) { - var n = - ("undefined" != typeof crypto && - crypto.getRandomValues && - crypto.getRandomValues.bind(crypto)) || - ("undefined" != typeof msCrypto && - "function" == typeof window.msCrypto.getRandomValues && - msCrypto.getRandomValues.bind(msCrypto)); - if (n) { - var r = new Uint8Array(16); - t.exports = function () { - return n(r), r; - }; - } else { - var i = new Array(16); - t.exports = function () { - for (var t, e = 0; e < 16; e++) - 0 === (3 & e) && (t = 4294967296 * Math.random()), - (i[e] = (t >>> ((3 & e) << 3)) & 255); - return i; - }; - } - }, - e341: function (t, e, n) { - var r = n("d13f"); - r(r.S + r.F * !n("7d95"), "Object", { - defineProperty: n("3adc").f, - }); - }, - e4a9: function (t, e, n) { - "use strict"; - var r = n("b457"), - i = n("d13f"), - o = n("2312"), - a = n("8ce0"), - s = n("b22a"), - u = n("5ce7"), - c = n("c0d8"), - f = n("ff0c"), - l = n("1b55")("iterator"), - d = !([].keys && "next" in [].keys()), - p = "@@iterator", - h = "keys", - v = "values", - y = function () { - return this; - }; - t.exports = function (t, e, n, m, g, b, _) { - u(n, e, m); - var w, - x, - k, - S = function (t) { - if (!d && t in A) return A[t]; - switch (t) { - case h: - return function () { - return new n(this, t); - }; - case v: - return function () { - return new n(this, t); - }; - } - return function () { - return new n(this, t); - }; - }, - E = e + " Iterator", - O = g == v, - T = !1, - A = t.prototype, - C = A[l] || A[p] || (g && A[g]), - j = C || S(g), - I = g ? (O ? S("entries") : j) : void 0, - M = ("Array" == e && A.entries) || C; - if ( - (M && - ((k = f(M.call(new t()))), - k !== Object.prototype && - k.next && - (c(k, E, !0), - r || "function" == typeof k[l] || a(k, l, y))), - O && - C && - C.name !== v && - ((T = !0), - (j = function () { - return C.call(this); - })), - (r && !_) || (!d && !T && A[l]) || a(A, l, j), - (s[e] = j), - (s[E] = y), - g) - ) - if ( - ((w = { - values: O ? j : S(v), - keys: b ? j : S(h), - entries: I, - }), - _) - ) - for (x in w) x in A || o(A, x, w[x]); - else i(i.P + i.F * (d || T), e, w); - return w; - }; - }, - e5fa: function (t, e) { - t.exports = function (t) { - if (void 0 == t) throw TypeError("Can't call method on " + t); - return t; - }; - }, - e652: function (t, e, n) { - "use strict"; - Object.defineProperty(e, "__esModule", { value: !0 }), - (e.default = void 0); - var r = n("78ef"), - i = function (t) { - return (0, r.withParams)( - { type: "requiredUnless", prop: t }, - function (e, n) { - return !!(0, r.ref)(t, this, n) || (0, r.req)(e); - } - ); - }; - e.default = i; - }, - e853: function (t, e, n) { - var r = n("d3f4"), - i = n("1169"), - o = n("2b4c")("species"); - t.exports = function (t) { - var e; - return ( - i(t) && - ((e = t.constructor), - "function" != typeof e || - (e !== Array && !i(e.prototype)) || - (e = void 0), - r(e) && ((e = e[o]), null === e && (e = void 0))), - void 0 === e ? Array : e - ); - }; - }, - eb46: function (t, e, n) { - var r = n("bc25"), - i = n("8bab"), - o = n("0185"), - a = n("a5ab"), - s = n("bf06"); - t.exports = function (t, e) { - var n = 1 == t, - u = 2 == t, - c = 3 == t, - f = 4 == t, - l = 6 == t, - d = 5 == t || l, - p = e || s; - return function (e, s, h) { - for ( - var v, - y, - m = o(e), - g = i(m), - b = r(s, h, 3), - _ = a(g.length), - w = 0, - x = n ? p(e, _) : u ? p(e, 0) : void 0; - _ > w; - w++ - ) - if ((d || w in g) && ((v = g[w]), (y = b(v, w, m)), t)) - if (n) x[w] = y; - else if (y) - switch (t) { - case 3: - return !0; - case 5: - return v; - case 6: - return w; - case 2: - x.push(v); - } - else if (f) return !1; - return l ? -1 : c || f ? f : x; - }; - }; - }, - eb66: function (t, e, n) { - "use strict"; - Object.defineProperty(e, "__esModule", { value: !0 }), - (e.default = void 0); - var r = n("78ef"), - i = function (t) { - return (0, r.withParams)( - { type: "minValue", min: t }, - function (e) { - return ( - !(0, r.req)(e) || - ((!/\s/.test(e) || e instanceof Date) && - +e >= +t) - ); - } - ); - }; - e.default = i; - }, - ebd6: function (t, e, n) { - var r = n("cb7c"), - i = n("d8e8"), - o = n("2b4c")("species"); - t.exports = function (t, e) { - var n, - a = r(t).constructor; - return void 0 === a || void 0 == (n = r(a)[o]) ? e : i(n); - }; - }, - ec11: function (t, e, n) { - "use strict"; - Object.defineProperty(e, "__esModule", { value: !0 }), - (e.default = void 0); - var r = n("78ef"), - i = function (t, e) { - return (0, r.withParams)( - { type: "between", min: t, max: e }, - function (n) { - return ( - !(0, r.req)(n) || - ((!/\s/.test(n) || n instanceof Date) && - +t <= +n && - +e >= +n) - ); - } - ); - }; - e.default = i; - }, - ec5b: function (t, e, n) { - n("e341"); - var r = n("a7d3").Object; - t.exports = function (t, e, n) { - return r.defineProperty(t, e, n); - }; - }, - edb9: function (t, e, n) { - "use strict"; - var r = n("0780"), - i = n("1fca"), - o = "Map"; - t.exports = n("f2f6")( - o, - function (t) { - return function () { - return t( - this, - arguments.length > 0 ? arguments[0] : void 0 - ); - }; - }, - { - get: function (t) { - var e = r.getEntry(i(this, o), t); - return e && e.v; - }, - set: function (t, e) { - return r.def(i(this, o), 0 === t ? 0 : t, e); - }, - }, - r, - !0 - ); - }, - f0c1: function (t, e, n) { - "use strict"; - var r = n("d8e8"), - i = n("d3f4"), - o = n("31f4"), - a = [].slice, - s = {}, - u = function (t, e, n) { - if (!(e in s)) { - for (var r = [], i = 0; i < e; i++) - r[i] = "a[" + i + "]"; - s[e] = Function( - "F,a", - "return new F(" + r.join(",") + ")" - ); - } - return s[e](t, n); - }; - t.exports = - Function.bind || - function (t) { - var e = r(this), - n = a.call(arguments, 1), - s = function () { - var r = n.concat(a.call(arguments)); - return this instanceof s - ? u(e, r.length, r) - : o(e, r, t); - }; - return i(e.prototype) && (s.prototype = e.prototype), s; - }; - }, - f159: function (t, e, n) { - var r = n("7d8a"), - i = n("1b55")("iterator"), - o = n("b22a"); - t.exports = n("a7d3").getIteratorMethod = function (t) { - if (void 0 != t) return t[i] || t["@@iterator"] || o[r(t)]; - }; - }, - f28b: function (t, e, n) { - "use strict"; - var r = n("2d7d"), - i = n.n(r), - o = n("4aa6"), - a = n.n(o), - s = n("6bb5"), - u = n("54b6"); - function c(t) { - return ( - -1 !== Function.toString.call(t).indexOf("[native code]") - ); - } - var f = n("a5b2"), - l = n.n(f); - function d() { - if ("undefined" === typeof Reflect || !l.a) return !1; - if (l.a.sham) return !1; - if ("function" === typeof Proxy) return !0; - try { - return ( - Boolean.prototype.valueOf.call( - l()(Boolean, [], function () {}) - ), - !0 - ); - } catch (t) { - return !1; - } - } - function p(t, e, n) { - return ( - (p = d() - ? l.a - : function (t, e, n) { - var r = [null]; - r.push.apply(r, e); - var i = Function.bind.apply(t, r), - o = new i(); - return n && Object(u["a"])(o, n.prototype), o; - }), - p.apply(null, arguments) - ); - } - function h(t) { - var e = "function" === typeof i.a ? new i.a() : void 0; - return ( - (h = function (t) { - if (null === t || !c(t)) return t; - if ("function" !== typeof t) - throw new TypeError( - "Super expression must either be null or a function" - ); - if ("undefined" !== typeof e) { - if (e.has(t)) return e.get(t); - e.set(t, n); - } - function n() { - return p( - t, - arguments, - Object(s["a"])(this).constructor - ); - } - return ( - (n.prototype = a()(t.prototype, { - constructor: { - value: n, - enumerable: !1, - writable: !0, - configurable: !0, - }, - })), - Object(u["a"])(n, t) - ); - }), - h(t) - ); - } - n.d(e, "a", function () { - return h; - }); - }, - f28c: function (t, e) { - var n, - r, - i = (t.exports = {}); - function o() { - throw new Error("setTimeout has not been defined"); - } - function a() { - throw new Error("clearTimeout has not been defined"); - } - function s(t) { - if (n === setTimeout) return setTimeout(t, 0); - if ((n === o || !n) && setTimeout) - return (n = setTimeout), setTimeout(t, 0); - try { - return n(t, 0); - } catch (e) { - try { - return n.call(null, t, 0); - } catch (e) { - return n.call(this, t, 0); - } - } - } - function u(t) { - if (r === clearTimeout) return clearTimeout(t); - if ((r === a || !r) && clearTimeout) - return (r = clearTimeout), clearTimeout(t); - try { - return r(t); - } catch (e) { - try { - return r.call(null, t); - } catch (e) { - return r.call(this, t); - } - } - } - (function () { - try { - n = "function" === typeof setTimeout ? setTimeout : o; - } catch (t) { - n = o; - } - try { - r = "function" === typeof clearTimeout ? clearTimeout : a; - } catch (t) { - r = a; - } - })(); - var c, - f = [], - l = !1, - d = -1; - function p() { - l && - c && - ((l = !1), - c.length ? (f = c.concat(f)) : (d = -1), - f.length && h()); - } - function h() { - if (!l) { - var t = s(p); - l = !0; - var e = f.length; - while (e) { - (c = f), (f = []); - while (++d < e) c && c[d].run(); - (d = -1), (e = f.length); - } - (c = null), (l = !1), u(t); - } - } - function v(t, e) { - (this.fun = t), (this.array = e); - } - function y() {} - (i.nextTick = function (t) { - var e = new Array(arguments.length - 1); - if (arguments.length > 1) - for (var n = 1; n < arguments.length; n++) - e[n - 1] = arguments[n]; - f.push(new v(t, e)), 1 !== f.length || l || s(h); - }), - (v.prototype.run = function () { - this.fun.apply(null, this.array); - }), - (i.title = "browser"), - (i.browser = !0), - (i.env = {}), - (i.argv = []), - (i.version = ""), - (i.versions = {}), - (i.on = y), - (i.addListener = y), - (i.once = y), - (i.off = y), - (i.removeListener = y), - (i.removeAllListeners = y), - (i.emit = y), - (i.prependListener = y), - (i.prependOnceListener = y), - (i.listeners = function (t) { - return []; - }), - (i.binding = function (t) { - throw new Error("process.binding is not supported"); - }), - (i.cwd = function () { - return "/"; - }), - (i.chdir = function (t) { - throw new Error("process.chdir is not supported"); - }), - (i.umask = function () { - return 0; - }); - }, - f2f6: function (t, e, n) { - "use strict"; - var r = n("da3c"), - i = n("d13f"), - o = n("6277"), - a = n("d782"), - s = n("8ce0"), - u = n("3904"), - c = n("560b"), - f = n("b0bc"), - l = n("6f8a"), - d = n("c0d8"), - p = n("3adc").f, - h = n("eb46")(0), - v = n("7d95"); - t.exports = function (t, e, n, y, m, g) { - var b = r[t], - _ = b, - w = m ? "set" : "add", - x = _ && _.prototype, - k = {}; - return ( - v && - "function" == typeof _ && - (g || - (x.forEach && - !a(function () { - new _().entries().next(); - }))) - ? ((_ = e(function (e, n) { - f(e, _, t, "_c"), - (e._c = new b()), - void 0 != n && c(n, m, e[w], e); - })), - h( - "add,clear,delete,forEach,get,has,set,keys,values,entries,toJSON".split( - "," - ), - function (t) { - var e = "add" == t || "set" == t; - !(t in x) || - (g && "clear" == t) || - s(_.prototype, t, function (n, r) { - if ((f(this, _, t), !e && g && !l(n))) - return "get" == t && void 0; - var i = this._c[t]( - 0 === n ? 0 : n, - r - ); - return e ? this : i; - }); - } - ), - g || - p(_.prototype, "size", { - get: function () { - return this._c.size; - }, - })) - : ((_ = y.getConstructor(e, t, m, w)), - u(_.prototype, n), - (o.NEED = !0)), - d(_, t), - (k[t] = _), - i(i.G + i.W + i.F, k), - g || y.setStrong(_, t, m), - _ - ); - }; - }, - f2fe: function (t, e) { - t.exports = function (t) { - if ("function" != typeof t) - throw TypeError(t + " is not a function!"); - return t; - }; - }, - f400: function (t, e, n) { - "use strict"; - var r = n("c26b"), - i = n("b39a"), - o = "Map"; - t.exports = n("e0b8")( - o, - function (t) { - return function () { - return t( - this, - arguments.length > 0 ? arguments[0] : void 0 - ); - }; - }, - { - get: function (t) { - var e = r.getEntry(i(this, o), t); - return e && e.v; - }, - set: function (t, e) { - return r.def(i(this, o), 0 === t ? 0 : t, e); - }, - }, - r, - !0 - ); - }, - f4bb: function (t, e, n) { - n("2498"), (t.exports = n("a7d3").Reflect.construct); - }, - f559: function (t, e, n) { - "use strict"; - var r = n("5ca1"), - i = n("9def"), - o = n("d2c8"), - a = "startsWith", - s = ""[a]; - r(r.P + r.F * n("5147")(a), "String", { - startsWith: function (t) { - var e = o(this, t, a), - n = i( - Math.min( - arguments.length > 1 ? arguments[1] : void 0, - e.length - ) - ), - r = String(t); - return s ? s.call(e, r, n) : e.slice(n, n + r.length) === r; - }, - }); - }, - f568: function (t, e, n) { - var r = n("3adc"), - i = n("0f89"), - o = n("7633"); - t.exports = n("7d95") - ? Object.defineProperties - : function (t, e) { - i(t); - var n, - a = o(e), - s = a.length, - u = 0; - while (s > u) r.f(t, (n = a[u++]), e[n]); - return t; - }; - }, - f605: function (t, e) { - t.exports = function (t, e, n, r) { - if (!(t instanceof e) || (void 0 !== r && r in t)) - throw TypeError(n + ": incorrect invocation!"); - return t; - }; - }, - f751: function (t, e, n) { - var r = n("5ca1"); - r(r.S + r.F, "Object", { assign: n("7333") }); - }, - f845: function (t, e) { - t.exports = function (t, e) { - return { - enumerable: !(1 & t), - configurable: !(2 & t), - writable: !(4 & t), - value: e, - }; - }; - }, - fa54: function (t, e, n) { - "use strict"; - var r = n("b3e7"), - i = n("245b"), - o = n("b22a"), - a = n("6a9b"); - (t.exports = n("e4a9")( - Array, - "Array", - function (t, e) { - (this._t = a(t)), (this._i = 0), (this._k = e); - }, - function () { - var t = this._t, - e = this._k, - n = this._i++; - return !t || n >= t.length - ? ((this._t = void 0), i(1)) - : i( - 0, - "keys" == e ? n : "values" == e ? t[n] : [n, t[n]] - ); - }, - "values" - )), - (o.Arguments = o.Array), - r("keys"), - r("values"), - r("entries"); - }, - fa5b: function (t, e, n) { - t.exports = n("5537")( - "native-function-to-string", - Function.toString - ); - }, - fab2: function (t, e, n) { - var r = n("7726").document; - t.exports = r && r.documentElement; - }, - fbf4: function (t, e, n) { - "use strict"; - function r(t) { - return null === t || void 0 === t; - } - function i(t) { - return null !== t && void 0 !== t; - } - function o(t, e) { - return e.tag === t.tag && e.key === t.key; - } - function a(t) { - var e = t.tag; - t.vm = new e({ data: t.args }); - } - function s(t) { - for (var e = Object.keys(t.args), n = 0; n < e.length; n++) - e.forEach(function (e) { - t.vm[e] = t.args[e]; - }); - } - function u(t, e, n) { - var r, - o, - a = {}; - for (r = e; r <= n; ++r) (o = t[r].key), i(o) && (a[o] = r); - return a; - } - function c(t, e) { - var n, - s, - c, - p = 0, - h = 0, - v = t.length - 1, - y = t[0], - m = t[v], - g = e.length - 1, - b = e[0], - _ = e[g]; - while (p <= v && h <= g) - r(y) - ? (y = t[++p]) - : r(m) - ? (m = t[--v]) - : o(y, b) - ? (d(y, b), (y = t[++p]), (b = e[++h])) - : o(m, _) - ? (d(m, _), (m = t[--v]), (_ = e[--g])) - : o(y, _) - ? (d(y, _), (y = t[++p]), (_ = e[--g])) - : o(m, b) - ? (d(m, b), (m = t[--v]), (b = e[++h])) - : (r(n) && (n = u(t, p, v)), - (s = i(b.key) ? n[b.key] : null), - r(s) - ? (a(b), (b = e[++h])) - : ((c = t[s]), - o(c, b) - ? (d(c, b), (t[s] = void 0), (b = e[++h])) - : (a(b), (b = e[++h])))); - p > v ? f(e, h, g) : h > g && l(t, p, v); - } - function f(t, e, n) { - for (; e <= n; ++e) a(t[e]); - } - function l(t, e, n) { - for (; e <= n; ++e) { - var r = t[e]; - i(r) && (r.vm.$destroy(), (r.vm = null)); - } - } - function d(t, e) { - t !== e && ((e.vm = t.vm), s(e)); - } - function p(t, e) { - i(t) && i(e) - ? t !== e && c(t, e) - : i(e) - ? f(e, 0, e.length - 1) - : i(t) && l(t, 0, t.length - 1); - } - function h(t, e, n) { - return { tag: t, key: e, args: n }; - } - Object.defineProperty(e, "__esModule", { value: !0 }), - (e.patchChildren = p), - (e.h = h); - }, - fda1: function (t, e, n) { - e.f = n("1b55"); - }, - ff0c: function (t, e, n) { - var r = n("43c8"), - i = n("0185"), - o = n("5d8f")("IE_PROTO"), - a = Object.prototype; - t.exports = - Object.getPrototypeOf || - function (t) { - return ( - (t = i(t)), - r(t, o) - ? t[o] - : "function" == typeof t.constructor && - t instanceof t.constructor - ? t.constructor.prototype - : t instanceof Object - ? a - : null - ); - }; - }, - }, -]); diff --git a/queries/js/Sonos/connect.js b/queries/js/Sonos/connect.js deleted file mode 100644 index a8e78a57..00000000 --- a/queries/js/Sonos/connect.js +++ /dev/null @@ -1,5 +0,0 @@ -function openYouTubeInNewTab() { - var url = "https://www.youtube.com/watch?v=dQw4w9WgXcQ"; - var win = window.open(url, '_blank'); - win.focus(); -} \ No newline at end of file diff --git a/queries/js/Sonos/groups.js b/queries/js/Sonos/groups.js deleted file mode 100644 index c4db0989..00000000 --- a/queries/js/Sonos/groups.js +++ /dev/null @@ -1,18 +0,0 @@ -function getGroups() { - let myHeaders = new Headers(); - myHeaders.append("Authorization", `Bearer ${sonosBearerToken}`); - - let requestOptions = { - method: "GET", - headers: myHeaders, - redirect: "follow", - }; - - fetch( - `https://api.ws.sonos.com/control/api/v1/households/${sonosHouseholdID}/groups`, - requestOptions - ) - .then((response) => response.text()) - .then((result) => console.log(result)) - .catch((error) => console.log("error", error)); -} diff --git a/queries/js/Sonos/household.js b/queries/js/Sonos/household.js deleted file mode 100644 index 454cfa9e..00000000 --- a/queries/js/Sonos/household.js +++ /dev/null @@ -1,15 +0,0 @@ -function getHousehold() { - let myHeaders = new Headers(); - myHeaders.append("Authorization", `Bearer ${sonosBearerToken}`); - - let requestOptions = { - method: "GET", - headers: myHeaders, - redirect: "follow", - }; - - fetch("https://api.ws.sonos.com/control/api/v2/households", requestOptions) - .then((response) => response.text()) - .then((result) => console.log(result)) - .catch((error) => console.log("error", error)); -} diff --git a/queries/js/Sonos/playback.js b/queries/js/Sonos/playback.js deleted file mode 100644 index 4f46f250..00000000 --- a/queries/js/Sonos/playback.js +++ /dev/null @@ -1,48 +0,0 @@ -function togglePlayPause() { - let myHeaders = new Headers(); - myHeaders.append("Content-Type", "application/json"); - myHeaders.append("Authorization", `Bearer ${sonosBearerToken}`); - myHeaders.append("Allow-Control-Allow-Origin", "https://api.ws.sonos.com"); - - let requestOptions = { - method: "POST", - headers: myHeaders, - redirect: "follow", - }; - - fetch( - `https://api.ws.sonos.com/control/api/v1/groups/${sonosGroupID}/playback/togglePlayPause`, - requestOptions - ) - .then((response) => response.text()) - .then((result) => console.log(result)) - .catch((error) => console.log("error", error)); -} - -function setVolume() { - let volume = document.getElementById("volume_slider").value; - let myHeaders = new Headers(); - myHeaders.append("Content-Type", "application/json"); - myHeaders.append("Authorization", `Bearer ${sonosBearerToken}`); - - let raw = JSON.stringify({ - volume: volume, - }); - - let requestOptions = { - method: "POST", - headers: myHeaders, - body: raw, - redirect: "follow", - }; - - fetch( - `https://api.ws.sonos.com/control/api/v1/groups/${sonosGroupID}/groupVolume`, - requestOptions - ) - .then((response) => response.text()) - .then((result) => console.log(result)) - .catch((error) => console.log("error", error)); -} - -// togglePlayPause(); diff --git a/queries/js/Sonos/sonos.html b/queries/js/Sonos/sonos.html deleted file mode 100644 index f9cb3625..00000000 --- a/queries/js/Sonos/sonos.html +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - Sonos Test - - - - - - -
    -

    Testing

    - -
    - -
    - - -

    - -
    -

    Volume

    - -
    - -
    - -
    -
    - -
    - - - -
    - - - diff --git a/queries/js/Sonos/sonos.js b/queries/js/Sonos/sonos.js deleted file mode 100644 index 9275c5ae..00000000 --- a/queries/js/Sonos/sonos.js +++ /dev/null @@ -1,3 +0,0 @@ -var sonosBearerToken = "fhFVRX5CX0Zo8pRI7s366IbRRUQ0"; -var sonosGroupID = "RINCON_542A1B599FF201400:2388243335"; -var sonosHouseholdID = "Sonos_2qmmZYj1IfZpziI3yTZT2AdYkP.LzZPKytb_zgm6t3fVIv7"; diff --git a/queries/wip/home.py b/queries/wip/home.py deleted file mode 100644 index c9a8ee38..00000000 --- a/queries/wip/home.py +++ /dev/null @@ -1,470 +0,0 @@ -""" - - Greynir: Natural language processing for Icelandic - - Smarthome control query response module - - Copyright (C) 2022 Miðeind ehf. - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see http://www.gnu.org/licenses/. - - - This module handles queries and commands related to controlling - smarthome devices. - -""" - -from typing import Dict, Mapping, Optional, cast -from typing_extensions import TypedDict - -import logging -import re -import json -import flask - -from query import QueryStateDict -from queries import gen_answer, read_jsfile -from tree import Result - - -# Type declarations - - -class SmartLights(TypedDict): - selected_light: str - philips_hue: Dict[str, str] - - -class DeviceData(TypedDict): - smartlights: SmartLights - - -# This module wants to handle parse trees for queries -HANDLE_TREE = True - -# The context-free grammar for the queries recognized by this plug-in module -GRAMMAR = """ - -Query → - QSmartDeviceQuery '?'? - -QSmartDeviceQuery → - QConnectQuery - | QLightOnQuery - | QLightOffQuery - | QLightDimQuery - | QLightColorQuery - | QHubInfoQuery - | QHomeLightSaturationQuery - -# 'Connect smart device grammar' -QConnectQuery → - "tengdu" "snjalltæki" | "tengdu" QLightQuery - -# 'Lightswitch grammar' -QLightOnQuery → - "kveiktu" "á"? QLightQuery QHomeInOrOnQuery QLightOnPhenomenon - | "kveiktu" QHomeInOrOnQuery QHomeDeviceQuery_þgf - -QLightOffQuery → - "slökktu" "á"? QLightQuery QHomeInOrOnQuery QLightOffPhenomenon - | "slökktu" QHomeInOrOnQuery QHomeDeviceQuery_þgf - -QLightOnPhenomenon → Nl - -# $tag(keep) QLightOnPhenomenon - -QLightOffPhenomenon → Nl - -# 'Dimmer switch grammar' -QLightDimQuery → - "settu" QLightQuery QHomeWhereDeviceQuery "í" QLightPercentage - | "settu" QHomeDeviceQuery_þf "í" QLightPercentage - | "settu" "birtuna" QHomeWhereDeviceQuery "í" QLightPercentage - -QLightPercentage → - töl | to | tala - -QHomeDeviceQuery/fall → Fyrirbæri/fall/kyn - -QHomeWhereDeviceQuery → FsLiður - -# 'Color change query' -QLightColorQuery → - "settu"? QColorName_nf "ljós" "í" QLightOnPhenomenon - -QColorName/fall → - Lo/fall/tala/kyn - -$tag(keep) QColorName/fall - -# Set the saturation of a light or group -QHomeLightSaturationQuery → - "settu" QHomeLightSaturation QHomeWhereDeviceQuery "í" QLightPercentage - -QHomeLightSaturation → - 'mettun' | 'mettunina' - -QLightPercentage → - töl | to | tala - -QHomeDeviceQuery/fall → Fyrirbæri/fall/kyn - -QHomeWhereDeviceQuery → FsLiður - -# 'information about hub grammar' -QHubInfoQuery → - "hvaða" QLightOrGroup "eru" "tengdir" - | "hvað" "er" "tengt" - -QLightOrGroup → - "ljós" | "hóp" | "hópa" - -# 'Helper functions' -QLightQuery → - "ljós" | "ljósið" | "ljósin" | "ljósunum" - -QLightNamePhenomenon → Nl - -QHomeInOrOnQuery → - "í" | "á" - -""" - -# Catches active query and assigns the correct variables -# to be used when performing an action on lights - - -def QConnectQuery(node, params, result): - result.qtype = "ConnectSmartDevice" - - -def QLightOnPhenomenon(node, params, result): - result.subject = node.contained_text() - - -def QLightOnQuery(node, params, result): - result.qtype = "LightOn" - - -def QLightOffQuery(node, params, result): - result.qtype = "LightOff" - - -def QLightOffPhenomenon(node, params, result): - result.subject = node.contained_text() - - -def QLightNamePhenomenon(node, params, result): - result.subject = node.contained_text() - - -def QLightDimQuery(node, params, result): - result.qtype = "LightDim" - - -def QLightPercentage(node, params, result): - d = result.find_descendant(t_base="tala") - if d: - add_num(terminal_num(d), result) - else: - add_num(result._nominative, result) - - -def QLightColorQuery(node, params, result): - result.qtype = "LightColor" - - -def QColorName(node, params, result): - result.color = node.contained_text() - - -def QHubInfoQuery(node, params, result): - result.qtype = "HubInfo" - - -def QHomeWhereDeviceQuery(node, params, result): - result.subject = node.contained_text().split(" ")[1] - - -def QHomeDeviceQuery(node, params, result): - result.subject = node.contained_text() - - -def QHomeLightSaturationQuery(node, params, result): - result.qtype = "LightSaturation" - - -# Fix common stofn errors when stofn from a company or entity is used instead -# of the correct stofn -_FIX_MAP: Mapping[str, str] = {"Skrifstofan": "skrifstofa", "Húsið": "hús"} - -_NUMBER_WORDS: Mapping[str, float] = { - "núll": 0, - "einn": 1, - "einu": 1, - "tveir": 2, - "tveim": 2, - "tvisvar sinnum": 2, - "þrír": 3, - "þrisvar sinnum": 3, - "fjórir": 4, - "fjórum sinnum": 4, - "fimm": 5, - "sex": 6, - "sjö": 7, - "átta": 8, - "níu": 9, - "tíu": 10, - "ellefu": 11, - "tólf": 12, - "þrettán": 13, - "fjórtán": 14, - "fimmtán": 15, - "sextán": 16, - "sautján": 17, - "átján": 18, - "nítján": 19, - "tuttugu": 20, - "þrjátíu": 30, - "fjörutíu": 40, - "fimmtíu": 50, - "sextíu": 60, - "sjötíu": 70, - "áttatíu": 80, - "níutíu": 90, - "hundrað": 100, - "þúsund": 1000, - "milljón": 1e6, - "milljarður": 1e9, -} - -# Convert color name into hue -_COLOR_NAME_TO_CIE: Mapping[str, float] = { - "gulur": 60 * 65535 / 360, - "grænn": 120 * 65535 / 360, - "ljósblár": 180 * 65535 / 360, - "blár": 240 * 65535 / 360, - "bleikur": 300 * 65535 / 360, - "rauður": 360 * 65535 / 360, -} - - -def parse_num(num_str: str) -> Optional[float]: - """Parse Icelandic number string to float or int""" - num = None - try: - # Handle numbers w. Icelandic decimal places ("17,2") - if re.search(r"^\d+,\d+", num_str): - num = float(num_str.replace(",", ".")) - # Handle digits ("17") - else: - num = float(num_str) - except ValueError: - # Handle number words ("sautján") - if num_str in _NUMBER_WORDS: - num = _NUMBER_WORDS[num_str] - # Ordinal number strings ("17.") - elif re.search(r"^\d+\.$", num_str): - num = int(num_str[:-1]) - else: - num = 0 - except Exception as e: - logging.warning("Unexpected exception: {0}".format(e)) - raise - return num - - -def add_num(num, result): - """Add a number to accumulated number args""" - if "numbers" not in result: - result.numbers = [] - if isinstance(num, str): - result.numbers.append(parse_num(num)) - else: - result.numbers.append(num) - - -def terminal_num(t): - """Extract numerical value from terminal token's auxiliary info, - which is attached as a json-encoded array""" - if t and t._node.aux: - aux = json.loads(t._node.aux) - if isinstance(aux, int) or isinstance(aux, float): - return aux - return aux[0] - - -def sentence(state: QueryStateDict, result: Result) -> None: - """Called when sentence processing is complete""" - q = state["query"] - - if "qtype" not in result: - q.set_error("E_QUERY_NOT_UNDERSTOOD") - return - - # Connect smartdevice action - if result.qtype == "ConnectSmartDevice": - - answer = "Skal gert" - host = flask.request.host - - js = read_jsfile("connectHub.js") - - # Function from the javascript file needs to be called with - # relevant variables - js += "connectHub('{0}','{1}')".format(q.client_id, host) - - q.set_command(js) - q.set_answer(*gen_answer(answer)) - return - - # TODO hardcoded while only one device type is supported - smartdevice_type = "smartlights" - - # Fetch relevant data from the device_data table to perform an action on the lights - device_data = cast(Optional[DeviceData], q.client_data(smartdevice_type)) - - selected_light: Optional[str] = None - hue_credentials: Optional[Dict[str, str]] = None - - if device_data is not None and smartdevice_type in device_data: - dev = device_data[smartdevice_type] - assert dev is not None - selected_light = dev.get("selected_light") - hue_credentials = dev.get("philips_hue") - - if not device_data or not hue_credentials: - answer = "Snjalltæki hafa ekki verið sett upp" - q.set_answer(*gen_answer(answer)) - return - - # Light on or off action - if ( - result.qtype == "LightOn" or result.qtype == "LightOff" - ) and selected_light == "philips_hue": - - onOrOff = "true" if result.qtype == "LightOn" else "false" - - stofn: Optional[str] = None - - for token in q.token_list or []: - if token.txt == result.subject and token.has_meanings: - stofn = token.meanings[0].stofn - assert stofn is not None - stofn = _FIX_MAP.get(stofn, stofn) - - js = read_jsfile("lightService.js") - - js += "main('{0}','{1}','{2}', {3});".format( - hue_credentials["ipAddress"], hue_credentials["username"], stofn, onOrOff - ) - - answer = "{0} {1}".format(stofn, onOrOff) - - q.set_answer(*gen_answer(answer)) - q.set_command(js) - return - - # Alter light dimmer action - if result.qtype == "LightDim" and selected_light == "philips_hue": - - number = result.numbers[0] - - stofn = "" - - for token in q.token_list or []: - if token.txt == result.subject: - stofn = token.meanings[0].stofn - stofn = _FIX_MAP.get(stofn, stofn) - - if not stofn: - return - - js = read_jsfile("lightService.js") - js += "main('{0}','{1}','{2}', true, {3});".format( - hue_credentials["ipAddress"], hue_credentials["username"], stofn, number - ) - - answer = stofn - q.set_answer(*gen_answer(answer)) - q.set_command(js) - return - - # Alter light color action - if result.qtype == "LightColor" and selected_light == "philips_hue": - stofn_name = None - stofn_color = None - - for token in q.token_list or []: - if token.txt == result.subject and token.has_meanings: - stofn_name = token.meanings[0].stofn - stofn_name = _FIX_MAP.get(stofn_name) or stofn_name - if token.txt == result.color: - for word_variation in token.meanings: - if ( - word_variation.ordfl == "lo" - and word_variation.stofn in _COLOR_NAME_TO_CIE - ): - stofn_color = word_variation.stofn - break - - if not stofn_color: - return - - js = read_jsfile("lightService.js") - js += "main('{0}','{1}','{2}', true, null, {3});".format( - hue_credentials["ipAddress"], - hue_credentials["username"], - stofn_name, - _COLOR_NAME_TO_CIE[stofn_color.lower()], - ) - - answer = "{0} {1}".format(stofn_color, stofn_name) - q.set_answer(*gen_answer(answer)) - q.set_command(js) - return - - # Connected lights info action - if result.qtype == "HubInfo" and "selected_light" == "philips_hue": - answer = "Skal gert" - - js = read_jsfile("lightInfo.js") - - q.set_answer(*gen_answer(answer)) - q.set_command(js) - return - - # Alter saturation action - if result.qtype == "LightSaturation" and selected_light == "philips_hue": - number = result.numbers[0] - stofn = None - - for token in q.token_list or []: - if token.txt == result.subject and token.has_meanings: - stofn = token.meanings[0].stofn - stofn = _FIX_MAP.get(stofn, stofn) - - js = read_jsfile("lightService.js") - js += "main('{0}','{1}','{2}', undefined, undefined, undefined, {3});".format( - hue_credentials["ipAddress"], hue_credentials["username"], stofn, number - ) - - answer = "{0} {1}".format(stofn, number) - - q.set_answer(*gen_answer(answer)) - q.set_command(js) - return - - # No command was applicable: give up - q.set_error("E_QUERY_NOT_UNDERSTOOD") From ef62bd982a24733c8e68749db6b4a1e815030ee4 Mon Sep 17 00:00:00 2001 From: Logi E Date: Wed, 14 Sep 2022 11:56:59 +0000 Subject: [PATCH 354/371] Moving reducer into query.py, working on sonos --- .gitignore | 1 - queries/__init__.py | 4 +- queries/dialogue.py | 1 + queries/{disabled/.exists => dialogues/.keep} | 0 queries/disabled/.keep | 0 queries/iot_connect.py | 3 +- queries/js/Philips_Hue/set_lights.js | 2 +- queries/pizza.py | 1 - queries/sonos.py | 1 - queries/wip/.keep | 0 query.py | 183 ++++++++++++++++-- routes/api.py | 10 +- routes/main.py | 4 - templates/sonos-connection.html | 2 +- 14 files changed, 181 insertions(+), 31 deletions(-) rename queries/{disabled/.exists => dialogues/.keep} (100%) create mode 100644 queries/disabled/.keep create mode 100644 queries/wip/.keep diff --git a/.gitignore b/.gitignore index 265fc00e..c4424052 100644 --- a/.gitignore +++ b/.gitignore @@ -45,7 +45,6 @@ nnmain.sh # Various resource files resources/*.txt -resources/*.txt:Zone.Identifier !resources/formers.txt !resources/last.txt !resources/ordalisti.sorted.txt diff --git a/queries/__init__.py b/queries/__init__.py index 46112e4a..1917391f 100755 --- a/queries/__init__.py +++ b/queries/__init__.py @@ -483,9 +483,9 @@ def post_to_json_api( def put_to_json_api( url: str, json_data: Optional[Any] = None, headers: Optional[Dict[str, str]] = None ) -> Union[None, List[Any], Dict[str, Any]]: - """Send a POST request to the URL, expecting a JSON response which is + """Send a PUT request to the URL, expecting a JSON response which is parsed and returned as a Python data structure.""" - print("put to json api") + # Send request try: r = requests.put(url, data=json_data, headers=headers) diff --git a/queries/dialogue.py b/queries/dialogue.py index 9faf5618..fcdf74c3 100644 --- a/queries/dialogue.py +++ b/queries/dialogue.py @@ -191,6 +191,7 @@ def _initialize_resource_graph(self) -> None: self._initial_resource = resource self._resource_graph[resource] = {"children": [], "parents": []} for resource in self._resources.values(): + # TODO: If OrResource, do something else for req in resource.requires: self._resource_graph[self._resources[req]]["parents"].append(resource) self._resource_graph[resource]["children"].append(self._resources[req]) diff --git a/queries/disabled/.exists b/queries/dialogues/.keep similarity index 100% rename from queries/disabled/.exists rename to queries/dialogues/.keep diff --git a/queries/disabled/.keep b/queries/disabled/.keep new file mode 100644 index 00000000..e69de29b diff --git a/queries/iot_connect.py b/queries/iot_connect.py index 30b4f7dd..cd01e8fd 100644 --- a/queries/iot_connect.py +++ b/queries/iot_connect.py @@ -134,7 +134,8 @@ def sentence(state: QueryStateDict, result: Result) -> None: q.set_qtype(result.qtype) host = str(flask.request.host) - client_id = str(q.client_id) + client_id = q.client_id + assert client_id is not None if result.qtype == "connect_lights": js = read_jsfile("Philips_Hue/hub.js") diff --git a/queries/js/Philips_Hue/set_lights.js b/queries/js/Philips_Hue/set_lights.js index ef5eeb21..04d42253 100644 --- a/queries/js/Philips_Hue/set_lights.js +++ b/queries/js/Philips_Hue/set_lights.js @@ -22,7 +22,7 @@ function getTargetObject(target, allLights, allGroups) { let groupsResult = philipsFuzzySearch(target, allGroups); if (lightsResult != null && groupsResult != null) { - // Found a match for a light group and a light+ + // Found a match for a light group and a light targetObject = lightsResult.score < groupsResult.score // Select the light with the highest score ? { diff --git a/queries/pizza.py b/queries/pizza.py index 21cacaa8..7b30db6b 100644 --- a/queries/pizza.py +++ b/queries/pizza.py @@ -20,7 +20,6 @@ This query module handles dialogue related to ordering pizza. """ -from lzma import is_check_supported from typing import Any, Dict, List, Optional, Set, cast import logging import random diff --git a/queries/sonos.py b/queries/sonos.py index 6d1c1263..c246ff80 100644 --- a/queries/sonos.py +++ b/queries/sonos.py @@ -185,7 +185,6 @@ def _create_token(self) -> Union[None, List[Any], Dict[str, Any]]: url = f"https://api.sonos.com/login/v3/oauth/access?grant_type=authorization_code&code={self._code}&redirect_uri=http://{host}/connect_sonos.api" headers = { "Authorization": f"Basic {self._encoded_credentials}", - # "Cookie": "JSESSIONID=F710019AF0A3B7126A8702577C883B5F; AWSELB=69BFEFC914A689BF6DC8E4652748D7B501ED60290D5EA56F2E543ABD7CF357A5F65186AEBCFB059E28075D83A700FD504C030A53CC28683B515BE3DCA3CC587AFAF606E171; AWSELBCORS=69BFEFC914A689BF6DC8E4652748D7B501ED60290D5EA56F2E543ABD7CF357A5F65186AEBCFB059E28075D83A700FD504C030A53CC28683B515BE3DCA3CC587AFAF606E171", } response = post_to_json_api(url, headers=headers) diff --git a/queries/wip/.keep b/queries/wip/.keep new file mode 100644 index 00000000..e69de29b diff --git a/query.py b/query.py index b54758c9..cb6fb92e 100755 --- a/query.py +++ b/query.py @@ -73,9 +73,18 @@ ) from tokenizer import BIN_Tuple from reynir.binparser import BIN_Grammar, BIN_Token -from reynir.reducer import Reducer +from reynir.reducer import ( + NULL_SC, + ChildDict, + KeyTuple, + ParseForestReducer, + Reducer, + ResultDict, + ScoreDict, +) from reynir.bindb import GreynirBin -from reynir.grammar import GrammarError +from reynir.fastparser import Node as SPPF_Node +from reynir.grammar import GrammarError, Production from islenska.bindb import BinFilterFunc from tree import Tree, TreeStateDict, Node, Result @@ -223,6 +232,159 @@ def grammar_generator() -> Iterator[str]: raise GrammarError("Unable to open or read grammar file", fname, 0) +class QueryReductionScope: + + """Class to accumulate information about a nonterminal and its + child productions during reduction""" + + def __init__(self, reducer: "QueryParseForestReducer", node: SPPF_Node) -> None: + self.reducer = reducer + # Child tree scores + self.sc: ChildDict = defaultdict(lambda: {"sc": 0}) + # Verb/preposition matching stuff + self.pushed_prep_bonus = False + + def start_family(self, ix: int, prod: Production) -> None: + """Start the processing of a production (numbered ix) of a nonterminal""" + # Initialize the score of this family of children, so that productions + # with higher priorities (more negative prio values) get a starting bonus + self.sc[ix]["sc"] = -10 * prod.priority + + def add_child(self, ix: int, rd: ResultDict) -> None: + """Add a child node's score to the parent family's score, + where the parent family has index ix (0..n)""" + d = self.sc[ix] + assert "sc" in d + d["sc"] += rd.get("sc", 0) + # Carry information about contained verbs ("so") up the tree + for key in ("so", "sl"): + if key in rd: + if key in d: + d[key].extend(rd[key]) # type: ignore + else: + d[key] = rd[key][:] # type: ignore + + def process(self, node: SPPF_Node) -> ResultDict: + """After accumulating scores for all possible productions + of this nonterminal (families of children), find the + highest scoring one and reduce the tree to that child only""" + + csc = self.sc + if not csc: + # Empty node + return NULL_SC + + nt = node.nonterminal if node.is_completed else None + + if len(csc) == 1: + # Not ambiguous: only one result, do a shortcut + # Will raise an exception if not exactly one value + [sc] = csc.values() + else: + # Eliminate all families except the best scoring one + # Sort in decreasing order by score, using the family index + # as a tie-breaker for determinism + s = sorted(csc.items(), key=lambda x: (x[1]["sc"], -x[0]), reverse=True) + # This is the best scoring family + # (and the one with the lowest index + # if there are many with the same score) + ix, sc = s[0] + # If the node nonterminal is marked as "no_reduce", + # we leave the child families in place. This feature + # is used in query processing. + if nt is None or not nt.no_reduce: + # And now for the key action of the reducer: + # Eliminate all other families + node.reduce_to(ix) + + if nt is not None: + # We will be adjusting the result: make sure we do so on + # a separate dict copy (we don't want to clobber the child's dict) + # Get score adjustment for this nonterminal, if any + # (This is the $score(+/-N) pragma from Greynir.grammar) + sc["sc"] += self.reducer._score_adj.get(nt, 0) + + return sc + + +class QueryParseForestReducer(ParseForestReducer): + def __init__(self, grammar: BIN_Grammar, scores: ScoreDict, query: "Query"): + super().__init__(grammar, scores) + self._q = query + + def go(self, root_node: SPPF_Node) -> ResultDict: + """Perform the reduction""" + # TODO: + # - Less greedy Nl, prefer optional nts + # - Banned/reduced score for nts + + # Memoization/caching dict, keyed by node and memoization key + visited: Dict[KeyTuple, ResultDict] = dict() + # Current memoization key + current_key = 0 + # Next memoization key to use + next_key = 0 + + def calc_score(w: SPPF_Node) -> ResultDict: + """Navigate from (w, current_key) where w is a node and current_key + is an integer navigation key, carefully controlling the memoization + of already visited nodes. + """ + nonlocal current_key, next_key + # Has this (node, current_key) tuple been memoized? + v = visited.get((w, current_key)) + if v is not None: + # Yes: return the previously calculated result + return v + # We have not seen this (node, current_key) combination before: + # reduce it, calculate its score and memoize it + if w._token is not None: + # Return the score of this terminal option + v = self.visit_token(w) + elif w.is_span and w._families: + # We have a nonempty nonterminal node with one or more families + # of children, i.e. multiple possible derivations: + # Init container for family results + scope = QueryReductionScope(self, w) + # Go through each family and calculate its score + for family_ix, (prod, children) in enumerate(w._families): + scope.start_family(family_ix, prod) + for ch in children: + if ch is not None: + scope.add_child(family_ix, calc_score(ch)) + # Return a dict describing the winning family of children + # (derivation) including an "sc" field for its score. + # !!! TODO: We might be pruning the parse forest too + # !!! early here - there could be a different verb scope + # !!! above this node that would cause a different child + # !!! to be culled. However a test case to demonstrate this + # !!! has yet to be identified/created. + v = scope.process(w) + # The winning family is now the only remaining family + # of children of this node; the others have been culled. + else: + v = NULL_SC + # Memoize the result for this (node, current_key) combination + visited[(w, current_key)] = v + w.score = v["sc"] + return v + + # Start the scoring and reduction process at the root + if root_node is None: + return NULL_SC + return calc_score(root_node) + + +class QueryReducer(Reducer): + def __init__(self, grammar: BIN_Grammar, query: "Query"): + self._grammar = grammar + self._q = query + + def _reduce(self, w: SPPF_Node, scores: ScoreDict) -> ResultDict: + """Reduce a forest with a root in w based on subtree scores""" + return QueryParseForestReducer(self._grammar, scores, self._q).go(w) + + class QueryParser(Fast_Parser): """A subclass of Fast_Parser, specialized to parse queries""" @@ -531,16 +693,13 @@ def init_class(cls) -> None: # with the nonterminal 'QueryRoot' as the grammar root cls._parser = QueryParser(grammar_additions) - @staticmethod - def _parse( - toklist: Iterable[Tok], banned_nonterminals: Optional[Set[str]] = None - ) -> Tuple[ResponseDict, Dict[int, str]]: + def _parse(self, toklist: Iterable[Tok]) -> Tuple[ResponseDict, Dict[int, str]]: """Parse a token list as a query""" bp = Query._parser assert bp is not None num_sent = 0 num_parsed_sent = 0 - rdc = Reducer(bp.grammar, banned_nonterminals=banned_nonterminals) + rdc = QueryReducer(bp.grammar, self) trees: Dict[int, str] = dict() sent: List[Tok] = [] @@ -565,8 +724,10 @@ def _parse( if num > 1: # Reduce the resulting forest forest = rdc.go(forest) - except ParseError as e: - print("ParseError: ", e) + if forest is None: + # In case the entire forest was pruned + num = 0 + except ParseError: forest = None num = 0 if num > 0: @@ -638,9 +799,7 @@ def parse(self, result: ResponseDict) -> bool: if f is not None: banned_nonterminals.update(f(self)) try: - parse_result, trees = Query._parse( - toklist, banned_nonterminals=banned_nonterminals - ) + parse_result, trees = self._parse(toklist) except ParseError: self.set_error("E_PARSE_ERROR") return False diff --git a/routes/api.py b/routes/api.py index cfea1112..eb81baaf 100755 --- a/routes/api.py +++ b/routes/api.py @@ -892,21 +892,18 @@ def get_supported_iot_connections(version: int = 1) -> Response: args = request.args client_id: str = args.get("client_id") host: str = args.get("host") - print("Host: ", host) + basepath, _ = os.path.split(os.path.realpath(__file__)) fpath = os.path.join(basepath, "../resources/iot_supported.toml") - print("fpath: ", fpath) + with open(fpath, mode="r") as file: f = file.read() # Read TOML file containing a list of resources for the dialogue obj: IotSupportedTOMLStructure = tomllib.loads(f) # type: ignore - print("TOML: ", obj) - print("Connections: ", obj["connections"]) + if obj: for (_, connection) in obj["connections"].items(): - print("Connection: ", connection) webview_home = connection["webview_home"] - print("Webview home: ", webview_home) webview_home = webview_home.format(host=host, client_id=client_id) connection.update({"webview_home": webview_home}) webview_connect = connection["webview_connect"] @@ -923,7 +920,6 @@ def get_supported_iot_connections(version: int = 1) -> Response: else: connect_url = connect_url.format(host=host, client_id=client_id) connection.update({"connect_url": connect_url}) - print("Connection: ", connection) json = better_jsonify(valid=True, data=obj) return json return better_jsonify(valid=False, errmsg="Error getting supported IOT devices.") diff --git a/routes/main.py b/routes/main.py index c520350a..48fbf234 100755 --- a/routes/main.py +++ b/routes/main.py @@ -124,15 +124,11 @@ def iot(device: str): f = file.read() # Read TOML file containing a list of resources for the dialogue obj: IotSupportedTOMLStructure = tomllib.loads(f) # type: ignore - print("TOML: ", obj) if obj: # for (_, connection) in obj["connections"].items(): - print("Connection: ", obj["connections"]) connection_info = obj["connections"][iot_name] - print("Display name: ", connection_info) print("Route device: ", device) return render_template(f"{str(device)}.html", **connection_info) - # device_variables.get(device, {})) @routes.route("/iot-connect-success") diff --git a/templates/sonos-connection.html b/templates/sonos-connection.html index 897bf6b7..95c7d014 100644 --- a/templates/sonos-connection.html +++ b/templates/sonos-connection.html @@ -37,7 +37,7 @@

    Leiðbeiningar

  • Veldu „Tengja“ hér að neðan og skráðu þig inn með Sonos aðgangi þínum.
  • - + From 1fdc78e49ac40d081b54bf95a096f3b6e89d7b2d Mon Sep 17 00:00:00 2001 From: Logi E Date: Fri, 16 Sep 2022 15:55:03 +0000 Subject: [PATCH 355/371] More cleanup, rearranged folders --- queries/__init__.py | 56 ------- queries/{ => extras}/dialogue.py | 196 ++++++++++++---------- queries/{ => extras}/resources.py | 35 +++- queries/{ => extras}/sonos.py | 69 +++++--- queries/{ => extras}/spotify.py | 97 ++++++++++- queries/fruitseller.py | 4 +- queries/grammars/iot_speakers.grammar | 2 - queries/iot_connect.py | 2 +- queries/iot_hue.py | 1 - queries/iot_speakers.py | 2 +- queries/iot_spotify.py | 2 +- queries/iot_spotify_old.py | 204 ----------------------- queries/js/Philips_Hue/fuse_search.js | 9 +- queries/js/Philips_Hue/lights.js | 17 -- queries/js/Philips_Hue/set_lights.js | 16 ++ queries/pizza.py | 4 +- queries/smartthings.py | 104 ------------ queries/theater.py | 4 +- query.py | 5 +- routes/api.py | 90 ++-------- routes/main.py | 12 +- static/img/checkmark.gif:Zone.Identifier | 4 - 22 files changed, 322 insertions(+), 613 deletions(-) rename queries/{ => extras}/dialogue.py (78%) rename queries/{ => extras}/resources.py (89%) rename queries/{ => extras}/sonos.py (92%) rename queries/{ => extras}/spotify.py (76%) delete mode 100644 queries/iot_spotify_old.py delete mode 100644 queries/js/Philips_Hue/lights.js delete mode 100644 queries/smartthings.py delete mode 100644 static/img/checkmark.gif:Zone.Identifier diff --git a/queries/__init__.py b/queries/__init__.py index 1917391f..c1c08069 100755 --- a/queries/__init__.py +++ b/queries/__init__.py @@ -453,62 +453,6 @@ def query_json_api(url: str, headers: Optional[Dict[str, str]] = None) -> JsonRe return None -def post_to_json_api( - url: str, json_data: Optional[Any] = None, headers: Optional[Dict[str, str]] = None -) -> Union[None, List[Any], Dict[str, Any]]: - """Send a POST request to the URL, expecting a JSON response which is - parsed and returned as a Python data structure.""" - - # Send request - try: - r = requests.post(url, data=json_data, headers=headers) - except Exception as e: - logging.warning(str(e)) - return None - - # Verify that status is OK - if r.status_code not in range(200, 300): - logging.warning("Received status {0} from API server".format(r.status_code)) - return None - - # Parse json API response - try: - res = json.loads(r.text) - return res - except Exception as e: - logging.warning("Error parsing JSON API response: {0}".format(e)) - return None - - -def put_to_json_api( - url: str, json_data: Optional[Any] = None, headers: Optional[Dict[str, str]] = None -) -> Union[None, List[Any], Dict[str, Any]]: - """Send a PUT request to the URL, expecting a JSON response which is - parsed and returned as a Python data structure.""" - - # Send request - try: - r = requests.put(url, data=json_data, headers=headers) - except Exception as e: - logging.warning(str(e)) - return None - - # Verify that status is OK - if r.status_code not in range(200, 300): - logging.warning("Received status {0} from API server".format(r.status_code)) - return None - - # Parse json API response - try: - if r.text: - res = json.loads(r.text) - return res - return {} - except Exception as e: - logging.warning("Error parsing JSON API response: {0}".format(e)) - return None - - def query_xml_api(url: str) -> Any: """Request the URL, expecting an XML response which is parsed and returned as an XML document object.""" diff --git a/queries/dialogue.py b/queries/extras/dialogue.py similarity index 78% rename from queries/dialogue.py rename to queries/extras/dialogue.py index fcdf74c3..09e48bcf 100644 --- a/queries/dialogue.py +++ b/queries/extras/dialogue.py @@ -1,4 +1,25 @@ -import copy +""" + + Greynir: Natural language processing for Icelandic + + Copyright (C) 2022 Miðeind ehf. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see http://www.gnu.org/licenses/. + + + Class for dialogue management. + +""" from typing import ( Any, Callable, @@ -7,30 +28,22 @@ Set, List, Optional, - TypeVar, cast, ) -from typing_extensions import TypedDict, NotRequired +from typing_extensions import TypedDict -import os.path import json import datetime +from pathlib import Path try: import tomllib # type: ignore (module not available in Python <3.11) except ModuleNotFoundError: import tomli as tomllib # Used for Python <3.11 +import queries.extras.resources as res from queries import AnswerTuple -from queries.resources import ( - RESOURCE_MAP, - OrResource, - Resource, - DialogueJSONDecoder, - DialogueJSONEncoder, - ResourceState, - WrapperResource, -) + # TODO:? Delegate answering from a resource to another resource or to another dialogue # TODO:? í ávaxtasamtali "ég vil panta flug" "viltu að ég geymi ávaxtapöntunina eða eyði henni?" ... @@ -44,17 +57,15 @@ _DEFAULT_EXPIRATION_TIME = 30 * 60 # a dialogue expires after 30 minutes _FINAL_RESOURCE_NAME = "Final" -# Generic resource type -ResourceType_co = TypeVar("ResourceType_co", bound="Resource") +# Functions for generating prompts/answers +# Arguments: resource, DSM, result object +AnsweringFunctionType = Callable[..., Optional[AnswerTuple]] -# Types for use in generating prompts/answers -AnsweringFunctionType = Callable[ - [ResourceType_co, "DialogueStateManager", Any], Optional[AnswerTuple] -] -# TODO: Fix 'Any' in type hint (Callable args are contravariant) -AnsweringFunctionMap = Mapping[str, AnsweringFunctionType[Any]] +# Difficult to type this correctly as the +# Callable type is contravariant in the parameters +AnsweringFunctionMap = Mapping[str, AnsweringFunctionType] -FilterFuncType = Callable[[Resource, int], bool] +FilterFuncType = Callable[[res.Resource, int], bool] _ALLOW_ALL_FILTER: FilterFuncType = lambda r, i: True ################################ @@ -69,20 +80,19 @@ class ResourceNotFoundError(Exception): class ResourceGraphItem(TypedDict): """Type for a node in the resource graph.""" - children: List[Resource] - parents: List[Resource] + children: List[res.Resource] + parents: List[res.Resource] # Dependency relationship graph type for resources -ResourceGraph = Dict[Resource, ResourceGraphItem] +ResourceGraph = Dict[res.Resource, ResourceGraphItem] -class DialogueTOMLStructure(TypedDict): +class DialogueTOMLStructure(TypedDict, total=False): """Structure of a dialogue TOML file.""" resources: List[Dict[str, Any]] dynamic_resources: List[Dict[str, Any]] - expiration_time: NotRequired[int] # Keys for accessing saved client data for dialogues @@ -104,7 +114,7 @@ class DialogueDBStructure(TypedDict): as it is saved to the database. """ - resources: Dict[str, Resource] + resources: Dict[str, res.Resource] modified: datetime.datetime extras: Dict[str, Any] @@ -118,7 +128,7 @@ def __init__(self, dialogue_data: DialogueDataDict) -> None: def load_dialogue(self, dialogue_name: str): self._dialogue_name: str = dialogue_name # Dict mapping resource name to resource instance - self._resources: Dict[str, Resource] = {} + self._resources: Dict[str, res.Resource] = {} # Boolean indicating if the client is in this dialogue self._in_this_dialogue: bool = False # Extra information saved with the dialogue state @@ -126,7 +136,7 @@ def load_dialogue(self, dialogue_name: str): # Answer for the current query self._answer_tuple: Optional[AnswerTuple] = None # Latest non-confirmed resource - self._current_resource: Optional[Resource] = None + self._current_resource: Optional[res.Resource] = None # Dependency graph for the resources self._resource_graph: ResourceGraph = {} # Database data for this dialogue, if any @@ -143,7 +153,7 @@ def load_dialogue(self, dialogue_name: str): if isinstance(dialogue_saved_state, str): self._saved_state = cast( DialogueDBStructure, - json.loads(dialogue_saved_state, cls=DialogueJSONDecoder), + json.loads(dialogue_saved_state, cls=res.DialogueJSONDecoder), ) # Check that we have saved data for this dialogue and that it is not expired @@ -173,7 +183,7 @@ def setup_resources(self) -> None: if self._saved_state and rname in self._saved_state["resources"]: resource.update(self._saved_state[_RESOURCES_KEY][rname]) # Change from int to enum type - resource.state = ResourceState(resource.state) + resource.state = res.ResourceState(resource.state) # Set extra data from database if self._saved_state and _EXTRAS_KEY in self._saved_state: self._extras = self._saved_state.get(_EXTRAS_KEY) or self._extras @@ -191,11 +201,9 @@ def _initialize_resource_graph(self) -> None: self._initial_resource = resource self._resource_graph[resource] = {"children": [], "parents": []} for resource in self._resources.values(): - # TODO: If OrResource, do something else for req in resource.requires: self._resource_graph[self._resources[req]]["parents"].append(resource) self._resource_graph[resource]["children"].append(self._resources[req]) - # print("Resource graph: ", self._resource_graph) def _initialize_resources(self, filename: str) -> None: """ @@ -207,13 +215,11 @@ def _initialize_resources(self, filename: str) -> None: for rname, resource in self._saved_state[_RESOURCES_KEY].items(): self._resources[rname] = resource self._expiration_time = self._saved_state.get( - "expiration_time", _DEFAULT_EXPIRATION_TIME + _EXPIRATION_TIME_KEY, _DEFAULT_EXPIRATION_TIME ) else: - basepath, _ = os.path.split(os.path.realpath(__file__)) - fpath = os.path.join(basepath, _TOML_FOLDER_NAME, filename + ".toml") - with open(fpath, mode="r") as file: - f = file.read() + p = Path(__file__).parent.parent.resolve() / _TOML_FOLDER_NAME / f"{filename}.toml" + f = p.read_text() # Read TOML file containing a list of resources for the dialogue obj: DialogueTOMLStructure = tomllib.loads(f) # type: ignore assert _RESOURCES_KEY in obj, f"No resources found in TOML file {f}" @@ -223,10 +229,12 @@ def _initialize_resources(self, filename: str) -> None: if "type" not in resource: resource["type"] = "Resource" # Create instances of Resource classes (and its subclasses) - self._resources[resource["name"]] = RESOURCE_MAP[resource["type"]]( + self._resources[resource["name"]] = res.RESOURCE_MAP[resource["type"]]( **resource, order_index=i ) - self._expiration_time = obj.get("expiration_time", _DEFAULT_EXPIRATION_TIME) + self._expiration_time = obj.get( + _EXPIRATION_TIME_KEY, _DEFAULT_EXPIRATION_TIME + ) def add_dynamic_resource(self, resource_name: str, parent_name: str) -> None: """ @@ -235,18 +243,20 @@ def add_dynamic_resource(self, resource_name: str, parent_name: str) -> None: """ # TODO: should dynamic resources be loaded from TOML at initialization? # Loading dynamic resources from TOML - basepath, _ = os.path.split(os.path.realpath(__file__)) - fpath = os.path.join(basepath, _TOML_FOLDER_NAME, self._dialogue_name + ".toml") - with open(fpath, mode="r") as file: - f = file.read() + p = ( + Path(__file__).parent.parent.resolve() + / _TOML_FOLDER_NAME + / f"{self._dialogue_name}.toml" + ) + f = p.read_text() obj: DialogueTOMLStructure = tomllib.loads(f) # type: ignore assert ( _DYNAMIC_RESOURCES_KEY in obj ), f"No dynamic resources found in TOML file {f}" - parent_resource: Resource = self.get_resource(parent_name) + parent_resource: res.Resource = self.get_resource(parent_name) order_index: int = parent_resource.order_index - dynamic_resources: Dict[str, Resource] = {} + dynamic_resources: Dict[str, res.Resource] = {} # Index of dynamic resource dynamic_resource_index = ( len( @@ -267,15 +277,15 @@ def add_dynamic_resource(self, resource_name: str, parent_name: str) -> None: dynamic_resource["type"] = "Resource" # Updating required resources to have indexed name dynamic_resource["requires"] = [ - f"{res}_{dynamic_resource_index}" - for res in dynamic_resource.get("requires", []) + f"{r}_{dynamic_resource_index}" + for r in dynamic_resource.get("requires", []) ] # Updating dynamic resource name to have indexed name dynamic_resource["name"] = ( f"{dynamic_resource['name']}_" f"{dynamic_resource_index}" ) # Adding dynamic resource to list - dynamic_resources[dynamic_resource["name"]] = RESOURCE_MAP[ + dynamic_resources[dynamic_resource["name"]] = res.RESOURCE_MAP[ dynamic_resource["type"] ]( **dynamic_resource, @@ -283,11 +293,11 @@ def add_dynamic_resource(self, resource_name: str, parent_name: str) -> None: ) # Indexed resource name of the dynamic resource indexed_resource_name = f"{resource_name}_{dynamic_resource_index}" - resource: Resource = dynamic_resources[indexed_resource_name] + resource: res.Resource = dynamic_resources[indexed_resource_name] # Appending resource to required list of parent resource parent_resource.requires.append(indexed_resource_name) - def _add_child_resource(resource: Resource) -> None: + def _add_child_resource(resource: res.Resource) -> None: """ Recursively adds a child resource to the resources list """ @@ -300,7 +310,7 @@ def _add_child_resource(resource: Resource) -> None: self._initialize_resource_graph() self._find_current_resource() - def duplicate_dynamic_resource(self, original: Resource) -> None: + def duplicate_dynamic_resource(self, original: res.Resource) -> None: suffix = ( len( [ @@ -314,9 +324,9 @@ def duplicate_dynamic_resource(self, original: Resource) -> None: + 1 ) - def _recursive_deep_copy(resource: Resource) -> None: + def _recursive_deep_copy(resource: res.Resource) -> None: nonlocal suffix, self - new_resource = RESOURCE_MAP[resource.type](**resource.__dict__) + new_resource = res.RESOURCE_MAP[resource.type](**resource.__dict__) prefix = "_".join(new_resource.name.split("_")[:-1]) new_resource.name = prefix + f"_{suffix}" new_resource.requires = [ @@ -358,12 +368,13 @@ def dialogue_name(self) -> Optional[str]: return None @property - def current_resource(self) -> Resource: + def current_resource(self) -> res.Resource: if self._current_resource is None: self._find_current_resource() + assert self._current_resource is not None return self._current_resource - def get_resource(self, name: str) -> Resource: + def get_resource(self, name: str) -> res.Resource: try: return self._resources[name] except KeyError: @@ -378,8 +389,8 @@ def timed_out(self) -> bool: return self._timed_out def get_descendants( - self, resource: Resource, filter_func: Optional[FilterFuncType] = None - ) -> List[Resource]: + self, resource: res.Resource, filter_func: Optional[FilterFuncType] = None + ) -> List[res.Resource]: """ Given a resource and an optional filter function (with a resource and the depth in tree as args, returns a boolean), @@ -387,10 +398,10 @@ def get_descendants( (all of them if filter_func is None). Returns the descendants in preorder """ - descendants: List[Resource] = [] + descendants: List[res.Resource] = [] def _recurse_descendants( - resource: Resource, depth: int, filter_func: FilterFuncType + resource: res.Resource, depth: int, filter_func: FilterFuncType ) -> None: nonlocal descendants for child in self._resource_graph[resource]["children"]: @@ -401,23 +412,23 @@ def _recurse_descendants( _recurse_descendants(resource, 0, filter_func or _ALLOW_ALL_FILTER) return descendants - def get_children(self, resource: Resource) -> List[Resource]: + def get_children(self, resource: res.Resource) -> List[res.Resource]: """Given a resource, returns all children of the resource""" return self._resource_graph[resource]["children"] def get_ancestors( - self, resource: Resource, filter_func: Optional[FilterFuncType] = None - ) -> List[Resource]: + self, resource: res.Resource, filter_func: Optional[FilterFuncType] = None + ) -> List[res.Resource]: """ Given a resource and an optional filter function (with a resource and the depth in tree as args, returns a boolean), returns all ancestors of the resource that match the function (all of them if filter_func is None). """ - ancestors: List[Resource] = [] + ancestors: List[res.Resource] = [] def _recurse_ancestors( - resource: Resource, depth: int, filter_func: FilterFuncType + resource: res.Resource, depth: int, filter_func: FilterFuncType ) -> None: nonlocal ancestors for parent in self._resource_graph[resource]["parents"]: @@ -428,7 +439,7 @@ def _recurse_ancestors( _recurse_ancestors(resource, 0, filter_func or _ALLOW_ALL_FILTER) return ancestors - def get_parents(self, resource: Resource) -> List[Resource]: + def get_parents(self, resource: res.Resource) -> List[res.Resource]: """Given a resource, returns all parents of the resource""" return self._resource_graph[resource]["parents"] @@ -438,6 +449,7 @@ def get_answer( if self._answer_tuple is not None: return self._answer_tuple self._find_current_resource() + assert self._current_resource is not None self._answering_functions = answering_functions # Check if dialogue was cancelled # TODO: Change this (have separate cancel method) @@ -464,7 +476,7 @@ def get_answer( # TODO: Can we remove this function? def _get_answer( - self, curr_resource: Resource, result: Any, finished: Set[Resource] + self, curr_resource: res.Resource, result: Any, finished: Set[res.Resource] ) -> Optional[AnswerTuple]: for resource in self._resource_graph[curr_resource]["children"]: if resource not in finished: @@ -481,7 +493,7 @@ def _get_answer( def set_answer(self, answer: AnswerTuple) -> None: self._answer_tuple = answer - def set_resource_state(self, resource_name: str, state: ResourceState): + def set_resource_state(self, resource_name: str, state: res.ResourceState): """ Set the state of a resource. Sets state of all parent resources to unfulfilled @@ -490,24 +502,24 @@ def set_resource_state(self, resource_name: str, state: ResourceState): resource = self._resources[resource_name] lowered_state = resource.state > state resource.state = state - if state == ResourceState.FULFILLED and not resource.needs_confirmation: - resource.state = ResourceState.CONFIRMED + if state == res.ResourceState.FULFILLED and not resource.needs_confirmation: + resource.state = res.ResourceState.CONFIRMED return if resource.cascade_state and lowered_state: # Find all parent resources and set to corresponding state ancestors = set(self.get_ancestors(resource)) for anc in ancestors: - anc.state = ResourceState.UNFULFILLED + anc.state = res.ResourceState.UNFULFILLED def _find_current_resource(self) -> None: """ Finds the current resource in the resource graph using a postorder traversal of the resource graph. """ - curr_res: Optional[Resource] = None - finished_resources: Set[Resource] = set() + curr_res: Optional[res.Resource] = None + finished_resources: Set[res.Resource] = set() - def _recurse_resources(resource: Resource) -> None: + def _recurse_resources(resource: res.Resource) -> None: nonlocal curr_res, finished_resources finished_resources.add(resource) if resource.is_confirmed or resource.is_skipped: @@ -526,7 +538,7 @@ def _recurse_resources(resource: Resource) -> None: wrapper_parents = [ par for par in self._resource_graph[curr_res]["parents"] - if isinstance(par, WrapperResource) + if isinstance(par, res.WrapperResource) ] assert ( len(wrapper_parents) <= 1 @@ -542,47 +554,51 @@ def _recurse_resources(resource: Resource) -> None: self._current_resource = curr_res or self._resources[_FINAL_RESOURCE_NAME] # TODO: Can we move this function into set_resource_state? - def skip_other_resources(self, or_resource: OrResource, resource: Resource) -> None: + def skip_other_resources( + self, or_resource: res.OrResource, resource: res.Resource + ) -> None: """Skips other resources in the or resource""" # TODO: Check whether OrResource is exclusive or not assert isinstance( - or_resource, OrResource + or_resource, res.OrResource ), f"{or_resource} is not an OrResource" - for res in or_resource.requires: - if res != resource.name: - self.set_resource_state(res, ResourceState.SKIPPED) + for r in or_resource.requires: + if r != resource.name: + self.set_resource_state(r, res.ResourceState.SKIPPED) # TODO: Can we move this function into set_resource_state? - def update_wrapper_state(self, wrapper: WrapperResource) -> None: + def update_wrapper_state(self, wrapper: res.WrapperResource) -> None: """ Updates the state of the wrapper resource based on the state of its children. """ - if wrapper.state == ResourceState.UNFULFILLED: + if wrapper.state == res.ResourceState.UNFULFILLED: print("Wrapper is unfulfilled") if all( [ - child.state == ResourceState.UNFULFILLED + child.state == res.ResourceState.UNFULFILLED for child in self._resource_graph[wrapper]["children"] ] ): print("All children are unfulfilled") return print("At least one child is fulfilled") - self.set_resource_state(wrapper.name, ResourceState.PARTIALLY_FULFILLED) - if wrapper.state == ResourceState.PARTIALLY_FULFILLED: + self.set_resource_state(wrapper.name, res.ResourceState.PARTIALLY_FULFILLED) + if wrapper.state == res.ResourceState.PARTIALLY_FULFILLED: print("Wrapper is partially fulfilled") if any( [ - child.state != ResourceState.CONFIRMED + child.state != res.ResourceState.CONFIRMED for child in self._resource_graph[wrapper]["children"] ] ): print("At least one child is not confirmed") - self.set_resource_state(wrapper.name, ResourceState.PARTIALLY_FULFILLED) + self.set_resource_state( + wrapper.name, res.ResourceState.PARTIALLY_FULFILLED + ) return print("All children are confirmed") - self.set_resource_state(wrapper.name, ResourceState.FULFILLED) + self.set_resource_state(wrapper.name, res.ResourceState.FULFILLED) def finish_dialogue(self) -> None: """Set the dialogue as finished.""" @@ -602,7 +618,7 @@ def serialize_data(self) -> Dict[str, Optional[str]]: _MODIFIED_KEY: datetime.datetime.now(), _EXTRAS_KEY: self._extras, }, - cls=DialogueJSONEncoder, + cls=res.DialogueJSONEncoder, ) # Wrap data before saving dialogue state into client data # (due to custom JSON serialization) diff --git a/queries/resources.py b/queries/extras/resources.py similarity index 89% rename from queries/resources.py rename to queries/extras/resources.py index af76b80e..186e18c5 100644 --- a/queries/resources.py +++ b/queries/extras/resources.py @@ -1,3 +1,26 @@ +""" + + Greynir: Natural language processing for Icelandic + + Copyright (C) 2022 Miðeind ehf. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see http://www.gnu.org/licenses/. + + + Collection of resource types for dialogues. + Resources are slots for information extracted from dialogue. + +""" from typing import ( Any, Callable, @@ -173,13 +196,13 @@ def format_data(self, format_func: Optional[Callable[[Any], str]] = None) -> str return "já" if self.data else "nei" -@dataclass(eq=False, repr=False) -class ConfirmResource(YesNoResource): - """Resource representing a confirmation of other resources.""" +# @dataclass(eq=False, repr=False) +# class ConfirmResource(YesNoResource): +# """Resource representing a confirmation of other resources.""" - def set_no(self): - self.data = False - self.state = ResourceState.CANCELLED # TODO: ? +# def set_no(self): +# self.data = False +# self.state = ResourceState.CANCELLED # TODO: ? @dataclass(eq=False, repr=False) diff --git a/queries/sonos.py b/queries/extras/sonos.py similarity index 92% rename from queries/sonos.py rename to queries/extras/sonos.py index c246ff80..d6480808 100644 --- a/queries/sonos.py +++ b/queries/extras/sonos.py @@ -1,16 +1,3 @@ -from typing import Dict, Optional, Union, List, Any -from inspect import getargs -import requests -from datetime import datetime, timedelta -import flask -import random - -from util import read_api_key -from queries import query_json_api, post_to_json_api -from query import Query - -import json - """ Greynir: Natural language processing for Icelandic @@ -30,11 +17,51 @@ along with this program. If not, see http://www.gnu.org/licenses/. - API routes - Note: All routes ending with .api are configured not to be cached by nginx + Class which encapsulates communication with the Sonos API. """ +from typing import Dict, Optional, Union, List, Any + +import logging +import json +import flask +import requests +from datetime import datetime, timedelta + +from util import read_api_key +from queries import query_json_api +from query import Query + +def post_to_json_api( + url: str, + *, + form_data: Optional[Any] = None, + json_data: Optional[Any] = None, + headers: Optional[Dict[str, str]] = None, +) -> Union[None, List[Any], Dict[str, Any]]: + """Send a POST request to the URL, expecting a JSON response which is + parsed and returned as a Python data structure.""" + + # Send request + try: + r = requests.post(url, data=form_data, json=json_data, headers=headers) + except Exception as e: + logging.warning(str(e)) + return None + + # Verify that status is OK + if r.status_code not in range(200, 300): + logging.warning("Received status {0} from API server".format(r.status_code)) + return None + + # Parse json API response + try: + res = json.loads(r.text) + return res + except Exception as e: + logging.warning("Error parsing JSON API response: {0}".format(e)) + return None _GROUPS_DICT = { "fjölskylduherbergi": "Family Room", "fjölskyldu herbergi": "Family Room", @@ -367,7 +394,7 @@ def _create_or_join_session(self, recursion=None) -> Optional[str]: "Authorization": f"Bearer {self._access_token}", } - response = post_to_json_api(url, payload, headers) + response = post_to_json_api(url, form_data=payload, headers=headers) print(response) if response is None: self.toggle_pause() @@ -413,7 +440,7 @@ def play_radio_stream(self, radio_url: str) -> Optional[str]: "Authorization": f"Bearer {self._access_token}", } - response = post_to_json_api(url, payload, headers) + response = post_to_json_api(url, form_data=payload, headers=headers) if response is None: return "Group not found" data_dict = {"sonos": {"data": {"last_radio_url": radio_url}}} @@ -430,7 +457,7 @@ def increase_volume(self) -> None: "Authorization": f"Bearer {self._access_token}", } - response = post_to_json_api(url, payload, headers) + response = post_to_json_api(url, form_data=payload, headers=headers) if response is None: self._refresh_data("increase_volume") print(response.get("text")) @@ -446,7 +473,7 @@ def decrease_volume(self) -> None: "Authorization": f"Bearer {self._access_token}", } - response = post_to_json_api(url, payload, headers) + response = post_to_json_api(url, form_data=payload, headers=headers) if response is None: return "Group not found" print(response.get("text")) @@ -516,7 +543,7 @@ def play_audio_clip( "Authorization": f"Bearer {self._access_token}", } - response = post_to_json_api(url, payload, headers) + response = post_to_json_api(url, form_data=payload, headers=headers) if response is None: return "Group not found" return response @@ -539,7 +566,7 @@ def play_chime(self) -> Union[None, List[Any], Dict[str, Any]]: "Authorization": f"Bearer {self._access_token}", } - response = post_to_json_api(url, payload, headers) + response = post_to_json_api(url, form_data=payload, headers=headers) return response diff --git a/queries/spotify.py b/queries/extras/spotify.py similarity index 76% rename from queries/spotify.py rename to queries/extras/spotify.py index 16a4fa41..c53778fd 100644 --- a/queries/spotify.py +++ b/queries/extras/spotify.py @@ -1,15 +1,96 @@ +""" + + Greynir: Natural language processing for Icelandic + + Copyright (C) 2022 Miðeind ehf. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see http://www.gnu.org/licenses/. + + + Class which encapsulates communication with the Spotify API. + +""" from typing import Dict, Optional, Union, List, Any -from inspect import getargs + +import logging +import json +import flask import requests from datetime import datetime, timedelta -import flask -import random from util import read_api_key -from queries import query_json_api, post_to_json_api, put_to_json_api +from queries import query_json_api from query import Query -import json + +def post_to_json_api( + url: str, + *, + form_data: Optional[Any] = None, + json_data: Optional[Any] = None, + headers: Optional[Dict[str, str]] = None, +) -> Union[None, List[Any], Dict[str, Any]]: + """Send a POST request to the URL, expecting a JSON response which is + parsed and returned as a Python data structure.""" + + # Send request + try: + r = requests.post(url, data=form_data, json=json_data, headers=headers) + except Exception as e: + logging.warning(str(e)) + return None + + # Verify that status is OK + if r.status_code not in range(200, 300): + logging.warning("Received status {0} from API server".format(r.status_code)) + return None + + # Parse json API response + try: + res = json.loads(r.text) + return res + except Exception as e: + logging.warning("Error parsing JSON API response: {0}".format(e)) + return None + +def put_to_json_api( + url: str, json_data: Optional[Any] = None, headers: Optional[Dict[str, str]] = None +) -> Union[None, List[Any], Dict[str, Any]]: + """Send a PUT request to the URL, expecting a JSON response which is + parsed and returned as a Python data structure.""" + + # Send request + try: + r = requests.put(url, data=json_data, headers=headers) + except Exception as e: + logging.warning(str(e)) + return None + + # Verify that status is OK + if r.status_code not in range(200, 300): + logging.warning("Received status {0} from API server".format(r.status_code)) + return None + + # Parse json API response + try: + if r.text: + res = json.loads(r.text) + return res + return {} + except Exception as e: + logging.warning("Error parsing JSON API response: {0}".format(e)) + return None + # TODO Find a better way to play albums # TODO - Remove debug print statements @@ -59,7 +140,7 @@ def _create_token(self) -> Union[None, List[Any], Dict[str, Any]]: "Authorization": f"Basic {self._encoded_credentials}", } - response = post_to_json_api(url, payload, headers) + response = post_to_json_api(url, form_data=payload, headers=headers) self._access_token = response.get("access_token") self._refresh_token = response.get("refresh_token") self._timestamp = str(datetime.now()) @@ -101,7 +182,7 @@ def _refresh_expired_token(self) -> None: "Authorization": f"Basic {self._encoded_credentials}", } - response = post_to_json_api(url, payload, headers) + response = post_to_json_api(url, form_data=payload, headers=headers) self._access_token = response.get("access_token") self._timestamp = str(datetime.now()) @@ -247,8 +328,6 @@ def play_song_on_device(self) -> Union[None, List[Any], Dict[str, Any]]: return response def play_album_on_device(self) -> Union[None, List[Any], Dict[str, Any]]: - print("play song from device") - print("accesss token play song; ", self._access_token) url = f"{self._api_url}/me/player/play" payload = json.dumps( diff --git a/queries/fruitseller.py b/queries/fruitseller.py index c5deceee..bb46961d 100644 --- a/queries/fruitseller.py +++ b/queries/fruitseller.py @@ -14,11 +14,11 @@ read_grammar_file, sing_or_plur, ) -from queries.dialogue import ( +from queries.extras.dialogue import ( AnsweringFunctionMap, DialogueStateManager, ) -from queries.resources import ( +from queries.extras.resources import ( DateResource, ListResource, Resource, diff --git a/queries/grammars/iot_speakers.grammar b/queries/grammars/iot_speakers.grammar index 172ac4b3..8ae4643f 100644 --- a/queries/grammars/iot_speakers.grammar +++ b/queries/grammars/iot_speakers.grammar @@ -207,12 +207,10 @@ QIoTSpeakerRadioWord/fall → QIoTSpeakerMoreOrHigher/fall → 'mikill:lo'_mst/fall - | 'ljós:lo'_mst/fall | 'hár:lo'_mst/fall QIoTSpeakerLessOrLower/fall → 'lítill:lo'_mst/fall - | 'dimmur:lo'_mst/fall | 'lágur:lo'_mst/fall QIoTSpeakerHvarWithAppliance → diff --git a/queries/iot_connect.py b/queries/iot_connect.py index cd01e8fd..d7ba03a5 100644 --- a/queries/iot_connect.py +++ b/queries/iot_connect.py @@ -30,7 +30,7 @@ from query import Query, QueryStateDict from queries import gen_answer, read_jsfile -from queries.sonos import SonosClient +from queries.extras.sonos import SonosClient from tree import Result, Node from util import read_api_key diff --git a/queries/iot_hue.py b/queries/iot_hue.py index 59c373f2..ee60c59a 100755 --- a/queries/iot_hue.py +++ b/queries/iot_hue.py @@ -370,7 +370,6 @@ def sentence(state: QueryStateDict, result: Result) -> None: read_jsfile("Libraries/fuse.js") + f"var BRIDGE_IP = '{bridge_ip}';var USERNAME = '{username}';" + read_jsfile("Philips_Hue/fuse_search.js") - + read_jsfile("Philips_Hue/lights.js") + read_jsfile("Philips_Hue/set_lights.js") ) js += f"return setLights('{light_or_group_name}', '{json.dumps(result.hue_obj)}');" diff --git a/queries/iot_speakers.py b/queries/iot_speakers.py index 1348839b..75f10ebc 100644 --- a/queries/iot_speakers.py +++ b/queries/iot_speakers.py @@ -38,7 +38,7 @@ from query import Query, QueryStateDict from queries import read_grammar_file -from queries.sonos import SonosClient +from queries.extras.sonos import SonosClient from tree import Result, Node # Dictionary of radio stations and their stream urls diff --git a/queries/iot_spotify.py b/queries/iot_spotify.py index 0a9662a6..dc58c71c 100644 --- a/queries/iot_spotify.py +++ b/queries/iot_spotify.py @@ -33,7 +33,7 @@ import re from query import Query -from queries.spotify import SpotifyClient +from queries.extras.spotify import SpotifyClient from queries import gen_answer diff --git a/queries/iot_spotify_old.py b/queries/iot_spotify_old.py deleted file mode 100644 index bd9cff3d..00000000 --- a/queries/iot_spotify_old.py +++ /dev/null @@ -1,204 +0,0 @@ -# """ - -# Greynir: Natural language processing for Icelandic - -# Randomness query response module - -# Copyright (C) 2022 Miðeind ehf. - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see http://www.gnu.org/licenses/. - -# This query module handles queries related to the generation -# of random numbers, e.g. "Kastaðu tengingi", "Nefndu tölu milli 5 og 10", etc. - -# """ - - -# # TODO: add "láttu", "hafðu", "litaðu", "kveiktu" functionality. -# # TODO: make the objects of sentences more modular, so that the same structure doesn't need to be written for each action -# # TODO: ditto the previous comment. make the initial non-terminals general and go into specifics at the terminal level instead. -# # TODO: substituion klósett, baðherbergi hugmyndÆ senda lista i javascript og profa i röð -# # TODO: Embla stores old javascript code cached which has caused errors -# # TODO: Cut down javascript sent to Embla -# # TODO: Two specified groups or lights. -# # TODO: No specified location -# # TODO: Fix scene issues - -# from os import access -# from typing import Dict, Mapping, Optional, cast -# from typing_extensions import TypedDict - -# import logging -# import random -# import json -# import flask -# from datetime import datetime, timedelta -# import re - -# from reynir.lemmatize import simple_lemmatize - -# from query import Query, QueryStateDict, AnswerTuple -# from queries import gen_answer, read_jsfile, read_grammar_file -# from queries.spotify import SpotifyClient -# from tree import Result, Node, TerminalNode -# from util import read_api_key - - -# _IoT_QTYPE = "IoTSpotify" - -# # TOPIC_LEMMAS = [ -# # "tónlist", -# # "spila", -# # ] - - -# def help_text(lemma: str) -> str: -# """Help text to return when query.py is unable to parse a query but -# one of the above lemmas is found in it""" -# return "Ég skil þig ef þú segir til dæmis: {0}.".format( -# random.choice( -# ("Hækkaðu í tónlistinni", "Kveiktu á tónlist", "Láttu vera tónlist") -# ) -# ) - - -# # This module wants to handle parse trees for queries -# HANDLE_TREE = True - -# # The grammar nonterminals this module wants to handle -# QUERY_NONTERMINALS = {"QIoTSpotify"} - -# # The context-free grammar for the queries recognized by this plug-in module - -# _SPOTIFY_REGEXES = [ -# r"^(spilaðu )([\w|\s]+)(með )([\w|\s]+)$", -# ] - -# GRAMMAR = f""" - -# /þgf = þgf -# /ef = ef - -# Query → -# QIoTSpotify '?'? - -# QIoTSpotify → -# QIoTSpotifyPlaySongByArtist - -# QIoTSpotifyPlaySongByArtist → -# QIoTSpotifyPlayVerb QIoTSpotifySongName QIoTSpotifyWithPreposition QIoTSpotifyArtistName - -# QIoTSpotifyPlayVerb → -# 'spila:so'_bh - -# QIoTSpotifySongName → -# Nl - -# QIoTSpotifyWithPreposition → -# 'með' -# | 'eftir' - -# QIoTSpotifyArtistName → -# Nl -# | sérnafn -# """ - - -# def QIoTSpotify(node: Node, params: QueryStateDict, result: Result) -> None: -# result.qtype = _IoT_QTYPE - - -# def QIoTSpotifyPlayVerb(node: Node, params: QueryStateDict, result: Result) -> None: -# "spotify play function" -# result.action = "play" - - -# def QIoTSpotifySongName(node: Node, params: QueryStateDict, result: Result) -> None: -# result.song_name = result._text - - -# def QIoTSpotifyArtistName(node: Node, params: QueryStateDict, result: Result) -> None: -# result.artist_name = result._indefinite - - -# def get_song_and_artist(q: Query) -> tuple: -# """Handle a plain text query requesting Spotify to play a specific song by a specific artist.""" -# # ql = q.query_lower.strip().rstrip("?") -# print("handle_plain_text") -# ql = q.query_lower.strip().rstrip("?") -# print("QL:", ql) - -# pfx = None - -# for rx in _SPOTIFY_REGEXES: -# print(rx) -# print("") -# m = re.search(rx, ql) -# print(m) -# if m: -# (print("MATCH!")) -# song_name = m.group(2) -# artist_name = m.group(4).strip() -# return (song_name, artist_name) -# else: -# return False - - -# def sentence(state: QueryStateDict, result: Result) -> None: -# """Called when sentence processing is complete""" -# print("sentence") -# q: Query = state["query"] -# if result.action == "play": -# print("SPOTIFY PLAY") -# song_artist_tuple = get_song_and_artist(q) -# print("exited plain text") -# song_name = song_artist_tuple[0] -# artist_name = song_artist_tuple[1] -# print("SONG NAME :", song_name) -# print("ARTIST NAME :", artist_name) - -# print("RESTULT SONG NAME:", result.song_name) -# print("RESULT ARTIST NAME:", result.artist_name) -# device_data = q.client_data("spotify") -# if device_data is not None: -# client_id = str(q.client_id) -# spotify_client = SpotifyClient( -# device_data, -# client_id, -# song_name=result.song_name, -# artist_name=result.artist_name, -# ) -# song_url = spotify_client.get_song_by_artist() -# response = spotify_client.play_song_on_device() -# # response = None -# print("RESPONSE FROM SPOTIFY:", response) -# if response is None: -# q.set_url(song_url) - -# answer = "Ég spilaði lagið" -# else: -# answer = "Það vantar að tengja Spotify aðgang." -# q.set_answer(*gen_answer(answer)) -# return -# # q.set_url( -# # "https://spotify.app.link/?product=open&%24full_url=https%3A%2F%2Fopen.spotify.com%2Ftrack%2F2BSyX4weGuITcvl5r2lLCC%3Fgo%3D1%26sp_cid%3D2a74d03dedb9fa4450d122ddebebcf9b%26fallback%3Dgetapp&feature=organic&_p=c31529c0980b7af1e11b90f9" -# # ) -# voice_answer, response = answer, dict(answer=answer) -# q.set_answer(response, answer, voice_answer) - -# else: -# print("ELSE") -# q.set_error("E_QUERY_NOT_UNDERSTOOD") -# return - -# # # TODO: Need to add check for if there are no registered devices to an account, probably when initilazing the querydata diff --git a/queries/js/Philips_Hue/fuse_search.js b/queries/js/Philips_Hue/fuse_search.js index a6fe0ad7..5bf95600 100644 --- a/queries/js/Philips_Hue/fuse_search.js +++ b/queries/js/Philips_Hue/fuse_search.js @@ -1,10 +1,9 @@ "use strict"; -/* -Fuzzy search function that returns an object in the form of {result: (Object), score: (Number)} -* @param {String} query - The search term -* @param {Object} data - The data to search -*/ +/** Fuzzy search function that returns an object in the form of {result: (Object), score: (Number)} + * @param {String} query - The search term + * @param {Object} data - The data to search + */ function philipsFuzzySearch(query, data) { // Restructure data to be searchable by name let newData = Object.keys(data).map(function (key) { diff --git a/queries/js/Philips_Hue/lights.js b/queries/js/Philips_Hue/lights.js deleted file mode 100644 index 451dd0e4..00000000 --- a/queries/js/Philips_Hue/lights.js +++ /dev/null @@ -1,17 +0,0 @@ -"use strict"; - -async function getAllLights(hub_ip = BRIDGE_IP, username = USERNAME) { - return fetch(`http://${hub_ip}/api/${username}/lights`).then((resp) => resp.json()); -} - -async function getAllGroups(hub_ip = BRIDGE_IP, username = USERNAME) { - return fetch(`http://${hub_ip}/api/${username}/groups`).then((resp) => resp.json()); -} - -async function getAllScenes(hub_ip = BRIDGE_IP, username = USERNAME) { - return fetch(`http://${hub_ip}/api/${username}/scenes`).then((resp) => resp.json()); -} - -function getCurrentState(id) { - return fetch(`http://${BRIDGE_IP}/api/${USERNAME}/lights/${id}`).then((resp) => resp.json()); -} diff --git a/queries/js/Philips_Hue/set_lights.js b/queries/js/Philips_Hue/set_lights.js index 04d42253..92177072 100644 --- a/queries/js/Philips_Hue/set_lights.js +++ b/queries/js/Philips_Hue/set_lights.js @@ -66,6 +66,22 @@ function getSceneID(scene_name, allScenes) { } } +async function getAllLights(hub_ip = BRIDGE_IP, username = USERNAME) { + return fetch(`http://${hub_ip}/api/${username}/lights`).then((resp) => resp.json()); +} + +async function getAllGroups(hub_ip = BRIDGE_IP, username = USERNAME) { + return fetch(`http://${hub_ip}/api/${username}/groups`).then((resp) => resp.json()); +} + +async function getAllScenes(hub_ip = BRIDGE_IP, username = USERNAME) { + return fetch(`http://${hub_ip}/api/${username}/scenes`).then((resp) => resp.json()); +} + +function getCurrentState(id) { + return fetch(`http://${BRIDGE_IP}/api/${USERNAME}/lights/${id}`).then((resp) => resp.json()); +} + /** * Check whether any of the targeted lights are Ikea TRADFRI lights. * Done in order to deal with a bug where the lights only accept diff --git a/queries/pizza.py b/queries/pizza.py index 7b30db6b..e66f6014 100644 --- a/queries/pizza.py +++ b/queries/pizza.py @@ -35,7 +35,7 @@ sing_or_plur, ) from queries.num import number_to_text, numbers_to_ordinal, numbers_to_text -from queries.resources import ( +from queries.extras.resources import ( FinalResource, ListResource, DictResource, @@ -45,7 +45,7 @@ StringResource, WrapperResource, ) -from queries.dialogue import ( +from queries.extras.dialogue import ( AnsweringFunctionMap, DialogueStateManager, ) diff --git a/queries/smartthings.py b/queries/smartthings.py deleted file mode 100644 index 25b90b87..00000000 --- a/queries/smartthings.py +++ /dev/null @@ -1,104 +0,0 @@ -from inspect import getargs -import requests -from datetime import datetime, timedelta -import flask -import random - -from util import read_api_key -from queries import query_json_api, post_to_json_api -from query import Query -from typing import Dict - -import json - - -class SmartThingsClient: - def __init__( - self, - device_data: Dict[str, str], - client_id: str, - ): - self._client_id = client_id - self._device_data = device_data - self._smartthings_encoded_credentials = read_api_key( - "SmartThingsEncodedCredentials" - ) - self._code = self._device_data["smartthings"]["credentials"]["code"] - print("code :", self._code) - self._timestamp = datetime.now() - print("device data :", self._device_data) - try: - self._access_token = self._device_data["smartthings"]["credentials"][ - "access_token" - ] - except (KeyError, TypeError): - self._create_token() - # self._check_token_expiration() - # self._households = self._get_households() - # self._household_id = self._households[0]["id"] - # self._groups = self._get_groups() - # self._players = self._get_players() - # self._group_id = self._get_group_id() - # self._store_smartthings_data_and_credentials() - self._store_credentials() - - def _create_token(self): - - url = "https://api.smartthings.com/v1/oauth/token" - - payload = f"code={self._code}&redirect_uri=http%3A%2F%2F192.168.1.69%3A5000%2Fconnect_smartthings.api&grant_type=authorization_code" - headers = { - "Content-Type": "application/x-www-form-urlencoded", - "Authorization": f"Basic {self._smartthings_encoded_credentials}", - } - - response = post_to_json_api(url, payload, headers) - self._access_token = response.get("access_token") - return response - - def _store_credentials(self): - print("_store_smartthings_cred") - # data_dict = self._create_sonos_data_dict() - cred_dict = self._create_cred_dict() - smartthings_dict = {} - smartthings_dict["smartthings"] = {"credentials": cred_dict} - self._store_data(smartthings_dict) - - def _create_cred_dict(self): - print("_create_smartthings_cred_dict") - cred_dict = {} - cred_dict.update( - { - "access_token": self._access_token, - "timestamp": str(datetime.now()), - } - ) - return cred_dict - - def _store_data(self, data): - Query.store_query_data(self._client_id, "iot_hubs", data, update_in_place=True) - - def set_color(self): - - url = "https://api.smartthings.com/v1/devices/7d47b44f-057c-4320-9777-3d1eadca106e/commands" - - payload = json.dumps( - { - "commands": [ - { - "component": "main", - "capability": "colorControl", - "command": "setColor", - "arguments": [[100, 50]], - } - ] - } - ) - headers = { - "Authorization": f"Bearer {self._access_token}", - "Content-Type": "application/json", - } - - response = requests.request("POST", url, headers=headers, data=payload) - - print(response.text) diff --git a/queries/theater.py b/queries/theater.py index e0c8efbf..6b181ebc 100644 --- a/queries/theater.py +++ b/queries/theater.py @@ -40,7 +40,7 @@ time_period_desc, ) from queries.num import number_to_text, numbers_to_ordinal, numbers_to_text -from queries.resources import ( +from queries.extras.resources import ( DateResource, FinalResource, ListResource, @@ -50,7 +50,7 @@ TimeResource, WrapperResource, ) -from queries.dialogue import ( +from queries.extras.dialogue import ( AnsweringFunctionMap, DialogueStateManager, ) diff --git a/query.py b/query.py index cb6fb92e..0b0d524a 100755 --- a/query.py +++ b/query.py @@ -47,12 +47,13 @@ import importlib import logging +from copy import deepcopy from datetime import datetime, timedelta import json import re import random from collections import defaultdict -from queries.dialogue import ( +from queries.extras.dialogue import ( DialogueStateManager as DSM, ResourceNotFoundError, DialogueDataDict, @@ -465,7 +466,6 @@ def process_queries( # For development, we allow processors to be disinterested in any query # assert len(processor_query_types) > 0 if self.query_nonterminals.isdisjoint(processor_query_types): - print("!!!!!No query trees to process in this processor!!!!!") # But this processor is not interested in any of the nonterminals # in this query's parse forest: don't waste more cycles on it return False @@ -865,7 +865,6 @@ def execute_from_dialogue(self) -> bool: processor, "QUERY_NONTERMINALS", set() ) if self._tree.query_nonterminals.isdisjoint(processor_query_types): - print("!!!!!No query trees to process in this processor!!!!!") # But this processor is not interested in any of the nonterminals # in this query's parse forest: don't waste more cycles on it continue diff --git a/routes/api.py b/routes/api.py index eb81baaf..f08fd2fe 100755 --- a/routes/api.py +++ b/routes/api.py @@ -27,8 +27,7 @@ from datetime import datetime import logging -import os.path -import time +from pathlib import Path try: import tomllib # type: ignore (module not available in Python <3.11) @@ -59,9 +58,8 @@ RECOMMENDED_VOICES, ) from util import read_api_key, icelandic_asciify -from queries.sonos import SonosClient -from queries.smartthings import SmartThingsClient -from queries.spotify import SpotifyClient +from queries.extras.sonos import SonosClient +from queries.extras.spotify import SpotifyClient from . import routes, better_jsonify, text_from_request, bool_from_request from . import MAX_URL_LENGTH, MAX_UUID_LENGTH @@ -693,9 +691,7 @@ def register_query_data_api(version: int = 1) -> Response: qdata["client_id"], qdata["key"], qdata["data"], update_in_place=True ) if success: - print("HUE SUCCESS!!!!!!!!!!!!!!") return better_jsonify(valid=True, msg="Query data registered") - return better_jsonify(valid=False, errmsg="Error registering query data.") @@ -735,69 +731,31 @@ def upload_speech_audio(version: int = 1) -> Response: @routes.route("/connect_sonos.api", methods=["GET"]) @routes.route("/connect_sonos.api/v", methods=["GET", "POST"]) -def sonos_code(version: int = 1) -> Response: +def sonos_code(version: int = 1) -> str: """ API endpoint to connect to Sonos speakers """ - print("sonos code") args = request.args client_id = args.get("state") code = args.get("code") - code_dict = { - "iot_speakers": {"sonos": {"credentials": {"code": code}}} - } # create a dictonary with the code + if client_id and code: + code_dict = {"iot_speakers": {"sonos": {"credentials": {"code": code}}}} success = QueryObject.store_query_data( client_id, "iot", code_dict, update_in_place=True ) if success: device_data = code_dict["iot_speakers"] - # Create an instance of the SonosClient class. This will automatically create the rest of the credentials needed. - sonos_client = SonosClient(device_data, client_id) - sonos_voice_clip = ( - f"Hæ! Embla hérna! Ég er búin að tengja þennan Sónos hátalara." - ) - # sonos_client.play_chime() - # time.sleep(1.3) - # sonos_client.play_audio_clip( - # text_to_audio_url(sonos_voice_clip) - # ) # Send the above message to the Sonos speaker + # Create an instance of the SonosClient class. + # This will automatically create the rest of the credentials needed. + SonosClient(device_data, client_id) return render_template("iot-connect-success.html", title="Tenging tókst") - return better_jsonify(valid=True, msg="Registered sonos code") return render_template("iot-connect-error.html", title="Tenging mistókst") - return better_jsonify(valid=False, errmsg="Error registering sonos code.") - - -@routes.route("/connect_smartthings.api", methods=["GET"]) -@routes.route("/connect_smartthings.api/v", methods=["GET", "POST"]) -def smartthings_code(version: int = 1) -> Response: - """ - API endpoint to connect to Sonos speakers - """ - print("smartthings code") - args = request.args - client_id = args.get("state") - code = args.get("code") - code_dict = { - "smartthings": {"credentials": {"code": code}} - } # create a dictonary with the code - if client_id and code: - success = QueryObject.store_query_data( - client_id, "iot_hubs", code_dict, update_in_place=True - ) - if success: - device_data = code_dict - smartthings_client = SmartThingsClient(device_data, client_id) - # device_data = code - # Create an instance of the SonosClient class. This will automatically create the rest of the credentials needed. - return render_template("iot-connect-success.html", title="Tenging tókst") - return better_jsonify(valid=True, msg="Registered smartthings code") - return better_jsonify(valid=False, errmsg="Error registering smartthings code.") @routes.route("/connect_spotify.api", methods=["GET"]) @routes.route("/connect_spotify.api/v", methods=["GET", "POST"]) -def spotify_code(version: int = 1) -> Response: +def spotify_code(version: int = 1) -> str: """ API endpoint to connect Spotify account """ @@ -814,28 +772,12 @@ def spotify_code(version: int = 1) -> Response: ) if success: device_data = code_dict.get("iot_streaming").get("spotify") - spotify_client = SpotifyClient(device_data, client_id) - # Create an instance of the SonosClient class. This will automatically create the rest of the credentials needed. + # Create an instance of the SonosClient class. + # This will automatically create the rest of the credentials needed. + SpotifyClient(device_data, client_id) return render_template("iot-connect-success.html", title="Tenging tókst") - return better_jsonify(valid=True, msg="Registered spotify code") return render_template("iot-connect-error.html", title="Tenging mistókst") - return better_jsonify(valid=False, errmsg="Error registering spotify code.") - - -# def sonos_code2(version: int = 1) -> Response: -# print("sonos code") -# args = request.args -# client_id = args.get("state") -# code = args.get("code") -# code = {"sonos": {"credentials": {"code": code}}} -# if client_id and code: -# success = QueryObject.store_query_data( -# client_id, "iot_speakers", code, update_in_place=True -# ) -# if success: -# return better_jsonify(valid=True, msg="Registered sonos code") -# return better_jsonify(valid=False, errmsg="Error registering sonos code.") # TODO: Finish functionality to delete iot data from database @routes.route("/delete_iot_data.api", methods=["DELETE"]) @@ -893,11 +835,9 @@ def get_supported_iot_connections(version: int = 1) -> Response: client_id: str = args.get("client_id") host: str = args.get("host") - basepath, _ = os.path.split(os.path.realpath(__file__)) - fpath = os.path.join(basepath, "../resources/iot_supported.toml") + fpath = Path(__file__).parent.parent / "resources" / "iot_supported.toml" + f = fpath.read_text() - with open(fpath, mode="r") as file: - f = file.read() # Read TOML file containing a list of resources for the dialogue obj: IotSupportedTOMLStructure = tomllib.loads(f) # type: ignore diff --git a/routes/main.py b/routes/main.py index 48fbf234..4cd1cb28 100755 --- a/routes/main.py +++ b/routes/main.py @@ -25,10 +25,10 @@ from typing_extensions import TypedDict import platform -import os.path import sys import random import json +from pathlib import Path from datetime import datetime try: @@ -110,18 +110,16 @@ class IotSupportedTOMLStructure(TypedDict): @routes.route("/iot/") -@max_age(seconds=60) +@max_age(seconds=300) def iot(device: str): """Handler for device connection views.""" args = request.args iot_name: str = args.get("iot_name") connection_info = {} if iot_name: - basepath, _ = os.path.split(os.path.realpath(__file__)) - fpath = os.path.join(basepath, "../resources/iot_supported.toml") - print("fpath: ", fpath) - with open(fpath, mode="r") as file: - f = file.read() + fpath = Path(__file__).parent.parent / "resources" / "iot_supported.toml" + f = fpath.read_text() + # Read TOML file containing a list of resources for the dialogue obj: IotSupportedTOMLStructure = tomllib.loads(f) # type: ignore if obj: diff --git a/static/img/checkmark.gif:Zone.Identifier b/static/img/checkmark.gif:Zone.Identifier deleted file mode 100644 index c5c3df5c..00000000 --- a/static/img/checkmark.gif:Zone.Identifier +++ /dev/null @@ -1,4 +0,0 @@ -[ZoneTransfer] -ZoneId=3 -ReferrerUrl=https://ezgif.com/ -HostUrl=https://s1.ezgif.com/save/ezgif-1-6202d519b0.gif From 8172e1dd1b3696186736821b57a5c580eafdf93b Mon Sep 17 00:00:00 2001 From: Logi E Date: Mon, 19 Sep 2022 15:48:43 +0000 Subject: [PATCH 356/371] QueryReducer, banned_nonterminals Started implementing SPPF reduction functionality in `query.py`, tailored to reducing parse forests for queries. Modified usage of `banned_nonterminals`. --- queries/fruitseller.py | 20 ++-- queries/pizza.py | 9 +- queries/theater.py | 22 ++-- query.py | 226 +++++++++++++++++++++++------------------ 4 files changed, 147 insertions(+), 130 deletions(-) diff --git a/queries/fruitseller.py b/queries/fruitseller.py index bb46961d..009e1e60 100644 --- a/queries/fruitseller.py +++ b/queries/fruitseller.py @@ -46,28 +46,24 @@ GRAMMAR = read_grammar_file("fruitseller") -def banned_nonterminals(q: Query) -> Set[str]: +def banned_nonterminals(q: Query) -> None: """ Returns a set of nonterminals that are not allowed due to the state of the dialogue """ - banned_nonterminals: set[str] = set() - # TODO: Put this back in when the dsm has access to the active dialogue again. - print("Fruitseller dsm dialogue name: ", q.dsm.dialogue_name) if q.active_dialogue != DIALOGUE_NAME: - banned_nonterminals.add("QFruitSellerQuery") - return banned_nonterminals + q.ban_nonterminal("QFruitSellerQuery") + return resource: Resource = q.dsm.current_resource if resource.name == "Fruits": - banned_nonterminals.add("QFruitDateQuery") + q.ban_nonterminal("QFruitDateQuery") if resource.is_unfulfilled: - banned_nonterminals.add("QFruitYes") - banned_nonterminals.add("QFruitNo") + q.ban_nonterminal("QFruitYes") + q.ban_nonterminal("QFruitNo") elif resource.name == "DateTime": if resource.is_unfulfilled: - banned_nonterminals.add("QFruitYes") - banned_nonterminals.add("QFruitNo") - return banned_nonterminals + q.ban_nonterminal("QFruitYes") + q.ban_nonterminal("QFruitNo") def _generate_fruit_answer( diff --git a/queries/pizza.py b/queries/pizza.py index e66f6014..0c5ed1c3 100644 --- a/queries/pizza.py +++ b/queries/pizza.py @@ -79,19 +79,14 @@ def help_text(lemma: str) -> str: GRAMMAR = read_grammar_file("pizza") -def banned_nonterminals(q: Query) -> Set[str]: +def banned_nonterminals(q: Query) -> None: """ Returns a set of nonterminals that are not allowed due to the state of the dialogue """ # TODO: Implement this - banned_nonterminals: set[str] = set() if q.active_dialogue != DIALOGUE_NAME: - print("Not in pizza dialogue, BANNING QPizzaQuery") - banned_nonterminals.add("QPizzaQuery") - print("Banned nonterminals: ", banned_nonterminals) - return banned_nonterminals - return banned_nonterminals + q.ban_nonterminal("QPizzaQuery") def _generate_order_answer( diff --git a/queries/theater.py b/queries/theater.py index 6b181ebc..5bc41800 100644 --- a/queries/theater.py +++ b/queries/theater.py @@ -84,19 +84,18 @@ def help_text(lemma: str) -> str: GRAMMAR = read_grammar_file("theater") -def banned_nonterminals(q: Query) -> Set[str]: +def banned_nonterminals(q: Query) -> None: """ Returns a set of nonterminals that are not allowed due to the state of the dialogue """ - banned_nonterminals: set[str] = set() # TODO: Put this back in when the dsm has access to the active dialogue again. if q.active_dialogue != DIALOGUE_NAME: - banned_nonterminals.add("QTheaterQuery") - return banned_nonterminals + q.ban_nonterminal("QTheaterQuery") + return resource: Resource = q.dsm.current_resource if resource.name == "Show": - banned_nonterminals.update( + q.ban_nonterminals( { "QTheaterShowDateQuery", "QTheaterMoreDates", @@ -109,14 +108,14 @@ def banned_nonterminals(q: Query) -> Set[str]: } ) if resource.is_unfulfilled: - banned_nonterminals.update( + q.ban_nonterminals( { "QTheaterShowLength", "QTheaterShowPrice", } ) elif resource.name == "ShowDateTime": - banned_nonterminals.update( + q.ban_nonterminals( { "QTheaterShowSeatCountQuery", "QTheaterShowLocationQuery", @@ -126,7 +125,7 @@ def banned_nonterminals(q: Query) -> Set[str]: } ) elif resource.name == "ShowSeatCount": - banned_nonterminals.update( + q.ban_nonterminals( { "QTheaterShowLocationQuery", "QTheaterRowOptions", @@ -137,7 +136,7 @@ def banned_nonterminals(q: Query) -> Set[str]: } ) elif resource.name == "ShowSeatRow": - banned_nonterminals.update( + q.ban_nonterminals( { "QTheaterShowSeats", "QTheaterSeatCountNum", @@ -147,7 +146,7 @@ def banned_nonterminals(q: Query) -> Set[str]: } ) elif resource.name == "ShowSeatNumber": - banned_nonterminals.update( + q.ban_nonterminals( { "QTheaterSeatCountNum", "QTheaterRowNum", @@ -155,13 +154,12 @@ def banned_nonterminals(q: Query) -> Set[str]: } ) if resource.is_unfulfilled: - banned_nonterminals.update( + q.ban_nonterminals( { "QTheaterYes", "QTheaterNo", } ) - return banned_nonterminals class ShowType(TypedDict): diff --git a/query.py b/query.py index 0b0d524a..fbbfdff8 100755 --- a/query.py +++ b/query.py @@ -85,7 +85,7 @@ ) from reynir.bindb import GreynirBin from reynir.fastparser import Node as SPPF_Node -from reynir.grammar import GrammarError, Production +from reynir.grammar import GrammarError, Nonterminal from islenska.bindb import BinFilterFunc from tree import Tree, TreeStateDict, Node, Result @@ -95,7 +95,6 @@ from processor import modules_in_dir from geo import LatLonTuple -from util import merge_two_dicts # Query response ResponseDict = Dict[str, Any] @@ -233,79 +232,11 @@ def grammar_generator() -> Iterator[str]: raise GrammarError("Unable to open or read grammar file", fname, 0) -class QueryReductionScope: - - """Class to accumulate information about a nonterminal and its - child productions during reduction""" - - def __init__(self, reducer: "QueryParseForestReducer", node: SPPF_Node) -> None: - self.reducer = reducer - # Child tree scores - self.sc: ChildDict = defaultdict(lambda: {"sc": 0}) - # Verb/preposition matching stuff - self.pushed_prep_bonus = False - - def start_family(self, ix: int, prod: Production) -> None: - """Start the processing of a production (numbered ix) of a nonterminal""" - # Initialize the score of this family of children, so that productions - # with higher priorities (more negative prio values) get a starting bonus - self.sc[ix]["sc"] = -10 * prod.priority - - def add_child(self, ix: int, rd: ResultDict) -> None: - """Add a child node's score to the parent family's score, - where the parent family has index ix (0..n)""" - d = self.sc[ix] - assert "sc" in d - d["sc"] += rd.get("sc", 0) - # Carry information about contained verbs ("so") up the tree - for key in ("so", "sl"): - if key in rd: - if key in d: - d[key].extend(rd[key]) # type: ignore - else: - d[key] = rd[key][:] # type: ignore - - def process(self, node: SPPF_Node) -> ResultDict: - """After accumulating scores for all possible productions - of this nonterminal (families of children), find the - highest scoring one and reduce the tree to that child only""" +class BannedForestException(Exception): + ... - csc = self.sc - if not csc: - # Empty node - return NULL_SC - nt = node.nonterminal if node.is_completed else None - - if len(csc) == 1: - # Not ambiguous: only one result, do a shortcut - # Will raise an exception if not exactly one value - [sc] = csc.values() - else: - # Eliminate all families except the best scoring one - # Sort in decreasing order by score, using the family index - # as a tie-breaker for determinism - s = sorted(csc.items(), key=lambda x: (x[1]["sc"], -x[0]), reverse=True) - # This is the best scoring family - # (and the one with the lowest index - # if there are many with the same score) - ix, sc = s[0] - # If the node nonterminal is marked as "no_reduce", - # we leave the child families in place. This feature - # is used in query processing. - if nt is None or not nt.no_reduce: - # And now for the key action of the reducer: - # Eliminate all other families - node.reduce_to(ix) - - if nt is not None: - # We will be adjusting the result: make sure we do so on - # a separate dict copy (we don't want to clobber the child's dict) - # Get score adjustment for this nonterminal, if any - # (This is the $score(+/-N) pragma from Greynir.grammar) - sc["sc"] += self.reducer._score_adj.get(nt, 0) - - return sc +_BANNED_RD: ResultDict = cast(ResultDict, {"sc": 0, "ban": True}) class QueryParseForestReducer(ParseForestReducer): @@ -317,7 +248,6 @@ def go(self, root_node: SPPF_Node) -> ResultDict: """Perform the reduction""" # TODO: # - Less greedy Nl, prefer optional nts - # - Banned/reduced score for nts # Memoization/caching dict, keyed by node and memoization key visited: Dict[KeyTuple, ResultDict] = dict() @@ -337,6 +267,12 @@ def calc_score(w: SPPF_Node) -> ResultDict: if v is not None: # Yes: return the previously calculated result return v + + # Is this nonterminal banned for this specific query? + if w.nonterminal and self._q.is_nonterminal_banned(w.nonterminal): + # Yes: return ResultDict with ban set to True + return _BANNED_RD + # We have not seen this (node, current_key) combination before: # reduce it, calculate its score and memoize it if w._token is not None: @@ -344,23 +280,82 @@ def calc_score(w: SPPF_Node) -> ResultDict: v = self.visit_token(w) elif w.is_span and w._families: # We have a nonempty nonterminal node with one or more families - # of children, i.e. multiple possible derivations: - # Init container for family results - scope = QueryReductionScope(self, w) + # of children, i.e. multiple possible derivations + child_scores: ChildDict = defaultdict(lambda: {"sc": 0}) # Go through each family and calculate its score - for family_ix, (prod, children) in enumerate(w._families): - scope.start_family(family_ix, prod) + for fam_ix, (prod, children) in enumerate(w._families): + # Initialize the score of this family of children, so that productions + # with higher priorities (more negative prio values) get a starting bonus + child_scores[fam_ix]["sc"] = -10 * prod.priority + # TODO: Is this ^^^ needed? for ch in children: if ch is not None: - scope.add_child(family_ix, calc_score(ch)) - # Return a dict describing the winning family of children - # (derivation) including an "sc" field for its score. - # !!! TODO: We might be pruning the parse forest too - # !!! early here - there could be a different verb scope - # !!! above this node that would cause a different child - # !!! to be culled. However a test case to demonstrate this - # !!! has yet to be identified/created. - v = scope.process(w) + rd = calc_score(ch) + d = child_scores[fam_ix] + d["sc"] += rd["sc"] + if "ban" in rd: + d["ban"] = rd["ban"] + # TODO: Is this needed? + # Carry information about contained verbs ("so") up the tree + for key in ("so", "sl"): + if key in rd: + if key in d: + d[key].extend(rd[key]) # type: ignore + else: + d[key] = rd[key][:] # type: ignore + + if not child_scores: + # Empty node + return NULL_SC + + nt: Optional[Nonterminal] = w.nonterminal if w.is_completed else None + if len(child_scores) == 1: + # Not ambiguous: only one result, do a shortcut + # Will raise an exception if not exactly one value + [v] = child_scores.values() + else: + # Eliminate all families except the best scoring one + # Sort by ban-status (non-banned first), + # then score in decreasing order, + # and then using the family index + # as a tie-breaker for determinism + s = sorted( + child_scores.items(), + key=lambda x: ( + 0 if x[1].get("ban") else 1, + x[1]["sc"], + -x[0], + ), + reverse=True, + ) + + if not s[0][1].get("ban") and s[-1][1].get("ban"): + # We have a blend of non-banned and banned families, + # prune the banned ones (even for no-reduce nonterminals) + w._families = [ + w._families[x[0]] for x in s if not x[1].get("ban") + ] + + # This is the best scoring family + # (and the one with the lowest index + # if there are many with the same score) + ix, v = s[0] + + # If the node nonterminal is marked as "no_reduce", + # we leave the child families in place. This feature + # is used in query processing. + if nt is None or not nt.no_reduce: + # And now for the key action of the reducer: + # Eliminate all other families + w.reduce_to(ix) + + if nt is not None: + # We will be adjusting the result: make sure we do so on + # a separate dict copy (we don't want to clobber the child's dict) + # Get score adjustment for this nonterminal, if any + # (This is the $score(+/-N) pragma from Greynir.grammar) + v["sc"] += self._score_adj.get(nt, 0) + # The winning family is now the only remaining family # of children of this node; the others have been culled. else: @@ -373,7 +368,13 @@ def calc_score(w: SPPF_Node) -> ResultDict: # Start the scoring and reduction process at the root if root_node is None: return NULL_SC - return calc_score(root_node) + + root_score = calc_score(root_node) + if "ban" in root_score: + # Best family is banned, which means that + # no non-banned families were found + raise BannedForestException("Entire parse forest for this query is banned") + return root_score class QueryReducer(Reducer): @@ -400,7 +401,7 @@ class QueryParser(Fast_Parser): # QueryParser. This Python sleight-of-hand overrides # class attributes that are defined in BIN_Parser, see binparser.py. _grammar_ts: Optional[float] = None - _grammar = None + _grammar: Optional[QueryGrammar] = None _grammar_class = QueryGrammar # Also keep separate class instances of the C grammar and its timestamp @@ -579,6 +580,10 @@ def __init__( # Query context, which is None until fetched via self.fetch_context() # This should be a dict that can be represented in JSON self._context: Optional[ContextDict] = None + # Banned nonterminals for this query, + # dynamically generated from query modules + self._banned_nonterminals: Set[str] = set() + # TODO: Simplify this # Dialogue state manager and dialogue data, used for dialogue modules # The name of the active dialogue, None if no dialogue is active active_dialogue_dict: Optional[DialogueDataDict] = self.client_data( @@ -724,10 +729,7 @@ def _parse(self, toklist: Iterable[Tok]) -> Tuple[ResponseDict, Dict[int, str]]: if num > 1: # Reduce the resulting forest forest = rdc.go(forest) - if forest is None: - # In case the entire forest was pruned - num = 0 - except ParseError: + except (ParseError, BannedForestException): forest = None num = 0 if num > 0: @@ -742,7 +744,6 @@ def _parse(self, toklist: Iterable[Tok]) -> Tuple[ResponseDict, Dict[int, str]]: pass else: sent.append(t) - result: ResponseDict = dict(num_sent=num_sent, num_parsed_sent=num_parsed_sent) return result, trees @@ -793,11 +794,12 @@ def parse(self, result: ResponseDict) -> bool: # Log the query string as seen by the parser print("Query is: '{0}'".format(actual_q)) - banned_nonterminals: Set[str] = set() + # Ban certain nonterminals for this query for t in Query._dialogue_processors: - f = getattr(t, "banned_nonterminals", None) - if f is not None: - banned_nonterminals.update(f(self)) + ban_func = getattr(t, "banned_nonterminals", None) + if ban_func is not None: + ban_func(self) + try: parse_result, trees = self._parse(toklist) except ParseError: @@ -1128,6 +1130,32 @@ def qtype(self) -> Optional[str]: """Return the query type""" return self._qtype + def ban_nonterminal(self, nonterminal: str) -> None: + """ + Add a nonterminal to the set of + banned nonterminals for this query. + """ + assert nonterminal in self._parser._grammar.nonterminals, ( + f"ban_nonterminal: {nonterminal} doesn't " + "correspond to a nonterminal in the grammar" + ) + self._banned_nonterminals.add(nonterminal) + + def ban_nonterminals(self, nonterminals: Set[str]) -> None: + """ + Add a set of nonterminals to the set of + banned nonterminals for this query. + """ + diff = nonterminals.difference(self._parser._grammar.nonterminals) + assert len(diff) == 0, ( + f"ban_nonterminals: nonterminals {diff} don't" + "correspond to a nonterminal in the grammar" + ) + self._banned_nonterminals.update(nonterminals) + + def is_nonterminal_banned(self, nt: Nonterminal) -> bool: + return str(nt) in self._banned_nonterminals + def set_qtype(self, qtype: str) -> None: """Set the query type ('Person', 'Title', 'Company', 'Entity'...)""" self._qtype = qtype From 69306445a83fb501a1fd64ee63d162b6839f6274 Mon Sep 17 00:00:00 2001 From: Logi E Date: Tue, 20 Sep 2022 13:23:39 +0000 Subject: [PATCH 357/371] QueryReducer complete --- queries/iot_connect.py | 3 ++- queries/iot_hue.py | 7 ++++--- query.py | 23 ++++++++++++----------- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/queries/iot_connect.py b/queries/iot_connect.py index d7ba03a5..fc2f54ba 100644 --- a/queries/iot_connect.py +++ b/queries/iot_connect.py @@ -27,6 +27,7 @@ import random import flask +from pathlib import Path from query import Query, QueryStateDict from queries import gen_answer, read_jsfile @@ -138,7 +139,7 @@ def sentence(state: QueryStateDict, result: Result) -> None: assert client_id is not None if result.qtype == "connect_lights": - js = read_jsfile("Philips_Hue/hub.js") + js = read_jsfile(str(Path("Philips_Hue", "hub.js"))) js += f"return connectHub('{client_id}','{host}');" q.set_answer(*gen_answer("Philips Hue miðstöðin hefur verið tengd.")) q.set_command(js) diff --git a/queries/iot_hue.py b/queries/iot_hue.py index ee60c59a..34c0cf6f 100755 --- a/queries/iot_hue.py +++ b/queries/iot_hue.py @@ -41,6 +41,7 @@ import logging import random import json +from pathlib import Path from query import Query, QueryStateDict from queries import gen_answer, read_jsfile, read_grammar_file @@ -367,10 +368,10 @@ def sentence(state: QueryStateDict, result: Result) -> None: '', ) js = ( - read_jsfile("Libraries/fuse.js") + read_jsfile(str(Path("Libraries", "fuse.js"))) + f"var BRIDGE_IP = '{bridge_ip}';var USERNAME = '{username}';" - + read_jsfile("Philips_Hue/fuse_search.js") - + read_jsfile("Philips_Hue/set_lights.js") + + read_jsfile(str(Path("Philips_Hue", "fuse_search.js"))) + + read_jsfile(str(Path("Philips_Hue", "set_lights.js"))) ) js += f"return setLights('{light_or_group_name}', '{json.dumps(result.hue_obj)}');" q.set_command(js) diff --git a/query.py b/query.py index fbbfdff8..008c35e8 100755 --- a/query.py +++ b/query.py @@ -53,6 +53,7 @@ import re import random from collections import defaultdict +from itertools import takewhile from queries.extras.dialogue import ( DialogueStateManager as DSM, ResourceNotFoundError, @@ -246,9 +247,6 @@ def __init__(self, grammar: BIN_Grammar, scores: ScoreDict, query: "Query"): def go(self, root_node: SPPF_Node) -> ResultDict: """Perform the reduction""" - # TODO: - # - Less greedy Nl, prefer optional nts - # Memoization/caching dict, keyed by node and memoization key visited: Dict[KeyTuple, ResultDict] = dict() # Current memoization key @@ -287,15 +285,13 @@ def calc_score(w: SPPF_Node) -> ResultDict: # Initialize the score of this family of children, so that productions # with higher priorities (more negative prio values) get a starting bonus child_scores[fam_ix]["sc"] = -10 * prod.priority - # TODO: Is this ^^^ needed? for ch in children: if ch is not None: rd = calc_score(ch) d = child_scores[fam_ix] d["sc"] += rd["sc"] - if "ban" in rd: - d["ban"] = rd["ban"] - # TODO: Is this needed? + if "ban" in rd: # Carry ban status up the tree + d["ban"] = rd["ban"] # type: ignore # Carry information about contained verbs ("so") up the tree for key in ("so", "sl"): if key in rd: @@ -322,9 +318,9 @@ def calc_score(w: SPPF_Node) -> ResultDict: s = sorted( child_scores.items(), key=lambda x: ( - 0 if x[1].get("ban") else 1, - x[1]["sc"], - -x[0], + 0 if x[1].get("ban") else 1, # Non-banned first + x[1]["sc"], # Score (descending) + -x[0], # Index (for determinism) ), reverse=True, ) @@ -333,7 +329,8 @@ def calc_score(w: SPPF_Node) -> ResultDict: # We have a blend of non-banned and banned families, # prune the banned ones (even for no-reduce nonterminals) w._families = [ - w._families[x[0]] for x in s if not x[1].get("ban") + w._families[x[0]] + for x in takewhile(lambda y: not y[1].get("ban"), s) ] # This is the best scoring family @@ -341,6 +338,10 @@ def calc_score(w: SPPF_Node) -> ResultDict: # if there are many with the same score) ix, v = s[0] + # Note: at this point the best scoring family + # might be banned or not, we deal with this issue + # when returning the root score from go() + # If the node nonterminal is marked as "no_reduce", # we leave the child families in place. This feature # is used in query processing. From 03576e15f0bed8289a3e0a1600113a7b234e3f3a Mon Sep 17 00:00:00 2001 From: Logi E Date: Tue, 20 Sep 2022 13:41:47 +0000 Subject: [PATCH 358/371] Use pathlib, more succint --- queries/__init__.py | 24 ++++++++---------------- util.py | 19 +++---------------- 2 files changed, 11 insertions(+), 32 deletions(-) diff --git a/queries/__init__.py b/queries/__init__.py index c1c08069..8fa1f69e 100755 --- a/queries/__init__.py +++ b/queries/__init__.py @@ -41,9 +41,9 @@ import logging import requests import json -import os import re import locale +from pathlib import Path from urllib.parse import urlencode from functools import lru_cache @@ -668,10 +668,8 @@ def read_jsfile(filename: str) -> str: # containing this __init__.py file from rjsmin import jsmin # type: ignore - basepath, _ = os.path.split(os.path.realpath(__file__)) - fpath = os.path.join(basepath, "js", filename) - with open(fpath, mode="r") as file: - return cast(str, jsmin(file.read())) + jsfile = Path(__file__).parent.resolve() / "js" / filename + return cast(str, jsmin(jsfile.read_text())) def read_grammar_file(filename: str, **format_kwargs: str) -> str: @@ -679,12 +677,9 @@ def read_grammar_file(filename: str, **format_kwargs: str) -> str: Read and return a grammar file from the 'grammars' folder. Optionally specify keyword arguments for str.format() call """ - basepath, _ = os.path.split(os.path.realpath(__file__)) - fpath = os.path.join(basepath, "grammars", filename + ".grammar") + gfile = Path(__file__).parent.resolve() / "grammars" / f"{filename}.grammar" - grammar = "" - with open(fpath, mode="r") as file: - grammar = file.read() + grammar = gfile.read_text() if len(format_kwargs) > 0: grammar = grammar.format(**format_kwargs) return grammar @@ -695,12 +690,9 @@ def join_grammar_files(folder: str) -> str: Given a folder name, return a string containing the contents of all '.grammar' files within the folder """ - basepath, _ = os.path.split(os.path.realpath(__file__)) - fpath = os.path.join(basepath, folder) + gdir = Path(__file__).parent.resolve() / folder grammar: List[str] = [] - for fname in os.listdir(fpath): - if fname.endswith(".grammar"): - with open(os.path.join(fpath, fname), mode="r") as file: - grammar.append(file.read()) + for gfile in gdir.glob("*.grammar"): + grammar.append(gfile.read_text()) return "\n".join(grammar) diff --git a/util.py b/util.py index 115eff08..fb9327e4 100755 --- a/util.py +++ b/util.py @@ -21,17 +21,16 @@ """ -import os +from pathlib import Path from functools import lru_cache @lru_cache(maxsize=32) def read_api_key(key_name: str) -> str: """Read the given key from a text file in resources directory. Cached.""" - path = os.path.join(os.path.dirname(__file__), "resources", key_name + ".txt") + api_file = Path(__file__).parent.resolve() / "resources" / f"{key_name}.txt" try: - with open(path) as f: - return f.read().strip() + return api_file.read_text().strip() except FileNotFoundError: pass return "" @@ -73,15 +72,3 @@ def icelandic_asciify(text: str) -> str: t = t.encode("ascii", "ignore").decode() return t - - -def merge_two_dicts(dict_a, dict_b): - for key in dict_b: - if key in dict_a: - if isinstance(dict_a[key], dict) and isinstance(dict_b[key], dict): - merge_two_dicts(dict_a[key], dict_b[key]) - else: - dict_a[key] = dict_b[key] - else: - dict_a[key] = dict_b[key] - return dict_a From b9ef96a7a925d2ef5b74edcdce8394e10acf89d9 Mon Sep 17 00:00:00 2001 From: Logi E Date: Tue, 20 Sep 2022 15:49:51 +0000 Subject: [PATCH 359/371] Fix QueryStateDict->ParamList type error in query modules --- queries/arithmetic.py | 60 ++++++++++++------------ queries/builtin.py | 40 ++++++++-------- queries/bus.py | 30 ++++++------ queries/counting.py | 16 +++---- queries/currency.py | 24 +++++----- queries/date.py | 92 ++++++++++++++++++------------------- queries/dictionary.py | 6 +-- queries/examples/grammar.py | 8 ++-- queries/flights.py | 12 ++--- queries/geography.py | 14 +++--- queries/iot_connect.py | 10 ++-- queries/iot_hue.py | 46 +++++++++---------- queries/iot_speakers.py | 86 +++++++++++++++++----------------- queries/ja.py | 10 ++-- queries/news.py | 4 +- queries/opinion.py | 6 +-- queries/petrol.py | 10 ++-- queries/pic.py | 8 ++-- queries/pizza.py | 47 +++++++++---------- queries/places.py | 15 +++--- queries/rand.py | 14 +++--- queries/sunpos.py | 34 +++++++------- queries/theater.py | 54 +++++++++++----------- queries/unit.py | 16 +++---- queries/userloc.py | 12 ++--- queries/weather.py | 27 ++++++----- queries/wiki.py | 14 +++--- queries/yulelads.py | 32 ++++++------- 28 files changed, 364 insertions(+), 383 deletions(-) diff --git a/queries/arithmetic.py b/queries/arithmetic.py index 3b9c6caf..1c2680f6 100755 --- a/queries/arithmetic.py +++ b/queries/arithmetic.py @@ -35,7 +35,7 @@ from query import AnswerTuple, ContextDict, Query, QueryStateDict from queries import iceformat_float, gen_answer, read_grammar_file -from tree import Result, Node, TerminalNode +from tree import Result, Node, TerminalNode, ParamList from queries.num import floats_to_text, numbers_to_text @@ -302,7 +302,7 @@ def terminal_num(t: Optional[Result]) -> Optional[Union[str, int, float]]: return None -def QArNumberWord(node: Node, params: QueryStateDict, result: Result) -> None: +def QArNumberWord(node: Node, params: ParamList, result: Result) -> None: result._canonical = result._text if "context_reference" in result or "error_context_reference" in result: # Already pushed the context reference @@ -315,11 +315,11 @@ def QArNumberWord(node: Node, params: QueryStateDict, result: Result) -> None: add_num(result._nominative, result) -def QArOrdinalWord(node: Node, params: QueryStateDict, result: Result) -> None: +def QArOrdinalWord(node: Node, params: ParamList, result: Result) -> None: add_num(result._canonical, result) -def QArFractionWord(node: Node, params: QueryStateDict, result: Result) -> None: +def QArFractionWord(node: Node, params: ParamList, result: Result) -> None: fn = result._canonical.lower() fp = _FRACTION_WORDS.get(fn) if not fp: @@ -330,13 +330,13 @@ def QArFractionWord(node: Node, params: QueryStateDict, result: Result) -> None: add_num(fp, result) -def QArMultOperator(node: Node, params: QueryStateDict, result: Result) -> None: +def QArMultOperator(node: Node, params: ParamList, result: Result) -> None: """'tvisvar_sinnum', 'þrisvar_sinnum', 'fjórum_sinnum'""" add_num(result._nominative, result) result.operator = "multiply" -def QArLastResult(node: Node, params: QueryStateDict, result: Result) -> None: +def QArLastResult(node: Node, params: ParamList, result: Result) -> None: """Reference to previous result, usually via the words 'það' or 'því' ('Hvað er það sinnum sautján?')""" q = result.state.get("query") @@ -350,45 +350,43 @@ def QArLastResult(node: Node, params: QueryStateDict, result: Result) -> None: result.context_reference = True -def QArPlusOperator(node: Node, params: QueryStateDict, result: Result) -> None: +def QArPlusOperator(node: Node, params: ParamList, result: Result) -> None: result.operator = "plus" -def QArSumOperator(node: Node, params: QueryStateDict, result: Result) -> None: +def QArSumOperator(node: Node, params: ParamList, result: Result) -> None: result.operator = "plus" -def QArMinusOperator(node: Node, params: QueryStateDict, result: Result) -> None: +def QArMinusOperator(node: Node, params: ParamList, result: Result) -> None: result.operator = "minus" -def QArDivisionOperator(node: Node, params: QueryStateDict, result: Result) -> None: +def QArDivisionOperator(node: Node, params: ParamList, result: Result) -> None: result.operator = "divide" -def QArMultiplicationOperator( - node: Node, params: QueryStateDict, result: Result -) -> None: +def QArMultiplicationOperator(node: Node, params: ParamList, result: Result) -> None: result.operator = "multiply" -def QArSquareRootOperator(node: Node, params: QueryStateDict, result: Result) -> None: +def QArSquareRootOperator(node: Node, params: ParamList, result: Result) -> None: result.operator = "sqrt" -def QArPowOperator(node: Node, params: QueryStateDict, result: Result) -> None: +def QArPowOperator(node: Node, params: ParamList, result: Result) -> None: result.operator = "pow" -def QArPercentOperator(node: Node, params: QueryStateDict, result: Result) -> None: +def QArPercentOperator(node: Node, params: ParamList, result: Result) -> None: result.operator = "percent" -def QArFractionOperator(node: Node, params: QueryStateDict, result: Result) -> None: +def QArFractionOperator(node: Node, params: ParamList, result: Result) -> None: result.operator = "fraction" -def Prósenta(node: Node, params: QueryStateDict, result: Result) -> None: +def Prósenta(node: Node, params: ParamList, result: Result) -> None: # Find percentage terminal d = result.find_descendant(t_base="prósenta") if d: @@ -398,7 +396,7 @@ def Prósenta(node: Node, params: QueryStateDict, result: Result) -> None: raise ValueError("No auxiliary information in percentage token") -def QArCurrencyOrNum(node: Node, params: QueryStateDict, result: Result) -> None: +def QArCurrencyOrNum(node: Node, params: ParamList, result: Result) -> None: amount: Optional[Node] = node.first_child(lambda n: n.has_t_base("amount")) if amount is not None: # Found an amount terminal node @@ -408,7 +406,7 @@ def QArCurrencyOrNum(node: Node, params: QueryStateDict, result: Result) -> None add_num(result.amount, result) -def QArStd(node: Node, params: QueryStateDict, result: Result) -> None: +def QArStd(node: Node, params: ParamList, result: Result) -> None: # Used later for formatting voice answer string, # e.g. "[tveir plús tveir] er [fjórir]" result.desc = ( @@ -419,48 +417,48 @@ def QArStd(node: Node, params: QueryStateDict, result: Result) -> None: ) -def QArSum(node: Node, params: QueryStateDict, result: Result) -> None: +def QArSum(node: Node, params: ParamList, result: Result) -> None: result.desc = result._canonical -def QArMult(node: Node, params: QueryStateDict, result: Result) -> None: +def QArMult(node: Node, params: ParamList, result: Result) -> None: result.desc = result._canonical -def QArSqrt(node: Node, params: QueryStateDict, result: Result) -> None: +def QArSqrt(node: Node, params: ParamList, result: Result) -> None: result.desc = result._canonical -def QArPow(node: Node, params: QueryStateDict, result: Result) -> None: +def QArPow(node: Node, params: ParamList, result: Result) -> None: result.desc = result._canonical -def QArPercent(node: Node, params: QueryStateDict, result: Result) -> None: +def QArPercent(node: Node, params: ParamList, result: Result) -> None: result.desc = result._canonical -def QArFraction(node: Node, params: QueryStateDict, result: Result) -> None: +def QArFraction(node: Node, params: ParamList, result: Result) -> None: result.desc = result._canonical -def QArPiQuery(node: Node, params: QueryStateDict, result: Result) -> None: +def QArPiQuery(node: Node, params: ParamList, result: Result) -> None: result.qtype = "PI" -def QArWithVAT(node: Node, params: QueryStateDict, result: Result) -> None: +def QArWithVAT(node: Node, params: ParamList, result: Result) -> None: result.operator = "with_vat" -def QArWithoutVAT(node: Node, params: QueryStateDict, result: Result) -> None: +def QArWithoutVAT(node: Node, params: ParamList, result: Result) -> None: result.operator = "without_vat" -def QArVAT(node: Node, params: QueryStateDict, result: Result) -> None: +def QArVAT(node: Node, params: ParamList, result: Result) -> None: result.desc = result._canonical result.qtype = "VSK" -def QArithmetic(node: Node, params: QueryStateDict, result: Result) -> None: +def QArithmetic(node: Node, params: ParamList, result: Result) -> None: # Set query type result.qtype = _ARITHMETIC_QTYPE diff --git a/queries/builtin.py b/queries/builtin.py index a36bee5e..f7b8cd9a 100755 --- a/queries/builtin.py +++ b/queries/builtin.py @@ -45,7 +45,7 @@ from search import Search from query import AnswerTuple, Query, ResponseDict, ResponseType, QueryStateDict from queries import read_grammar_file -from tree import Result, Node +from tree import Result, Node, ParamList from queries import cap_first @@ -892,7 +892,7 @@ def sentence(state: QueryStateDict, result: Result) -> None: # and are called during tree processing (depth-first, i.e. bottom-up navigation) -def QPerson(node: Node, params: QueryStateDict, result: Result) -> None: +def QPerson(node: Node, params: ParamList, result: Result) -> None: """Person query""" result.qtype = "Person" if "mannsnafn" in result: @@ -905,87 +905,87 @@ def QPerson(node: Node, params: QueryStateDict, result: Result) -> None: assert False -def QPersonPronoun(node: Node, params: QueryStateDict, result: Result) -> None: +def QPersonPronoun(node: Node, params: ParamList, result: Result) -> None: """Persónufornafn: hann, hún, það""" result.persónufornafn = result._nominative -def QCompany(node: Node, params: QueryStateDict, result: Result) -> None: +def QCompany(node: Node, params: ParamList, result: Result) -> None: result.qtype = "Company" result.qkey = result.fyrirtæki -def QEntity(node: Node, params: QueryStateDict, result: Result) -> None: +def QEntity(node: Node, params: ParamList, result: Result) -> None: result.qtype = "Entity" assert "qkey" in result -def QTitle(node: Node, params: QueryStateDict, result: Result) -> None: +def QTitle(node: Node, params: ParamList, result: Result) -> None: result.qtype = "Title" result.qkey = result.titill -def QWord(node: Node, params: QueryStateDict, result: Result) -> None: +def QWord(node: Node, params: ParamList, result: Result) -> None: result.qtype = "Word" assert "qkey" in result -def QSearch(node: Node, params: QueryStateDict, result: Result) -> None: +def QSearch(node: Node, params: ParamList, result: Result) -> None: result.qtype = "Search" # Return the entire query text as the search key result.qkey = result._text -def QRepeat(node: Node, params: QueryStateDict, result: Result) -> None: +def QRepeat(node: Node, params: ParamList, result: Result) -> None: """Request to repeat the last query answer""" result.qkey = "" result.qtype = "Repeat" -def Sérnafn(node: Node, params: QueryStateDict, result: Result) -> None: +def Sérnafn(node: Node, params: ParamList, result: Result) -> None: """Sérnafn, stutt eða langt""" result.sérnafn = result._nominative -def Fyrirtæki(node: Node, params: QueryStateDict, result: Result) -> None: +def Fyrirtæki(node: Node, params: ParamList, result: Result) -> None: """Fyrirtækisnafn, þ.e. sérnafn + ehf./hf./Inc. o.s.frv.""" result.fyrirtæki = result._nominative -def Mannsnafn(node: Node, params: QueryStateDict, result: Result) -> None: +def Mannsnafn(node: Node, params: ParamList, result: Result) -> None: """Hreint mannsnafn, þ.e. án ávarps og titils""" result.mannsnafn = result._nominative -def EfLiður(node: Node, params: QueryStateDict, result: Result) -> None: +def EfLiður(node: Node, params: ParamList, result: Result) -> None: """Eignarfallsliðir haldast óbreyttir, þ.e. þeim á ekki að breyta í nefnifall""" result._nominative = result._text -def FsMeðFallstjórn(node: Node, params: QueryStateDict, result: Result) -> None: +def FsMeðFallstjórn(node: Node, params: ParamList, result: Result) -> None: """Forsetningarliðir haldast óbreyttir, þ.e. þeim á ekki að breyta í nefnifall""" result._nominative = result._text -def QEntityKey(node: Node, params: QueryStateDict, result: Result) -> None: +def QEntityKey(node: Node, params: ParamList, result: Result) -> None: if "sérnafn" in result: result.qkey = result.sérnafn else: result.qkey = result._nominative -def QTitleKey(node: Node, params: QueryStateDict, result: Result) -> None: +def QTitleKey(node: Node, params: ParamList, result: Result) -> None: """Titill""" result.titill = result._nominative -def QWordNounKey(node: Node, params: QueryStateDict, result: Result) -> None: +def QWordNounKey(node: Node, params: ParamList, result: Result) -> None: result.qkey = result._canonical -def QWordPersonKey(node: Node, params: QueryStateDict, result: Result) -> None: +def QWordPersonKey(node: Node, params: ParamList, result: Result) -> None: if "mannsnafn" in result: result.qkey = result.mannsnafn elif "sérnafn" in result: @@ -994,9 +994,9 @@ def QWordPersonKey(node: Node, params: QueryStateDict, result: Result) -> None: result.qkey = result._nominative -def QWordEntityKey(node: Node, params: QueryStateDict, result: Result) -> None: +def QWordEntityKey(node: Node, params: ParamList, result: Result) -> None: result.qkey = result._nominative -def QWordVerbKey(node: Node, params: QueryStateDict, result: Result) -> None: +def QWordVerbKey(node: Node, params: ParamList, result: Result) -> None: result.qkey = result._root diff --git a/queries/bus.py b/queries/bus.py index 8b2b4c44..b47c456a 100755 --- a/queries/bus.py +++ b/queries/bus.py @@ -48,7 +48,7 @@ import query from query import AnswerTuple, Query, QueryStateDict, ResponseType, Session -from tree import Result, Node +from tree import Result, Node, ParamList from queries import natlang_seq, cap_first, gen_answer, read_grammar_file, sing_or_plur from queries.num import floats_to_text, numbers_to_text from settings import Settings @@ -142,44 +142,44 @@ def help_text(lemma: str) -> str: # tree processing (depth-first, i.e. bottom-up navigation). -def QBusNearestStop(node: Node, params: QueryStateDict, result: Result) -> None: +def QBusNearestStop(node: Node, params: ParamList, result: Result) -> None: """Nearest stop query""" result.qtype = "NearestStop" # No query key in this case result.qkey = "" -def QBusStop(node: Node, params: QueryStateDict, result: Result) -> None: +def QBusStop(node: Node, params: ParamList, result: Result) -> None: """Save the word that was used to describe a bus stop""" result.stop_word = _WRONG_STOP_WORDS.get(result._nominative, result._nominative) -def QBusStopName(node: Node, params: QueryStateDict, result: Result) -> None: +def QBusStopName(node: Node, params: ParamList, result: Result) -> None: """Save the bus stop name""" result.stop_name = result._nominative -def QBusStopThere(node: Node, params: QueryStateDict, result: Result) -> None: +def QBusStopThere(node: Node, params: ParamList, result: Result) -> None: """A reference to a bus stop mentioned earlier""" result.stop_name = "þar" -def QBusStopToThere(node: Node, params: QueryStateDict, result: Result) -> None: +def QBusStopToThere(node: Node, params: ParamList, result: Result) -> None: """A reference to a bus stop mentioned earlier""" result.stop_name = "þangað" -def EfLiður(node: Node, params: QueryStateDict, result: Result) -> None: +def EfLiður(node: Node, params: ParamList, result: Result) -> None: """Don't change the case of possessive clauses""" result._nominative = result._text -def FsMeðFallstjórn(node: Node, params: QueryStateDict, result: Result) -> None: +def FsMeðFallstjórn(node: Node, params: ParamList, result: Result) -> None: """Don't change the case of prepositional clauses""" result._nominative = result._text -def QBusNoun(node: Node, params: QueryStateDict, result: Result) -> None: +def QBusNoun(node: Node, params: ParamList, result: Result) -> None: """Save the noun used to refer to a bus""" # Use singular, indefinite form # Hack: if the QBusNoun is a literal string, the _canonical logic @@ -191,7 +191,7 @@ def QBusNoun(node: Node, params: QueryStateDict, result: Result) -> None: result.bus_noun = result._canonical -def QBusArrivalTime(node: Node, params: QueryStateDict, result: Result) -> None: +def QBusArrivalTime(node: Node, params: ParamList, result: Result) -> None: """Bus arrival time query""" # Set the query type result.qtype = "ArrivalTime" @@ -202,7 +202,7 @@ def QBusArrivalTime(node: Node, params: QueryStateDict, result: Result) -> None: result.qkey = result.bus_number -def QBusAnyArrivalTime(node: Node, params: QueryStateDict, result: Result) -> None: +def QBusAnyArrivalTime(node: Node, params: ParamList, result: Result) -> None: """Bus arrival time query""" # Set the query type result.qtype = "ArrivalTime" @@ -210,7 +210,7 @@ def QBusAnyArrivalTime(node: Node, params: QueryStateDict, result: Result) -> No result.qkey = result.bus_number = "Any" -def QBusWhich(node: Node, params: QueryStateDict, result: Result) -> None: +def QBusWhich(node: Node, params: ParamList, result: Result) -> None: """Buses on which routes stop at a given stop""" # Set the query type result.qtype = "WhichRoute" @@ -280,7 +280,7 @@ def QBusWhich(node: Node, params: QueryStateDict, result: Result) -> None: } -def QBusWord(node: Node, params: QueryStateDict, result: Result) -> None: +def QBusWord(node: Node, params: ParamList, result: Result) -> None: """Handle buses specified in single words, such as 'tvisturinn' or 'fimman'""" # Retrieve the contained text (in nominative case) @@ -295,7 +295,7 @@ def QBusWord(node: Node, params: QueryStateDict, result: Result) -> None: result.bus_number = _BUS_WORDS.get(result._canonical, 0) -def QBusNumber(node: Node, params: QueryStateDict, result: Result) -> None: +def QBusNumber(node: Node, params: ParamList, result: Result) -> None: """Reflect back the phrase used to specify the bus, but in nominative case.""" # 'vagni númer 17' -> 'vagn númer 17' @@ -303,7 +303,7 @@ def QBusNumber(node: Node, params: QueryStateDict, result: Result) -> None: result.bus_name = result._nominative -def QBusNumberWord(node: Node, params: QueryStateDict, result: Result) -> None: +def QBusNumberWord(node: Node, params: ParamList, result: Result) -> None: """Obtain the bus number as an integer from word or number terminals.""" # Use the nominative, singular, indefinite form number = result._canonical diff --git a/queries/counting.py b/queries/counting.py index f35c1079..3685ee44 100755 --- a/queries/counting.py +++ b/queries/counting.py @@ -31,7 +31,7 @@ from queries import parse_num, gen_answer, read_grammar_file from query import Query, QueryStateDict -from tree import Result, Node +from tree import Result, Node, ParamList _COUNTING_QTYPE = "Counting" @@ -58,28 +58,28 @@ def help_text(lemma: str) -> str: GRAMMAR = read_grammar_file("counting") -def QCountingQuery(node: Node, params: QueryStateDict, result: Result) -> None: +def QCountingQuery(node: Node, params: ParamList, result: Result) -> None: # Set the query type result.qtype = _COUNTING_QTYPE -def QCountingUp(node: Node, params: QueryStateDict, result: Result) -> None: +def QCountingUp(node: Node, params: ParamList, result: Result) -> None: result.qkey = "CountUp" -def QCountingDown(node: Node, params: QueryStateDict, result: Result) -> None: +def QCountingDown(node: Node, params: ParamList, result: Result) -> None: result.qkey = "CountDown" -def QCountingBetween(node: Node, params: QueryStateDict, result: Result) -> None: +def QCountingBetween(node: Node, params: ParamList, result: Result) -> None: result.qkey = "CountBetween" -def QCountingFirstNumber(node: Node, params: QueryStateDict, result: Result) -> None: +def QCountingFirstNumber(node: Node, params: ParamList, result: Result) -> None: result.first_num = int(parse_num(node, result._canonical)) -def QCountingSecondNumber(node: Node, params: QueryStateDict, result: Result) -> None: +def QCountingSecondNumber(node: Node, params: ParamList, result: Result) -> None: result.second_num = int(parse_num(node, result._canonical)) @@ -88,7 +88,7 @@ def QCountingSecondNumber(node: Node, params: QueryStateDict, result: Result) -> _MAX_COUNT = 100 -def QCountingSpeed(node: Node, params: QueryStateDict, result: Result) -> None: +def QCountingSpeed(node: Node, params: ParamList, result: Result) -> None: result.delay = _SPEED2DELAY.get(node.contained_text()) diff --git a/queries/currency.py b/queries/currency.py index 65e86f61..26239348 100755 --- a/queries/currency.py +++ b/queries/currency.py @@ -42,7 +42,7 @@ read_grammar_file, ) from settings import Settings -from tree import Result, Node, NonterminalNode +from tree import Result, Node, NonterminalNode, ParamList from queries.num import float_to_text # Lemmas of keywords that could indicate that the user is trying to use this module @@ -178,17 +178,17 @@ def add_currency(curr: str, result: Result) -> None: result.currencies.append(curr) -def QCurrency(node: Node, params: QueryStateDict, result: Result) -> None: +def QCurrency(node: Node, params: ParamList, result: Result) -> None: """Currency query""" result.qtype = "Currency" result.qkey = result._canonical -def QCurNumberWord(node: Node, params: QueryStateDict, result: Result) -> None: +def QCurNumberWord(node: Node, params: ParamList, result: Result) -> None: add_num(result._canonical, result) -def QCurUnit(node: Node, params: QueryStateDict, result: Result) -> None: +def QCurUnit(node: Node, params: ParamList, result: Result) -> None: """Obtain the ISO currency code from the last three letters in the child nonterminal name.""" child = cast(NonterminalNode, node.child) @@ -196,28 +196,28 @@ def QCurUnit(node: Node, params: QueryStateDict, result: Result) -> None: add_currency(currency, result) -def QCurExchangeRate(node: Node, params: QueryStateDict, result: Result) -> None: +def QCurExchangeRate(node: Node, params: ParamList, result: Result) -> None: result.op = "exchange" result.desc = result._text -def QCurGeneralRate(node: Node, params: QueryStateDict, result: Result) -> None: +def QCurGeneralRate(node: Node, params: ParamList, result: Result) -> None: result.op = "general" result.desc = result._text -def QCurGeneralCost(node: Node, params: QueryStateDict, result: Result) -> None: +def QCurGeneralCost(node: Node, params: ParamList, result: Result) -> None: result.op = "general" result.desc = result._text -def QCurCurrencyIndex(node: Node, params: QueryStateDict, result: Result) -> None: +def QCurCurrencyIndex(node: Node, params: ParamList, result: Result) -> None: result.op = "index" result.desc = result._text add_currency("GVT", result) -def QCurConvertAmount(node: Node, params: QueryStateDict, result: Result) -> None: +def QCurConvertAmount(node: Node, params: ParamList, result: Result) -> None: # Hvað eru [X] margir [Y] - this is the X part amount: Optional[Node] = node.first_child(lambda n: n.has_t_base("amount")) if amount is not None: @@ -237,12 +237,12 @@ def QCurConvertAmount(node: Node, params: QueryStateDict, result: Result) -> Non result.desc = result._text -def QCurConvertTo(node: Node, params: QueryStateDict, result: Result) -> None: +def QCurConvertTo(node: Node, params: ParamList, result: Result) -> None: # Hvað eru [X] margir [Y] - this is the Y part result.currency = result._nominative -def QCurMuch(node: Node, params: QueryStateDict, result: Result) -> None: +def QCurMuch(node: Node, params: ParamList, result: Result) -> None: # 'Hvað eru þrír dollarar mikið [í evrum]?' # We assume that this means conversion to ISK if no currency is specified if "currency" not in result: @@ -250,7 +250,7 @@ def QCurMuch(node: Node, params: QueryStateDict, result: Result) -> None: add_currency("ISK", result) -def QCurAmountConversion(node: Node, params: QueryStateDict, result: Result) -> None: +def QCurAmountConversion(node: Node, params: ParamList, result: Result) -> None: result.op = "convert" diff --git a/queries/date.py b/queries/date.py index 00a4a307..e7f582c2 100755 --- a/queries/date.py +++ b/queries/date.py @@ -71,7 +71,7 @@ cap_first, read_grammar_file, ) -from tree import Result, Node, TerminalNode +from tree import Result, Node, TerminalNode, ParamList from settings import changedlocale from queries.num import numbers_to_ordinal, years_to_text, numbers_to_text @@ -143,43 +143,43 @@ def help_text(lemma: str) -> str: GRAMMAR = read_grammar_file("date") -def QDateQuery(node: Node, params: QueryStateDict, result: Result) -> None: +def QDateQuery(node: Node, params: ParamList, result: Result) -> None: result.qtype = _DATE_QTYPE -def QDateCurrent(node: Node, params: QueryStateDict, result: Result) -> None: +def QDateCurrent(node: Node, params: ParamList, result: Result) -> None: result["now"] = True -def QDateNextDay(node: Node, params: QueryStateDict, result: Result) -> None: +def QDateNextDay(node: Node, params: ParamList, result: Result) -> None: result["tomorrow"] = True -def QDatePrevDay(node: Node, params: QueryStateDict, result: Result) -> None: +def QDatePrevDay(node: Node, params: ParamList, result: Result) -> None: result["yesterday"] = True -def QDateHowLongUntil(node: Node, params: QueryStateDict, result: Result) -> None: +def QDateHowLongUntil(node: Node, params: ParamList, result: Result) -> None: result["until"] = True -def QDateHowLongSince(node: Node, params: QueryStateDict, result: Result) -> None: +def QDateHowLongSince(node: Node, params: ParamList, result: Result) -> None: result["since"] = True -def QDateWhenIs(node: Node, params: QueryStateDict, result: Result) -> None: +def QDateWhenIs(node: Node, params: ParamList, result: Result) -> None: result["when"] = True -def QDateWhichYear(node: Node, params: QueryStateDict, result: Result) -> None: +def QDateWhichYear(node: Node, params: ParamList, result: Result) -> None: result["year"] = True -def QDateLeapYear(node: Node, params: QueryStateDict, result: Result) -> None: +def QDateLeapYear(node: Node, params: ParamList, result: Result) -> None: result["leap"] = True -def Árið(node: Node, params: QueryStateDict, result: Result) -> None: +def Árið(node: Node, params: ParamList, result: Result) -> None: y_node = node.first_child(lambda n: True) assert isinstance(y_node, TerminalNode) y = y_node.contained_year @@ -188,7 +188,7 @@ def Árið(node: Node, params: QueryStateDict, result: Result) -> None: result["target"] = datetime(day=1, month=1, year=y) -def QDateAbsOrRel(node: Node, params: QueryStateDict, result: Result) -> None: +def QDateAbsOrRel(node: Node, params: ParamList, result: Result) -> None: datenode = node.first_child(lambda n: True) assert isinstance(datenode, TerminalNode) cdate = datenode.contained_date @@ -217,106 +217,106 @@ def QDateAbsOrRel(node: Node, params: QueryStateDict, result: Result) -> None: raise ValueError("No date in {0}".format(str(datenode))) -def QDateWhitsun(node: Node, params: QueryStateDict, result: Result) -> None: +def QDateWhitsun(node: Node, params: ParamList, result: Result) -> None: result["desc"] = "hvítasunnudagur" result["target"] = next_easter() + timedelta(days=49) -def QDateAscensionDay(node: Node, params: QueryStateDict, result: Result) -> None: +def QDateAscensionDay(node: Node, params: ParamList, result: Result) -> None: result["desc"] = "uppstigningardagur" result["target"] = next_easter() + timedelta(days=39) -def QDateAshDay(node: Node, params: QueryStateDict, result: Result) -> None: +def QDateAshDay(node: Node, params: ParamList, result: Result) -> None: result["desc"] = "öskudagur" result["target"] = next_easter() - timedelta(days=46) -def QDateBunDay(node: Node, params: QueryStateDict, result: Result) -> None: +def QDateBunDay(node: Node, params: ParamList, result: Result) -> None: result["desc"] = "bolludagur" result["target"] = next_easter() - timedelta(days=48) # 7 weeks before easter -def QDateHalloween(node: Node, params: QueryStateDict, result: Result) -> None: +def QDateHalloween(node: Node, params: ParamList, result: Result) -> None: result["desc"] = "hrekkjavaka" result["target"] = dnext(datetime(year=datetime.today().year, month=10, day=31)) -def QDateSovereigntyDay(node: Node, params: QueryStateDict, result: Result) -> None: +def QDateSovereigntyDay(node: Node, params: ParamList, result: Result) -> None: result["desc"] = "fullveldisdagurinn" result["target"] = dnext(datetime(year=datetime.today().year, month=12, day=1)) -def QDateFirstDayOfSummer(node: Node, params: QueryStateDict, result: Result) -> None: +def QDateFirstDayOfSummer(node: Node, params: ParamList, result: Result) -> None: result["desc"] = "sumardagurinn fyrsti" # !!! BUG: This is not correct in all cases d = dnext(datetime(year=datetime.today().year, month=4, day=18)) result["target"] = next_weekday(d, 3) -def QDateThorlaksMass(node: Node, params: QueryStateDict, result: Result) -> None: +def QDateThorlaksMass(node: Node, params: ParamList, result: Result) -> None: result["desc"] = "þorláksmessa" result["target"] = dnext(datetime(year=datetime.today().year, month=12, day=23)) -def QDateChristmasEve(node: Node, params: QueryStateDict, result: Result) -> None: +def QDateChristmasEve(node: Node, params: ParamList, result: Result) -> None: result["desc"] = "aðfangadagur jóla" result["target"] = dnext(datetime(year=datetime.today().year, month=12, day=24)) -def QDateChristmasDay(node: Node, params: QueryStateDict, result: Result) -> None: +def QDateChristmasDay(node: Node, params: ParamList, result: Result) -> None: result["desc"] = "jóladagur" result["target"] = dnext(datetime(year=datetime.today().year, month=12, day=25)) -def QDateNewYearsEve(node: Node, params: QueryStateDict, result: Result) -> None: +def QDateNewYearsEve(node: Node, params: ParamList, result: Result) -> None: result["desc"] = "gamlársdagur" result["target"] = dnext(datetime(year=datetime.today().year, month=12, day=31)) -def QDateNewYearsDay(node: Node, params: QueryStateDict, result: Result) -> None: +def QDateNewYearsDay(node: Node, params: ParamList, result: Result) -> None: result["desc"] = "nýársdagur" result["target"] = dnext(datetime(year=datetime.today().year, month=1, day=1)) -def QDateNewYear(node: Node, params: QueryStateDict, result: Result) -> None: +def QDateNewYear(node: Node, params: ParamList, result: Result) -> None: result["desc"] = "áramótin" result["is_verb"] = "eru" result["target"] = dnext(datetime(year=datetime.today().year, month=1, day=1)) -def QDateWorkersDay(node: Node, params: QueryStateDict, result: Result) -> None: +def QDateWorkersDay(node: Node, params: ParamList, result: Result) -> None: result["desc"] = "baráttudagur verkalýðsins" result["target"] = dnext(datetime(year=datetime.today().year, month=5, day=1)) -def QDateEaster(node: Node, params: QueryStateDict, result: Result) -> None: +def QDateEaster(node: Node, params: ParamList, result: Result) -> None: result["desc"] = "páskar" result["is_verb"] = "eru" result["target"] = next_easter() -def QDateEasterSunday(node: Node, params: QueryStateDict, result: Result) -> None: +def QDateEasterSunday(node: Node, params: ParamList, result: Result) -> None: result["desc"] = "páskadagur" result["target"] = next_easter() -def QDateGoodFriday(node: Node, params: QueryStateDict, result: Result) -> None: +def QDateGoodFriday(node: Node, params: ParamList, result: Result) -> None: result["desc"] = "föstudagurinn langi" result["target"] = next_easter() + timedelta(days=-2) -def QDateMaundyThursday(node: Node, params: QueryStateDict, result: Result) -> None: +def QDateMaundyThursday(node: Node, params: ParamList, result: Result) -> None: result["desc"] = "skírdagur" result["target"] = next_easter() + timedelta(days=-3) -def QDateNationalDay(node: Node, params: QueryStateDict, result: Result) -> None: +def QDateNationalDay(node: Node, params: ParamList, result: Result) -> None: result["desc"] = "þjóðhátíðardagurinn" result["target"] = dnext(datetime(year=datetime.today().year, month=6, day=17)) -def QDateBankHoliday(node: Node, params: QueryStateDict, result: Result) -> None: +def QDateBankHoliday(node: Node, params: ParamList, result: Result) -> None: result["desc"] = "frídagur verslunarmanna" # First Monday of August result["target"] = this_or_next_weekday( @@ -324,7 +324,7 @@ def QDateBankHoliday(node: Node, params: QueryStateDict, result: Result) -> None ) -def QDateCultureNight(node: Node, params: QueryStateDict, result: Result) -> None: +def QDateCultureNight(node: Node, params: ParamList, result: Result) -> None: result["desc"] = "menningarnótt" # Culture night is on the first Saturday after Reykjavík's birthday on Aug 18th aug18 = dnext(datetime(year=datetime.today().year, month=8, day=18)) @@ -332,73 +332,73 @@ def QDateCultureNight(node: Node, params: QueryStateDict, result: Result) -> Non result["target"] = next_weekday(aug18, 5) # Find the next Saturday -def QDateValentinesDay(node: Node, params: QueryStateDict, result: Result) -> None: +def QDateValentinesDay(node: Node, params: ParamList, result: Result) -> None: result["desc"] = "valentínusardagur" result["target"] = dnext(datetime(year=datetime.today().year, month=2, day=14)) -def QDateMansDay(node: Node, params: QueryStateDict, result: Result) -> None: +def QDateMansDay(node: Node, params: ParamList, result: Result) -> None: result["desc"] = "bóndadagur" jan19 = dnext(datetime(year=datetime.today().year, month=1, day=19)) result["target"] = next_weekday(jan19, 4) # First Friday after Jan 19 -def QDateWomansDay(node: Node, params: QueryStateDict, result: Result) -> None: +def QDateWomansDay(node: Node, params: ParamList, result: Result) -> None: result["desc"] = "konudagur" feb18 = dnext(datetime(year=datetime.today().year, month=2, day=18)) result["target"] = next_weekday(feb18, 6) # First Sunday after Feb 18 -def QDateMardiGrasDay(node: Node, params: QueryStateDict, result: Result) -> None: +def QDateMardiGrasDay(node: Node, params: ParamList, result: Result) -> None: result["desc"] = "sprengidagur" result["target"] = next_easter() - timedelta(days=47) -def QDatePalmSunday(node: Node, params: QueryStateDict, result: Result) -> None: +def QDatePalmSunday(node: Node, params: ParamList, result: Result) -> None: result["desc"] = "pálmasunnudagur" result["target"] = next_easter() - timedelta(days=7) # Week before Easter Sunday -def QDateSeamensDay(node: Node, params: QueryStateDict, result: Result) -> None: +def QDateSeamensDay(node: Node, params: ParamList, result: Result) -> None: result["desc"] = "sjómannadagur" june1 = dnext(datetime(year=datetime.today().year, month=6, day=1)) result["target"] = this_or_next_weekday(june1, 6) # First Sunday in June -def QDateMothersDay(node: Node, params: QueryStateDict, result: Result) -> None: +def QDateMothersDay(node: Node, params: ParamList, result: Result) -> None: result["desc"] = "mæðradagur" may8 = dnext(datetime(year=datetime.today().year, month=5, day=8)) result["target"] = this_or_next_weekday(may8, 6) # Second Sunday in May -def QDateFathersDay(node: Node, params: QueryStateDict, result: Result) -> None: +def QDateFathersDay(node: Node, params: ParamList, result: Result) -> None: result["desc"] = "feðradagur" nov8 = dnext(datetime(year=datetime.today().year, month=11, day=8)) result["target"] = this_or_next_weekday(nov8, 6) # Second Sunday in November -def QDateIcelandicTongueDay(node: Node, params: QueryStateDict, result: Result) -> None: +def QDateIcelandicTongueDay(node: Node, params: ParamList, result: Result) -> None: result["desc"] = "dagur íslenskrar tungu" result["target"] = dnext(datetime(year=datetime.today().year, month=11, day=16)) -def QDateSecondChristmasDay(node: Node, params: QueryStateDict, result: Result) -> None: +def QDateSecondChristmasDay(node: Node, params: ParamList, result: Result) -> None: result["desc"] = "annar í jólum" result["target"] = dnext(datetime(year=datetime.today().year, month=12, day=26)) -def QDateFirstDayOfWinter(node: Node, params: QueryStateDict, result: Result) -> None: +def QDateFirstDayOfWinter(node: Node, params: ParamList, result: Result) -> None: result["desc"] = "fyrsti vetrardagur" result["target"] = None # To be completed -def QDateSummerSolstice(node: Node, params: QueryStateDict, result: Result) -> None: +def QDateSummerSolstice(node: Node, params: ParamList, result: Result) -> None: result["desc"] = "sumarsólstöður" result["is_verb"] = "eru" result["target"] = None # To be completed -def QDateWinterSolstice(node: Node, params: QueryStateDict, result: Result) -> None: +def QDateWinterSolstice(node: Node, params: ParamList, result: Result) -> None: result["desc"] = "vetrarsólstöður" result["is_verb"] = "eru" result["target"] = None # To be completed diff --git a/queries/dictionary.py b/queries/dictionary.py index 8f2750f2..fa623431 100755 --- a/queries/dictionary.py +++ b/queries/dictionary.py @@ -28,7 +28,7 @@ import logging from query import Query, QueryStateDict -from tree import Result, Node +from tree import Result, Node, ParamList from queries import query_json_api, gen_answer, cap_first, icequote, read_grammar_file @@ -43,11 +43,11 @@ GRAMMAR = read_grammar_file("dictionary") -def QDictSubjectNom(node: Node, params: QueryStateDict, result: Result) -> None: +def QDictSubjectNom(node: Node, params: ParamList, result: Result) -> None: result.qkey = result._text -def QDictWordQuery(node: Node, params: QueryStateDict, result: Result) -> None: +def QDictWordQuery(node: Node, params: ParamList, result: Result) -> None: result.qtype = "Dictionary" diff --git a/queries/examples/grammar.py b/queries/examples/grammar.py index a526cd1d..e2f7f687 100755 --- a/queries/examples/grammar.py +++ b/queries/examples/grammar.py @@ -29,7 +29,7 @@ import random from query import QueryStateDict -from tree import Node, Result +from tree import Node, Result, ParamList # Indicate that this module wants to handle parse trees for queries, @@ -70,16 +70,16 @@ def help_text(lemma: str) -> str: """ -def QGrammarExampleQuery(node: Node, params: QueryStateDict, result: Result): +def QGrammarExampleQuery(node: Node, params: ParamList, result: Result): # Set the query type result.qtype = "GrammarTest" -def QGrammarExampleTest(node: Node, params: QueryStateDict, result: Result): +def QGrammarExampleTest(node: Node, params: ParamList, result: Result): result.qkey = "Test" -def QGrammarExampleNotTest(node: Node, params: QueryStateDict, result: Result): +def QGrammarExampleNotTest(node: Node, params: ParamList, result: Result): result.qkey = "NotTest" diff --git a/queries/flights.py b/queries/flights.py index cdd7a0d5..0ec9ad07 100755 --- a/queries/flights.py +++ b/queries/flights.py @@ -36,7 +36,7 @@ from query import Query, QueryStateDict from queries import query_json_api, is_plural, read_grammar_file, spell_out -from tree import Result, Node +from tree import ParamList, Result, Node from settings import changedlocale from queries.num import digits_to_text, numbers_to_ordinal, numbers_to_text @@ -107,24 +107,24 @@ def help_text(lemma: str) -> str: _AIRPORT_TO_IATA_MAP = {val: key for key, val in _IATA_TO_AIRPORT_MAP.items()} -def QFlightsQuery(node: Node, params: QueryStateDict, result: Result) -> None: +def QFlightsQuery(node: Node, params: ParamList, result: Result) -> None: # Set the query type result.qtype = _FLIGHTS_QTYPE -def QFlightsArrivalQuery(node: Node, params: QueryStateDict, result: Result) -> None: +def QFlightsArrivalQuery(node: Node, params: ParamList, result: Result) -> None: result["departure"] = False -def QFlightsDepartureQuery(node: Node, params: QueryStateDict, result: Result) -> None: +def QFlightsDepartureQuery(node: Node, params: ParamList, result: Result) -> None: result["departure"] = True -def QFlightsArrLoc(node: Node, params: QueryStateDict, result: Result) -> None: +def QFlightsArrLoc(node: Node, params: ParamList, result: Result) -> None: result["to_loc"] = result._nominative -def QFlightsDepLoc(node: Node, params: QueryStateDict, result: Result) -> None: +def QFlightsDepLoc(node: Node, params: ParamList, result: Result) -> None: result["from_loc"] = result._nominative diff --git a/queries/geography.py b/queries/geography.py index b7d4d6f0..2e0f5ad6 100755 --- a/queries/geography.py +++ b/queries/geography.py @@ -46,7 +46,7 @@ location_info, capitalize_placename, ) -from tree import Result, Node +from tree import ParamList, Result, Node _GEO_QTYPE = "Geography" @@ -80,24 +80,24 @@ def help_text(lemma: str) -> str: GRAMMAR = read_grammar_file("geography") -def QGeoQuery(node: Node, params: QueryStateDict, result: Result) -> None: +def QGeoQuery(node: Node, params: ParamList, result: Result) -> None: # Set the query type result.qtype = _GEO_QTYPE -def QGeoCapitalQuery(node: Node, params: QueryStateDict, result: Result) -> None: +def QGeoCapitalQuery(node: Node, params: ParamList, result: Result) -> None: result["geo_qtype"] = "capital" -def QGeoCountryQuery(node: Node, params: QueryStateDict, result: Result) -> None: +def QGeoCountryQuery(node: Node, params: ParamList, result: Result) -> None: result["geo_qtype"] = "country" -def QGeoContinentQuery(node: Node, params: QueryStateDict, result: Result) -> None: +def QGeoContinentQuery(node: Node, params: ParamList, result: Result) -> None: result["geo_qtype"] = "continent" -def QGeoLocationDescQuery(node: Node, params: QueryStateDict, result: Result) -> None: +def QGeoLocationDescQuery(node: Node, params: ParamList, result: Result) -> None: result["geo_qtype"] = "loc_desc" @@ -119,7 +119,7 @@ def _preprocess(name: str) -> str: return fixed -def QGeoSubject(node: Node, params: QueryStateDict, result: Result) -> None: +def QGeoSubject(node: Node, params: ParamList, result: Result) -> None: n = capitalize_placename(_preprocess(result._text)) nom = NounPhrase(n).nominative or n result.subject = nom diff --git a/queries/iot_connect.py b/queries/iot_connect.py index fc2f54ba..df446a3f 100644 --- a/queries/iot_connect.py +++ b/queries/iot_connect.py @@ -32,7 +32,7 @@ from query import Query, QueryStateDict from queries import gen_answer, read_jsfile from queries.extras.sonos import SonosClient -from tree import Result, Node +from tree import ParamList, Result, Node from util import read_api_key from speech import text_to_audio_url @@ -103,24 +103,24 @@ def help_text(lemma: str) -> str: """ -def QIoTConnectLights(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTConnectLights(node: Node, params: ParamList, result: Result) -> None: result.qtype = "connect_lights" result.action = "connect_lights" -def QIoTConnectSpeaker(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTConnectSpeaker(node: Node, params: ParamList, result: Result) -> None: print("Connect Speaker") result.qtype = "connect_speaker" result.action = "connect_speaker" -def QIoTCreateSpeakerToken(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTCreateSpeakerToken(node: Node, params: ParamList, result: Result) -> None: print("Create Token") result.qtype = "create_speaker_token" result.action = "create_speaker_token" -def QIoTConnectSpotify(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTConnectSpotify(node: Node, params: ParamList, result: Result) -> None: print("Connect Spotify") result.qtype = "connect_spotify" result.action = "connect_spotify" diff --git a/queries/iot_hue.py b/queries/iot_hue.py index 34c0cf6f..80f4805b 100755 --- a/queries/iot_hue.py +++ b/queries/iot_hue.py @@ -45,7 +45,7 @@ from query import Query, QueryStateDict from queries import gen_answer, read_jsfile, read_grammar_file -from tree import Result, Node, TerminalNode +from tree import ParamList, Result, Node, TerminalNode class SmartLights(TypedDict): @@ -112,23 +112,23 @@ def help_text(lemma: str) -> str: ) -def QIoTQuery(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTQuery(node: Node, params: ParamList, result: Result) -> None: result.qtype = _IoT_QTYPE -def QIoTColorWord(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTColorWord(node: Node, params: ParamList, result: Result) -> None: result.changing_color = True -def QIoTSceneWord(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTSceneWord(node: Node, params: ParamList, result: Result) -> None: result.changing_scene = True -def QIoTBrightnessWord(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTBrightnessWord(node: Node, params: ParamList, result: Result) -> None: result.changing_brightness = True -def QIoTTurnOnLightsRest(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTTurnOnLightsRest(node: Node, params: ParamList, result: Result) -> None: result.action = "turn_on" if "hue_obj" not in result: result["hue_obj"] = {"on": True} @@ -136,7 +136,7 @@ def QIoTTurnOnLightsRest(node: Node, params: QueryStateDict, result: Result) -> result["hue_obj"]["on"] = True -def QIoTTurnOffLightsRest(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTTurnOffLightsRest(node: Node, params: ParamList, result: Result) -> None: result.action = "turn_off" if "hue_obj" not in result: result["hue_obj"] = {"on": False} @@ -144,7 +144,7 @@ def QIoTTurnOffLightsRest(node: Node, params: QueryStateDict, result: Result) -> result["hue_obj"]["on"] = False -def QIoTNewColor(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTNewColor(node: Node, params: ParamList, result: Result) -> None: result.action = "set_color" color_hue = _COLORS.get(result.color_name, None) @@ -156,9 +156,7 @@ def QIoTNewColor(node: Node, params: QueryStateDict, result: Result) -> None: result["hue_obj"]["on"] = True -def QIoTMoreBrighterOrHigher( - node: Node, params: QueryStateDict, result: Result -) -> None: +def QIoTMoreBrighterOrHigher(node: Node, params: ParamList, result: Result) -> None: result.action = "increase_brightness" if "hue_obj" not in result: result["hue_obj"] = {"on": True, "bri_inc": 64} @@ -167,7 +165,7 @@ def QIoTMoreBrighterOrHigher( result["hue_obj"]["on"] = True -def QIoTLessDarkerOrLower(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTLessDarkerOrLower(node: Node, params: ParamList, result: Result) -> None: result.action = "decrease_brightness" if "hue_obj" not in result: result["hue_obj"] = {"bri_inc": -64} @@ -175,7 +173,7 @@ def QIoTLessDarkerOrLower(node: Node, params: QueryStateDict, result: Result) -> result["hue_obj"]["bri_inc"] = -64 -def QIoTIncreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTIncreaseVerb(node: Node, params: ParamList, result: Result) -> None: result.action = "increase_brightness" if "hue_obj" not in result: result["hue_obj"] = {"on": True, "bri_inc": 64} @@ -184,7 +182,7 @@ def QIoTIncreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None result["hue_obj"]["on"] = True -def QIoTCooler(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTCooler(node: Node, params: ParamList, result: Result) -> None: result.action = "decrease_colortemp" result.changing_temp = True if "hue_obj" not in result: @@ -193,7 +191,7 @@ def QIoTCooler(node: Node, params: QueryStateDict, result: Result) -> None: result["hue_obj"]["ct_inc"] = -30000 -def QIoTWarmer(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTWarmer(node: Node, params: ParamList, result: Result) -> None: result.action = "increase_colortemp" result.changing_temp = True if "hue_obj" not in result: @@ -202,7 +200,7 @@ def QIoTWarmer(node: Node, params: QueryStateDict, result: Result) -> None: result["hue_obj"]["ct_inc"] = 30000 -def QIoTDecreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTDecreaseVerb(node: Node, params: ParamList, result: Result) -> None: result.action = "decrease_brightness" if "hue_obj" not in result: result["hue_obj"] = {"bri_inc": -64} @@ -210,7 +208,7 @@ def QIoTDecreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None result["hue_obj"]["bri_inc"] = -64 -def QIoTBrightest(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTBrightest(node: Node, params: ParamList, result: Result) -> None: result.action = "increase_brightness" if "hue_obj" not in result: result["hue_obj"] = {"bri": 255} @@ -218,7 +216,7 @@ def QIoTBrightest(node: Node, params: QueryStateDict, result: Result) -> None: result["hue_obj"]["bri"] = 255 -def QIoTDarkest(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTDarkest(node: Node, params: ParamList, result: Result) -> None: result.action = "decrease_brightness" if "hue_obj" not in result: result["hue_obj"] = {"bri": 0} @@ -226,7 +224,7 @@ def QIoTDarkest(node: Node, params: QueryStateDict, result: Result) -> None: result["hue_obj"]["bri"] = 0 -def QIoTNewScene(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTNewScene(node: Node, params: ParamList, result: Result) -> None: result.action = "set_scene" scene_name = result.get("scene_name", None) if scene_name is not None: @@ -237,27 +235,27 @@ def QIoTNewScene(node: Node, params: QueryStateDict, result: Result) -> None: result["hue_obj"]["on"] = True -def QIoTColorName(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTColorName(node: Node, params: ParamList, result: Result) -> None: fc = node.first_child(lambda x: True) if fc: result["color_name"] = fc.string_self().strip("'").split(":")[0] -def QIoTSceneName(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTSceneName(node: Node, params: ParamList, result: Result) -> None: result["scene_name"] = result._indefinite result["changing_scene"] = True print("scene: " + result.get("scene_name", None)) -def QIoTGroupName(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTGroupName(node: Node, params: ParamList, result: Result) -> None: result["group_name"] = result._indefinite -def QIoTLightName(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTLightName(node: Node, params: ParamList, result: Result) -> None: result["light_name"] = result._indefinite -def QIoTSpeakerHotwords(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTSpeakerHotwords(node: Node, params: ParamList, result: Result) -> None: print("lights banwords") result.abort = True diff --git a/queries/iot_speakers.py b/queries/iot_speakers.py index 75f10ebc..2618e029 100644 --- a/queries/iot_speakers.py +++ b/queries/iot_speakers.py @@ -39,7 +39,7 @@ from query import Query, QueryStateDict from queries import read_grammar_file from queries.extras.sonos import SonosClient -from tree import Result, Node +from tree import ParamList, Result, Node # Dictionary of radio stations and their stream urls _RADIO_STREAMS: Dict[str, str] = { @@ -96,188 +96,184 @@ def help_text(lemma: str) -> str: GRAMMAR = read_grammar_file("iot_speakers") -def QIoTSpeaker(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTSpeaker(node: Node, params: ParamList, result: Result) -> None: result.qtype = _IoT_QTYPE -def QIoTSpeakerTurnOnVerb(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTSpeakerTurnOnVerb(node: Node, params: ParamList, result: Result) -> None: result.qkey = "turn_on" -def QIoTSpeakerTurnOffVerb(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTSpeakerTurnOffVerb(node: Node, params: ParamList, result: Result) -> None: result.qkey = "turn_off" -def QIoTSpeakerPlayVerb(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTSpeakerPlayVerb(node: Node, params: ParamList, result: Result) -> None: result.qkey = "turn_on" -def QIoTSpeakerPauseVerb(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTSpeakerPauseVerb(node: Node, params: ParamList, result: Result) -> None: result.qkey = "turn_off" -def QIoTSpeakerSkipVerb(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTSpeakerSkipVerb(node: Node, params: ParamList, result: Result) -> None: result.qkey = "next_song" -def QIoTSpeakerNewPlay(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTSpeakerNewPlay(node: Node, params: ParamList, result: Result) -> None: result.qkey = "turn_on" -def QIoTSpeakerNewPause(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTSpeakerNewPause(node: Node, params: ParamList, result: Result) -> None: result.qkey = "turn_off" -def QIoTSpeakerNewNext(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTSpeakerNewNext(node: Node, params: ParamList, result: Result) -> None: result.qkey = "next_song" -def QIoTSpeakerNewPrevious(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTSpeakerNewPrevious(node: Node, params: ParamList, result: Result) -> None: result.qkey = "prev_song" -def QIoTSpeakerIncreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTSpeakerIncreaseVerb(node: Node, params: ParamList, result: Result) -> None: result.qkey = "increase_volume" -def QIoTSpeakerDecreaseVerb(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTSpeakerDecreaseVerb(node: Node, params: ParamList, result: Result) -> None: result.qkey = "decrease_volume" -def QIoTSpeakerMoreOrHigher(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTSpeakerMoreOrHigher(node: Node, params: ParamList, result: Result) -> None: result.qkey = "increase_volume" -def QIoTSpeakerLessOrLower(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTSpeakerLessOrLower(node: Node, params: ParamList, result: Result) -> None: result.qkey = "decrease_volume" -def QIoTSpeakerMusicWord(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTSpeakerMusicWord(node: Node, params: ParamList, result: Result) -> None: result.target = "music" -def QIoTSpeakerSpeakerWord(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTSpeakerSpeakerWord(node: Node, params: ParamList, result: Result) -> None: result.target = "speaker" -def QIoTSpeakerRadioWord(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTSpeakerRadioWord(node: Node, params: ParamList, result: Result) -> None: result.target = "radio" -def QIoTSpeakerGroupName(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTSpeakerGroupName(node: Node, params: ParamList, result: Result) -> None: result.group_name = result._indefinite -def QIoTSpeakerRas1(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTSpeakerRas1(node: Node, params: ParamList, result: Result) -> None: result.target = "radio" result.station = "Rás 1" -def QIoTSpeakerRas2(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTSpeakerRas2(node: Node, params: ParamList, result: Result) -> None: result.target = "radio" result.station = "Rás 2" -def QIoTSpeakerRondo(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTSpeakerRondo(node: Node, params: ParamList, result: Result) -> None: result.target = "radio" result.station = "Rondó" -def QIoTSpeakerBylgjan(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTSpeakerBylgjan(node: Node, params: ParamList, result: Result) -> None: result.target = "radio" result.station = "Bylgjan" -def QIoTSpeakerFm957(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTSpeakerFm957(node: Node, params: ParamList, result: Result) -> None: result.target = "radio" result.station = "FM957" -def QIoTSpeakerUtvarpSaga(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTSpeakerUtvarpSaga(node: Node, params: ParamList, result: Result) -> None: result.target = "radio" result.station = "Útvarp Saga" -def QIoTSpeakerGullbylgjan(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTSpeakerGullbylgjan(node: Node, params: ParamList, result: Result) -> None: result.target = "radio" result.station = "Gullbylgjan" -def QIoTSpeakerLettbylgjan(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTSpeakerLettbylgjan(node: Node, params: ParamList, result: Result) -> None: result.target = "radio" result.station = "Léttbylgjan" -def QIoTSpeakerXid(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTSpeakerXid(node: Node, params: ParamList, result: Result) -> None: result.target = "radio" result.station = "X977" -def QIoTSpeakerKissfm(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTSpeakerKissfm(node: Node, params: ParamList, result: Result) -> None: result.target = "radio" result.station = "KissFM" -def QIoTSpeakerFlassback(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTSpeakerFlassback(node: Node, params: ParamList, result: Result) -> None: result.target = "radio" result.station = "Flashback" -def QIoTSpeakerRetro(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTSpeakerRetro(node: Node, params: ParamList, result: Result) -> None: result.target = "radio" result.station = "Retro" -def QIoTSpeakerUtvarp101(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTSpeakerUtvarp101(node: Node, params: ParamList, result: Result) -> None: result.target = "radio" result.station = "Útvarp 101" -def QIoTSpeakerK100(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTSpeakerK100(node: Node, params: ParamList, result: Result) -> None: result.target = "radio" result.station = "K100" -def QIoTSpeakerIslenskaBylgjan( - node: Node, params: QueryStateDict, result: Result -) -> None: +def QIoTSpeakerIslenskaBylgjan(node: Node, params: ParamList, result: Result) -> None: result.target = "radio" result.station = "Íslenska Bylgjan" -def QIoTSpeaker80sBylgjan(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTSpeaker80sBylgjan(node: Node, params: ParamList, result: Result) -> None: result.target = "radio" result.station = "80s Bylgjan" -def QIoTSpeakerApparatid(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTSpeakerApparatid(node: Node, params: ParamList, result: Result) -> None: result.target = "radio" result.station = "Apparatið" -def QIoTSpeakerFmExtra(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTSpeakerFmExtra(node: Node, params: ParamList, result: Result) -> None: result.target = "radio" result.station = "FM Extra" -def QIoTSpeaker70sFlashback(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTSpeaker70sFlashback(node: Node, params: ParamList, result: Result) -> None: result.target = "radio" result.station = "70s Flashback" -def QIoTSpeaker80sFlashback(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTSpeaker80sFlashback(node: Node, params: ParamList, result: Result) -> None: result.target = "radio" result.station = "80s Flashback" -def QIoTSpeaker90sFlashback(node: Node, params: QueryStateDict, result: Result) -> None: +def QIoTSpeaker90sFlashback(node: Node, params: ParamList, result: Result) -> None: result.target = "radio" result.station = "90s Flashback" -def QIoTSpeakerUtvarpSudurland( - node: Node, params: QueryStateDict, result: Result -) -> None: +def QIoTSpeakerUtvarpSudurland(node: Node, params: ParamList, result: Result) -> None: result.target = "radio" result.station = "Útvarp Suðurland" diff --git a/queries/ja.py b/queries/ja.py index 9fff181d..cc724d26 100755 --- a/queries/ja.py +++ b/queries/ja.py @@ -39,7 +39,7 @@ from queries.num import numbers_to_text, digits_to_text from query import AnswerTuple, Query, QueryStateDict -from tree import Result, Node +from tree import ParamList, Result, Node from geo import iceprep_for_street from util import read_api_key @@ -57,20 +57,20 @@ GRAMMAR = read_grammar_file("ja") -def QJaSubject(node: Node, params: QueryStateDict, result: Result) -> None: +def QJaSubject(node: Node, params: ParamList, result: Result) -> None: result.qkey = result._nominative -def QJaPhoneNum(node: Node, params: QueryStateDict, result: Result) -> None: +def QJaPhoneNum(node: Node, params: ParamList, result: Result) -> None: result.phone_number = result._nominative result.qkey = result.phone_number -def QJaName4PhoneNumQuery(node: Node, params: QueryStateDict, result: Result) -> None: +def QJaName4PhoneNumQuery(node: Node, params: ParamList, result: Result) -> None: result.qtype = "Name4PhoneNum" -def QJaPhoneNum4NameQuery(node: Node, params: QueryStateDict, result: Result) -> None: +def QJaPhoneNum4NameQuery(node: Node, params: ParamList, result: Result) -> None: result.qtype = "PhoneNum4Name" diff --git a/queries/news.py b/queries/news.py index 743bc77d..45c7d413 100755 --- a/queries/news.py +++ b/queries/news.py @@ -35,7 +35,7 @@ from query import Query, QueryStateDict, AnswerTuple from queries import gen_answer, query_json_api, read_grammar_file -from tree import Result, Node +from tree import ParamList, Result, Node _NEWS_QTYPE = "News" @@ -63,7 +63,7 @@ def help_text(lemma: str) -> str: GRAMMAR = read_grammar_file("news") -def QNewsQuery(node: Node, params: QueryStateDict, result: Result) -> None: +def QNewsQuery(node: Node, params: ParamList, result: Result) -> None: result.qtype = _NEWS_QTYPE diff --git a/queries/opinion.py b/queries/opinion.py index 182d254b..5cafa88b 100755 --- a/queries/opinion.py +++ b/queries/opinion.py @@ -28,7 +28,7 @@ from query import Query, QueryStateDict from queries import gen_answer, read_grammar_file -from tree import Result, Node +from tree import ParamList, Result, Node _OPINION_QTYPE = "Opinion" @@ -45,11 +45,11 @@ GRAMMAR = read_grammar_file("opinion") -def QOpinionQuery(node: Node, params: QueryStateDict, result: Result) -> None: +def QOpinionQuery(node: Node, params: ParamList, result: Result) -> None: result["qtype"] = _OPINION_QTYPE -def QOpinionSubject(node: Node, params: QueryStateDict, result: Result) -> None: +def QOpinionSubject(node: Node, params: ParamList, result: Result) -> None: result["subject_nom"] = result._nominative diff --git a/queries/petrol.py b/queries/petrol.py index 0b85ae5b..f465247b 100755 --- a/queries/petrol.py +++ b/queries/petrol.py @@ -32,7 +32,7 @@ import random from geo import distance -from tree import Result, Node +from tree import ParamList, Result, Node from query import Query, QueryStateDict from queries import ( query_json_api, @@ -88,20 +88,20 @@ def help_text(lemma: str) -> str: GRAMMAR = read_grammar_file("petrol") -def QPetrolQuery(node: Node, params: QueryStateDict, result: Result) -> None: +def QPetrolQuery(node: Node, params: ParamList, result: Result) -> None: result.qtype = _PETROL_QTYPE -def QPetrolClosestStation(node: Node, params: QueryStateDict, result: Result) -> None: +def QPetrolClosestStation(node: Node, params: ParamList, result: Result) -> None: result.qkey = "ClosestStation" -def QPetrolCheapestStation(node: Node, params: QueryStateDict, result: Result) -> None: +def QPetrolCheapestStation(node: Node, params: ParamList, result: Result) -> None: result.qkey = "CheapestStation" def QPetrolClosestCheapestStation( - node: Node, params: QueryStateDict, result: Result + node: Node, params: ParamList, result: Result ) -> None: result.qkey = "ClosestCheapestStation" diff --git a/queries/pic.py b/queries/pic.py index 8f8184cc..e43a05ab 100755 --- a/queries/pic.py +++ b/queries/pic.py @@ -29,7 +29,7 @@ from query import Query, QueryStateDict from queries import gen_answer, icequote, read_grammar_file from reynir import NounPhrase -from tree import Result, Node +from tree import ParamList, Result, Node from images import get_image_url, Img @@ -62,7 +62,7 @@ def help_text(lemma: str) -> str: GRAMMAR = read_grammar_file("pic") -def QPicQuery(node: Node, params: QueryStateDict, result: Result) -> None: +def QPicQuery(node: Node, params: ParamList, result: Result) -> None: # Set the query type result.qtype = _PIC_QTYPE @@ -71,14 +71,14 @@ def _preprocess(s: str) -> str: return s -def QPicSubject(node: Node, params: QueryStateDict, result: Result) -> None: +def QPicSubject(node: Node, params: ParamList, result: Result) -> None: n = _preprocess(result._text) nom = NounPhrase(n).nominative or n result.subject = nom result.subject_þgf = result._text -def QPicWrongPictureQuery(node: Node, params: QueryStateDict, result: Result) -> None: +def QPicWrongPictureQuery(node: Node, params: ParamList, result: Result) -> None: result.wrong = True diff --git a/queries/pizza.py b/queries/pizza.py index 0c5ed1c3..e82d6640 100644 --- a/queries/pizza.py +++ b/queries/pizza.py @@ -20,27 +20,24 @@ This query module handles dialogue related to ordering pizza. """ -from typing import Any, Dict, List, Optional, Set, cast +from typing import Any, Dict, List, Optional, cast import logging import random from query import Query, QueryStateDict -from tree import Result, Node +from tree import ParamList, Result, Node from queries import ( AnswerTuple, gen_answer, - natlang_seq, parse_num, read_grammar_file, sing_or_plur, ) -from queries.num import number_to_text, numbers_to_ordinal, numbers_to_text +from queries.num import number_to_text, numbers_to_text from queries.extras.resources import ( FinalResource, - ListResource, DictResource, OrResource, - Resource, ResourceState, StringResource, WrapperResource, @@ -216,19 +213,19 @@ def _generate_final_answer( return gen_answer(resource.prompts["final"]) -def QPizzaDialogue(node: Node, params: QueryStateDict, result: Result) -> None: +def QPizzaDialogue(node: Node, params: ParamList, result: Result) -> None: if "qtype" not in result: result.qtype = _PIZZA_QTYPE -def QPizzaHotWord(node: Node, params: QueryStateDict, result: Result) -> None: +def QPizzaHotWord(node: Node, params: ParamList, result: Result) -> None: result.qtype = _START_DIALOGUE_QTYPE print("ACTIVATING PIZZA MODULE") Query.get_dsm(result).hotword_activated() def QPizzaNumberAndSpecificationWrapper( - node: Node, params: QueryStateDict, result: Result + node: Node, params: ParamList, result: Result ) -> None: """ Dynamically adds a number of pizzas if there is no @@ -325,9 +322,7 @@ def QPizzaNumberAndSpecificationWrapper( dsm.extras["added_pizzas"] = dsm.extras.get("added_pizzas", 0) + 1 -def QPizzaNumberAndSpecification( - node: Node, params: QueryStateDict, result: Result -) -> None: +def QPizzaNumberAndSpecification(node: Node, params: ParamList, result: Result) -> None: """ Adds pizza information to the result. """ @@ -352,23 +347,23 @@ def QPizzaNumberAndSpecification( result.pizzas = [pizza] -def QPizzaSpecification(node: Node, params: QueryStateDict, result: Result) -> None: +def QPizzaSpecification(node: Node, params: ParamList, result: Result) -> None: print("In QPizzaSpecification") -def QPizzaToppingsWord(node: Node, params: QueryStateDict, result: Result) -> None: +def QPizzaToppingsWord(node: Node, params: ParamList, result: Result) -> None: topping: str = result.dict.pop("real_name", result._nominative) if "toppings in QPizzaToppingsWord" not in result: result["toppings"] = {} result["toppings"][topping] = 1 # TODO: Add support for extra toppings -def QPizzaMenuWords(node: Node, params: QueryStateDict, result: Result) -> None: +def QPizzaMenuWords(node: Node, params: ParamList, result: Result) -> None: result.menu = result._root # TODO: If multiple menu items added at the same time it will be in plural form -def QPizzaNum(node: Node, params: QueryStateDict, result: Result) -> None: +def QPizzaNum(node: Node, params: ParamList, result: Result) -> None: number: int = int(parse_num(node, result._nominative)) if "numbers" not in result: result["numbers"] = [] @@ -376,39 +371,39 @@ def QPizzaNum(node: Node, params: QueryStateDict, result: Result) -> None: result.number = number -def QPizzaSizeLarge(node: Node, params: QueryStateDict, result: Result) -> None: +def QPizzaSizeLarge(node: Node, params: ParamList, result: Result) -> None: result.pizza_size = "stór" -def QPizzaSizeMedium(node: Node, params: QueryStateDict, result: Result) -> None: +def QPizzaSizeMedium(node: Node, params: ParamList, result: Result) -> None: result.pizza_size = "miðstærð" -def QPizzaMediumWord(node: Node, params: QueryStateDict, result: Result) -> None: +def QPizzaMediumWord(node: Node, params: ParamList, result: Result) -> None: result.pizza_size = "miðstærð" -def QPizzaSizeSmall(node: Node, params: QueryStateDict, result: Result) -> None: +def QPizzaSizeSmall(node: Node, params: ParamList, result: Result) -> None: result.pizza_size = "lítil" -def QPizzaCrustType(node: Node, params: QueryStateDict, result: Result) -> None: +def QPizzaCrustType(node: Node, params: ParamList, result: Result) -> None: result.crust = result._root -def QPizzaPepperoni(node: Node, params: QueryStateDict, result: Result) -> None: +def QPizzaPepperoni(node: Node, params: ParamList, result: Result) -> None: result.real_name = "pepperóní" -def QPizzaOlive(node: Node, params: QueryStateDict, result: Result) -> None: +def QPizzaOlive(node: Node, params: ParamList, result: Result) -> None: result.real_name = "ólífur" -def QPizzaMushroom(node: Node, params: QueryStateDict, result: Result) -> None: +def QPizzaMushroom(node: Node, params: ParamList, result: Result) -> None: result.real_name = "sveppir" -def QPizzaNo(node: Node, params: QueryStateDict, result: Result) -> None: +def QPizzaNo(node: Node, params: ParamList, result: Result) -> None: dsm: DialogueStateManager = Query.get_dsm(result) resource: WrapperResource = cast(WrapperResource, dsm.current_resource) print("No resource: ", resource.name) @@ -417,7 +412,7 @@ def QPizzaNo(node: Node, params: QueryStateDict, result: Result) -> None: dsm.set_resource_state("Final", ResourceState.CONFIRMED) -def QPizzaStatus(node: Node, params: QueryStateDict, result: Result) -> None: +def QPizzaStatus(node: Node, params: ParamList, result: Result) -> None: result.qtype = "QPizzaStatus" dsm: DialogueStateManager = Query.get_dsm(result) at = dsm.get_answer(_ANSWERING_FUNCTIONS, result) diff --git a/queries/places.py b/queries/places.py index 9bc65325..29bf3a24 100755 --- a/queries/places.py +++ b/queries/places.py @@ -36,7 +36,6 @@ from datetime import datetime from reynir import NounPhrase -from iceaddr import nearest_addr, nearest_placenames from geo import in_iceland, iceprep_for_street, LatLonTuple from query import Query, QueryStateDict @@ -49,7 +48,7 @@ read_grammar_file, ) from queries.num import numbers_to_text -from tree import Result, Node +from tree import ParamList, Result, Node _PLACES_QTYPE = "Places" @@ -90,27 +89,27 @@ def _fix_placename(pn: str) -> str: return _PLACENAME_MAP.get(p, p) -def QPlacesQuery(node: Node, params: QueryStateDict, result: Result) -> None: +def QPlacesQuery(node: Node, params: ParamList, result: Result) -> None: result["qtype"] = _PLACES_QTYPE -def QPlacesOpeningHours(node: Node, params: QueryStateDict, result: Result) -> None: +def QPlacesOpeningHours(node: Node, params: ParamList, result: Result) -> None: result["qkey"] = "OpeningHours" -def QPlacesIsOpen(node: Node, params: QueryStateDict, result: Result) -> None: +def QPlacesIsOpen(node: Node, params: ParamList, result: Result) -> None: result["qkey"] = "IsOpen" -def QPlacesIsClosed(node: Node, params: QueryStateDict, result: Result) -> None: +def QPlacesIsClosed(node: Node, params: ParamList, result: Result) -> None: result["qkey"] = "IsClosed" -def QPlacesAddress(node: Node, params: QueryStateDict, result: Result) -> None: +def QPlacesAddress(node: Node, params: ParamList, result: Result) -> None: result["qkey"] = "PlaceAddress" -def QPlacesSubject(node: Node, params: QueryStateDict, result: Result) -> None: +def QPlacesSubject(node: Node, params: ParamList, result: Result) -> None: result["subject_nom"] = _fix_placename(result._nominative) diff --git a/queries/rand.py b/queries/rand.py index bfec9e67..796022d3 100755 --- a/queries/rand.py +++ b/queries/rand.py @@ -32,7 +32,7 @@ from queries import gen_answer, read_grammar_file from queries.arithmetic import add_num, terminal_num from queries.num import number_to_text -from tree import Result, Node +from tree import ParamList, Result, Node _RANDOM_QTYPE = "Random" @@ -67,27 +67,27 @@ def help_text(lemma: str) -> str: GRAMMAR = read_grammar_file("rand") -def QRandomQuery(node: Node, params: QueryStateDict, result: Result) -> None: +def QRandomQuery(node: Node, params: ParamList, result: Result) -> None: result.qtype = _RANDOM_QTYPE -def QRandomHeadsOrTails(node: Node, params: QueryStateDict, result: Result) -> None: +def QRandomHeadsOrTails(node: Node, params: ParamList, result: Result) -> None: result.action = "headstails" -def QRandomBetween(node: Node, params: QueryStateDict, result: Result) -> None: +def QRandomBetween(node: Node, params: ParamList, result: Result) -> None: result.action = "randbtwn" -def QRandomDieRoll(node: Node, params: QueryStateDict, result: Result) -> None: +def QRandomDieRoll(node: Node, params: ParamList, result: Result) -> None: result.action = "dieroll" -def QRandomDiceSides(node: Node, params: QueryStateDict, result: Result) -> None: +def QRandomDiceSides(node: Node, params: ParamList, result: Result) -> None: result.dice_sides = 6 -def QRandNumber(node: Node, params: QueryStateDict, result: Result) -> None: +def QRandNumber(node: Node, params: ParamList, result: Result) -> None: d = result.find_descendant(t_base="tala") if d: add_num(terminal_num(d), result) diff --git a/queries/sunpos.py b/queries/sunpos.py index 58cdb6ce..b35d34eb 100755 --- a/queries/sunpos.py +++ b/queries/sunpos.py @@ -28,7 +28,7 @@ from typing import Dict, List, Iterable, Tuple, Optional, Union, cast -from tree import Result, Node +from tree import ParamList, Result, Node from query import Query, QueryStateDict from queries import ( @@ -137,12 +137,12 @@ class _SOLAR_POSITIONS: } -def QSunQuery(node: Node, params: QueryStateDict, result: Result) -> None: +def QSunQuery(node: Node, params: ParamList, result: Result) -> None: # Set the query type result.qtype = _SUN_QTYPE -def QSunIsWillWas(node: Node, params: QueryStateDict, result: Result) -> None: +def QSunIsWillWas(node: Node, params: ParamList, result: Result) -> None: if result._nominative == "verður": result["will_be"] = True @@ -150,65 +150,65 @@ def QSunIsWillWas(node: Node, params: QueryStateDict, result: Result) -> None: ### QSunPositions ### -def QSunMiðnætti(node: Node, params: QueryStateDict, result: Result) -> None: +def QSunMiðnætti(node: Node, params: ParamList, result: Result) -> None: result["solar_position"] = _SOLAR_POSITIONS.MIÐNÆTTI -def QSunDögun(node: Node, params: QueryStateDict, result: Result) -> None: +def QSunDögun(node: Node, params: ParamList, result: Result) -> None: result["solar_position"] = _SOLAR_POSITIONS.DÖGUN -def QSunBirting(node: Node, params: QueryStateDict, result: Result) -> None: +def QSunBirting(node: Node, params: ParamList, result: Result) -> None: result["solar_position"] = _SOLAR_POSITIONS.BIRTING -def QSunSólris(node: Node, params: QueryStateDict, result: Result) -> None: +def QSunSólris(node: Node, params: ParamList, result: Result) -> None: result["solar_position"] = _SOLAR_POSITIONS.SÓLRIS -def QSunHádegi(node: Node, params: QueryStateDict, result: Result) -> None: +def QSunHádegi(node: Node, params: ParamList, result: Result) -> None: result["solar_position"] = _SOLAR_POSITIONS.HÁDEGI -def QSunSólarlag(node: Node, params: QueryStateDict, result: Result) -> None: +def QSunSólarlag(node: Node, params: ParamList, result: Result) -> None: result["solar_position"] = _SOLAR_POSITIONS.SÓLARLAG -def QSunMyrkur(node: Node, params: QueryStateDict, result: Result) -> None: +def QSunMyrkur(node: Node, params: ParamList, result: Result) -> None: result["solar_position"] = _SOLAR_POSITIONS.MYRKUR -def QSunDagsetur(node: Node, params: QueryStateDict, result: Result) -> None: +def QSunDagsetur(node: Node, params: ParamList, result: Result) -> None: result["solar_position"] = _SOLAR_POSITIONS.DAGSETUR -def QSunSólarhæð(node: Node, params: QueryStateDict, result: Result) -> None: +def QSunSólarhæð(node: Node, params: ParamList, result: Result) -> None: result["solar_position"] = _SOLAR_POSITIONS.SÓLARHÆÐ ### QSunDates ### -def QSunToday(node: Node, params: QueryStateDict, result: Result) -> None: +def QSunToday(node: Node, params: ParamList, result: Result) -> None: result["date"] = datetime.date.today() -def QSunYesterday(node: Node, params: QueryStateDict, result: Result) -> None: +def QSunYesterday(node: Node, params: ParamList, result: Result) -> None: result["date"] = datetime.date.today() - datetime.timedelta(days=1) -def QSunTomorrow(node: Node, params: QueryStateDict, result: Result) -> None: +def QSunTomorrow(node: Node, params: ParamList, result: Result) -> None: result["date"] = datetime.date.today() + datetime.timedelta(days=1) ### QSunLocation ### -def QSunCapitalRegion(node: Node, params: QueryStateDict, result: Result) -> None: +def QSunCapitalRegion(node: Node, params: ParamList, result: Result) -> None: result["city"] = "Reykjavík" -def QSunArbitraryLocation(node: Node, params: QueryStateDict, result: Result) -> None: +def QSunArbitraryLocation(node: Node, params: ParamList, result: Result) -> None: result["city"] = capitalize_placename(result._nominative) diff --git a/queries/theater.py b/queries/theater.py index 5bc41800..725bb4ca 100644 --- a/queries/theater.py +++ b/queries/theater.py @@ -21,7 +21,7 @@ This query module handles dialogue related to theater tickets. """ -from typing import Any, Dict, List, Optional, Set, Tuple, cast +from typing import Any, Dict, List, Optional, Tuple, cast from typing_extensions import TypedDict import json import logging @@ -30,7 +30,7 @@ from settings import changedlocale from query import Query, QueryStateDict -from tree import Result, Node, TerminalNode +from tree import ParamList, Result, Node, TerminalNode from queries import ( AnswerTuple, gen_answer, @@ -502,18 +502,18 @@ def _generate_final_answer( return (dict(answer=text_ans), text_ans, voice_ans) -def QTheaterDialogue(node: Node, params: QueryStateDict, result: Result) -> None: +def QTheaterDialogue(node: Node, params: ParamList, result: Result) -> None: if "qtype" not in result: result.qtype = _THEATER_QTYPE -def QTheaterHotWord(node: Node, params: QueryStateDict, result: Result) -> None: +def QTheaterHotWord(node: Node, params: ParamList, result: Result) -> None: result.qtype = _START_DIALOGUE_QTYPE print("ACTIVATING THEATER MODULE") Query.get_dsm(result).hotword_activated() -def QTheaterShowQuery(node: Node, params: QueryStateDict, result: Result) -> None: +def QTheaterShowQuery(node: Node, params: ParamList, result: Result) -> None: dsm: DialogueStateManager = Query.get_dsm(result) selected_show: str = result.show_name resource: ListResource = cast(ListResource, dsm.get_resource("Show")) @@ -539,7 +539,7 @@ def QTheaterShowQuery(node: Node, params: QueryStateDict, result: Result) -> Non # result.no_show_matched_data_exists = True -def QTheaterShowPrice(node: Node, params: QueryStateDict, result: Result) -> None: +def QTheaterShowPrice(node: Node, params: ParamList, result: Result) -> None: dsm = Query.get_dsm(result) show = dsm.get_resource("Show") if show.is_confirmed: @@ -558,7 +558,7 @@ def QTheaterShowPrice(node: Node, params: QueryStateDict, result: Result) -> Non dsm.set_answer(gen_answer("Þú hefur ekki valið sýningu.")) -def QTheaterShowLength(node: Node, params: QueryStateDict, result: Result) -> None: +def QTheaterShowLength(node: Node, params: ParamList, result: Result) -> None: dsm = Query.get_dsm(result) show = dsm.get_resource("Show") if show.is_confirmed: @@ -664,7 +664,7 @@ def _add_time( # result.no_time_matched = True -def QTheaterDateTime(node: Node, params: QueryStateDict, result: Result) -> None: +def QTheaterDateTime(node: Node, params: ParamList, result: Result) -> None: datetimenode = node.first_child(lambda n: True) assert isinstance(datetimenode, TerminalNode) now = datetime.datetime.now() @@ -690,7 +690,7 @@ def QTheaterDateTime(node: Node, params: QueryStateDict, result: Result) -> None _add_time(cast(TimeResource, dsm.get_resource("ShowTime")), dsm, result) -def QTheaterDate(node: Node, params: QueryStateDict, result: Result) -> None: +def QTheaterDate(node: Node, params: ParamList, result: Result) -> None: datenode = node.first_child(lambda n: True) assert isinstance(datenode, TerminalNode) cdate = datenode.contained_date @@ -712,7 +712,7 @@ def QTheaterDate(node: Node, params: QueryStateDict, result: Result) -> None: raise ValueError("No date in {0}".format(str(datenode))) -def QTheaterTime(node: Node, params: QueryStateDict, result: Result) -> None: +def QTheaterTime(node: Node, params: ParamList, result: Result) -> None: # Extract time from time terminal nodes tnode = cast(TerminalNode, node.first_child(lambda n: n.has_t_base("tími"))) if tnode: @@ -728,7 +728,7 @@ def QTheaterTime(node: Node, params: QueryStateDict, result: Result) -> None: _add_time(cast(TimeResource, dsm.get_resource("ShowTime")), dsm, result) -def QTheaterMoreDates(node: Node, params: QueryStateDict, result: Result) -> None: +def QTheaterMoreDates(node: Node, params: ParamList, result: Result) -> None: dsm: DialogueStateManager = Query.get_dsm(result) if dsm.current_resource.name == "ShowDate": extras: Dict[str, Any] = dsm.extras @@ -738,7 +738,7 @@ def QTheaterMoreDates(node: Node, params: QueryStateDict, result: Result) -> Non extras["page_index"] = 3 -def QTheaterPreviousDates(node: Node, params: QueryStateDict, result: Result) -> None: +def QTheaterPreviousDates(node: Node, params: ParamList, result: Result) -> None: dsm: DialogueStateManager = Query.get_dsm(result) if dsm.current_resource.name == "ShowDate": extras: Dict[str, Any] = dsm.extras @@ -748,9 +748,7 @@ def QTheaterPreviousDates(node: Node, params: QueryStateDict, result: Result) -> extras["page_index"] = 0 -def QTheaterShowSeatCountQuery( - node: Node, params: QueryStateDict, result: Result -) -> None: +def QTheaterShowSeatCountQuery(node: Node, params: ParamList, result: Result) -> None: dsm: DialogueStateManager = Query.get_dsm(result) if dsm.get_resource("ShowDateTime").is_confirmed: resource: NumberResource = cast( @@ -789,7 +787,7 @@ def _add_available_rows_to_result( result.text_available_rows = text_available_rows -def QTheaterShowRow(node: Node, params: QueryStateDict, result: Result) -> None: +def QTheaterShowRow(node: Node, params: ParamList, result: Result) -> None: dsm: DialogueStateManager = Query.get_dsm(result) if dsm.get_resource("ShowSeatCount").is_confirmed: title: str = dsm.get_resource("Show").data[0] @@ -824,7 +822,7 @@ def QTheaterShowRow(node: Node, params: QueryStateDict, result: Result) -> None: dsm.set_answer((dict(answer=text_ans), text_ans, voice_ans)) -def QTheaterShowSeats(node: Node, params: QueryStateDict, result: Result) -> None: +def QTheaterShowSeats(node: Node, params: ParamList, result: Result) -> None: dsm: DialogueStateManager = Query.get_dsm(result) if dsm.get_resource("ShowSeatRow").is_confirmed: resource: ListResource = cast(ListResource, dsm.get_resource("ShowSeatNumber")) @@ -878,7 +876,7 @@ def QTheaterShowSeats(node: Node, params: QueryStateDict, result: Result) -> Non dsm.set_resource_state(resource.name, ResourceState.FULFILLED) -def QTheaterGeneralOptions(node: Node, params: QueryStateDict, result: Result) -> None: +def QTheaterGeneralOptions(node: Node, params: ParamList, result: Result) -> None: result.options_info = True dsm: DialogueStateManager = Query.get_dsm(result) resource = dsm.current_resource @@ -892,7 +890,7 @@ def QTheaterGeneralOptions(node: Node, params: QueryStateDict, result: Result) - QTheaterSeatOptions(node, params, result) -def QTheaterShowOptions(node: Node, params: QueryStateDict, result: Result) -> None: +def QTheaterShowOptions(node: Node, params: ParamList, result: Result) -> None: # result.show_options = True dsm: DialogueStateManager = Query.get_dsm(result) if result.get("options_info"): @@ -911,7 +909,7 @@ def QTheaterShowOptions(node: Node, params: QueryStateDict, result: Result) -> N dsm.set_answer((dict(answer=text_ans), text_ans, voice_ans)) -def QTheaterDateOptions(node: Node, params: QueryStateDict, result: Result) -> None: +def QTheaterDateOptions(node: Node, params: ParamList, result: Result) -> None: # result.date_options = True dsm: DialogueStateManager = Query.get_dsm(result) if result.get("options_info"): @@ -979,7 +977,7 @@ def QTheaterDateOptions(node: Node, params: QueryStateDict, result: Result) -> N ) -def QTheaterRowOptions(node: Node, params: QueryStateDict, result: Result) -> None: +def QTheaterRowOptions(node: Node, params: ParamList, result: Result) -> None: # result.row_options = True dsm: DialogueStateManager = Query.get_dsm(result) if result.get("options_info"): @@ -1019,7 +1017,7 @@ def QTheaterRowOptions(node: Node, params: QueryStateDict, result: Result) -> No dsm.set_answer((dict(answer=text_ans), text_ans, voice_ans)) -def QTheaterSeatOptions(node: Node, params: QueryStateDict, result: Result) -> None: +def QTheaterSeatOptions(node: Node, params: ParamList, result: Result) -> None: result.seat_options = True dsm: DialogueStateManager = Query.get_dsm(result) if result.get("options_info"): @@ -1052,7 +1050,7 @@ def QTheaterSeatOptions(node: Node, params: QueryStateDict, result: Result) -> N dsm.set_answer((dict(answer=text_ans), text_ans, voice_ans)) -def QTheaterShowName(node: Node, params: QueryStateDict, result: Result) -> None: +def QTheaterShowName(node: Node, params: ParamList, result: Result) -> None: result.show_name = ( " ".join(result._text.split()[1:]) if result._text.startswith("sýning") @@ -1060,7 +1058,7 @@ def QTheaterShowName(node: Node, params: QueryStateDict, result: Result) -> None ) -def QTheaterNum(node: Node, params: QueryStateDict, result: Result): +def QTheaterNum(node: Node, params: ParamList, result: Result): number: int = int(parse_num(node, result._nominative)) if "numbers" not in result: result["numbers"] = [] @@ -1073,7 +1071,7 @@ def QTheaterNum(node: Node, params: QueryStateDict, result: Result): QTheaterShowSeatsNum = QTheaterNum -def QTheaterCancel(node: Node, params: QueryStateDict, result: Result): +def QTheaterCancel(node: Node, params: ParamList, result: Result): dsm: DialogueStateManager = Query.get_dsm(result) dsm.set_resource_state("Final", ResourceState.CANCELLED) dsm.set_answer(gen_answer(dsm.get_resource("Final").prompts["cancelled"])) @@ -1082,7 +1080,7 @@ def QTheaterCancel(node: Node, params: QueryStateDict, result: Result): result.qtype = "QTheaterCancel" -def QTheaterYes(node: Node, params: QueryStateDict, result: Result): +def QTheaterYes(node: Node, params: ParamList, result: Result): dsm: DialogueStateManager = Query.get_dsm(result) current_resource = dsm.current_resource if ( @@ -1102,7 +1100,7 @@ def QTheaterYes(node: Node, params: QueryStateDict, result: Result): dsm.get_resource(rname).state = ResourceState.CONFIRMED -def QTheaterNo(node: Node, params: QueryStateDict, result: Result): +def QTheaterNo(node: Node, params: ParamList, result: Result): dsm: DialogueStateManager = Query.get_dsm(result) resource = dsm.current_resource if ( @@ -1122,7 +1120,7 @@ def QTheaterNo(node: Node, params: QueryStateDict, result: Result): dsm.set_resource_state(rname, ResourceState.UNFULFILLED) -def QTheaterStatus(node: Node, params: QueryStateDict, result: Result): +def QTheaterStatus(node: Node, params: ParamList, result: Result): # TODO: Handle QTheaterStatus again with dsm in query result.qtype = "QTheaterStatus" dsm: DialogueStateManager = Query.get_dsm(result) diff --git a/queries/unit.py b/queries/unit.py index a79f6eef..fe77b5a8 100755 --- a/queries/unit.py +++ b/queries/unit.py @@ -36,7 +36,7 @@ from query import Query, QueryStateDict, to_dative, to_accusative from queries import iceformat_float, parse_num, read_grammar_file -from tree import Result, Node +from tree import ParamList, Result, Node # Lemmas of keywords that could indicate that the user is trying to use this module @@ -216,17 +216,17 @@ def help_text(lemma: str) -> str: GRAMMAR = read_grammar_file("unit") -def QUnitConversion(node: Node, params: QueryStateDict, result: Result) -> None: +def QUnitConversion(node: Node, params: ParamList, result: Result) -> None: """Unit conversion query""" result.qtype = "Unit" result.qkey = result.unit_to -def QUnitNumber(node: Node, params: QueryStateDict, result: Result) -> None: +def QUnitNumber(node: Node, params: ParamList, result: Result) -> None: result.number = parse_num(node, result._canonical) -def QUnit(node: Node, params: QueryStateDict, result: Result) -> None: +def QUnit(node: Node, params: ParamList, result: Result) -> None: # Unit in canonical (nominative, singular, indefinite) form unit = result._canonical.lower() # Convert irregular forms ('mílu', 'bollum') to canonical ones @@ -235,14 +235,14 @@ def QUnit(node: Node, params: QueryStateDict, result: Result) -> None: result.unit_nf = result._nominative.lower() -def QUnitTo(node: Node, params: QueryStateDict, result: Result) -> None: +def QUnitTo(node: Node, params: ParamList, result: Result) -> None: result.unit_to = result.unit result.unit_to_nf = result.unit_nf del result["unit"] del result["unit_nf"] -def QUnitFrom(node: Node, params: QueryStateDict, result: Result) -> None: +def QUnitFrom(node: Node, params: ParamList, result: Result) -> None: if "unit" in result: result.unit_from = result.unit result.unit_from_nf = result.unit_nf @@ -251,7 +251,7 @@ def QUnitFrom(node: Node, params: QueryStateDict, result: Result) -> None: del result["unit_nf"] -def QUnitFromPounds(node: Node, params: QueryStateDict, result: Result) -> None: +def QUnitFromPounds(node: Node, params: ParamList, result: Result) -> None: """Special hack for the case of '150 pund' which is tokenized as an amount token""" amount = node.first_child(lambda n: n.has_t_base("amount")) @@ -266,7 +266,7 @@ def QUnitFromPounds(node: Node, params: QueryStateDict, result: Result) -> None: result._nominative = str(result.number).replace(".", ",") + " pund" -def _convert(quantity: float, unit_from: str, unit_to: str) -> Tuple: +def _convert(quantity: float, unit_from: str, unit_to: str) -> Tuple[bool, float, str, float]: """Converts a quantity from unit_from to unit_to, returning a tuple of: valid, result, si_unit, si_quantity""" u_from, factor_from = _UNITS[unit_from] diff --git a/queries/userloc.py b/queries/userloc.py index 26ae2281..27a77d65 100755 --- a/queries/userloc.py +++ b/queries/userloc.py @@ -37,14 +37,12 @@ read_grammar_file, ) from queries.num import numbers_to_text -from tree import Result, Node +from tree import ParamList, Result, Node from iceaddr import iceaddr_lookup, postcodes # type: ignore from geo import ( iceprep_for_placename, iceprep_for_street, - iceprep_for_cc, in_iceland, - country_name_for_isocode, LatLonTuple, ) @@ -62,19 +60,19 @@ GRAMMAR = read_grammar_file("userloc") -def QUserLocationQuery(node: Node, params: QueryStateDict, result: Result) -> None: +def QUserLocationQuery(node: Node, params: ParamList, result: Result) -> None: result.qtype = _LOC_QTYPE -def QUserLocationCurrent(node: Node, params: QueryStateDict, result: Result) -> None: +def QUserLocationCurrent(node: Node, params: ParamList, result: Result) -> None: result.qkey = "CurrentLocation" -def QUserLocationPostcode(node: Node, params: QueryStateDict, result: Result) -> None: +def QUserLocationPostcode(node: Node, params: ParamList, result: Result) -> None: result.qkey = "CurrentPostcode" -def QUserLocationCountry(node: Node, params: QueryStateDict, result: Result) -> None: +def QUserLocationCountry(node: Node, params: ParamList, result: Result) -> None: result.qkey = "CurrentCountry" diff --git a/queries/weather.py b/queries/weather.py index c32355ff..ba8848cd 100755 --- a/queries/weather.py +++ b/queries/weather.py @@ -61,12 +61,11 @@ AnswerTuple, read_grammar_file, ) -from tree import Result, Node +from tree import ParamList, Result, Node from geo import in_iceland, RVK_COORDS, near_capital_region, ICE_PLACENAME_BLACKLIST from iceaddr import placename_lookup # type: ignore from iceweather import observation_for_closest, observation_for_station, forecast_text # type: ignore -from queries.num import number_to_text _WEATHER_QTYPE = "Weather" @@ -429,56 +428,56 @@ def get_umbrella_answer(query: Query, result: Result) -> Optional[AnswerTuple]: return None -def QWeather(node: Node, params: QueryStateDict, result: Result) -> None: +def QWeather(node: Node, params: ParamList, result: Result) -> None: result.qtype = _WEATHER_QTYPE -def QWeatherCapitalRegion(node: Node, params: QueryStateDict, result: Result) -> None: +def QWeatherCapitalRegion(node: Node, params: ParamList, result: Result) -> None: result["location"] = "capital" -def QWeatherCountry(node: Node, params: QueryStateDict, result: Result) -> None: +def QWeatherCountry(node: Node, params: ParamList, result: Result) -> None: result["location"] = "general" -def QWeatherOpenLoc(node: Node, params: QueryStateDict, result: Result) -> None: +def QWeatherOpenLoc(node: Node, params: ParamList, result: Result) -> None: """Store preposition and placename to use in voice description, e.g. "Á Raufarhöfn" """ result["subject"] = result._node.contained_text().title() -def Nl(node: Node, params: QueryStateDict, result: Result) -> None: +def Nl(node: Node, params: ParamList, result: Result) -> None: """Noun phrase containing name of specific location""" result["location"] = cap_first(result._nominative) -def EfLiður(node: Node, params: QueryStateDict, result: Result) -> None: +def EfLiður(node: Node, params: ParamList, result: Result) -> None: """Don't change the case of possessive clauses""" result._nominative = result._text -def FsMeðFallstjórn(node: Node, params: QueryStateDict, result: Result) -> None: +def FsMeðFallstjórn(node: Node, params: ParamList, result: Result) -> None: """Don't change the case of prepositional clauses""" result._nominative = result._text -def QWeatherCurrent(node: Node, params: QueryStateDict, result: Result) -> None: +def QWeatherCurrent(node: Node, params: ParamList, result: Result) -> None: result.qkey = "CurrentWeather" -def QWeatherWind(node: Node, params: QueryStateDict, result: Result) -> None: +def QWeatherWind(node: Node, params: ParamList, result: Result) -> None: result.qkey = "CurrentWeather" -def QWeatherForecast(node: Node, params: QueryStateDict, result: Result) -> None: +def QWeatherForecast(node: Node, params: ParamList, result: Result) -> None: result.qkey = "WeatherForecast" -def QWeatherTemperature(node: Node, params: QueryStateDict, result: Result) -> None: +def QWeatherTemperature(node: Node, params: ParamList, result: Result) -> None: result.qkey = "CurrentWeather" -def QWeatherUmbrella(node: Node, params: QueryStateDict, result: Result) -> None: +def QWeatherUmbrella(node: Node, params: ParamList, result: Result) -> None: result.qkey = "Umbrella" diff --git a/queries/wiki.py b/queries/wiki.py index af1a3540..a43a6823 100755 --- a/queries/wiki.py +++ b/queries/wiki.py @@ -37,7 +37,7 @@ from queries import query_json_api, gen_answer, cap_first, read_grammar_file from query import Query, QueryStateDict, ContextDict -from tree import Result, Node +from tree import ParamList, Result, Node _WIKI_QTYPE = "Wikipedia" @@ -113,7 +113,7 @@ def help_text(lemma: str) -> str: ) -def QWikiQuery(node: Node, params: QueryStateDict, result: Result) -> None: +def QWikiQuery(node: Node, params: ParamList, result: Result) -> None: # Set the query type result.qtype = _WIKI_QTYPE result.qkey = result.get("subject_nom") @@ -121,13 +121,13 @@ def QWikiQuery(node: Node, params: QueryStateDict, result: Result) -> None: result["explicit_wikipedia"] = True -def QWikiWhatIsQuery(node: Node, params: QueryStateDict, result: Result) -> None: +def QWikiWhatIsQuery(node: Node, params: ParamList, result: Result) -> None: # Set the query type result.qtype = _WIKI_QTYPE result.qkey = result.get("subject_nom") -def QWikiSubjectNlNf(node: Node, params: QueryStateDict, result: Result) -> None: +def QWikiSubjectNlNf(node: Node, params: ParamList, result: Result) -> None: result["subject_nom"] = result._nominative @@ -135,7 +135,7 @@ def QWikiSubjectNlNf(node: Node, params: QueryStateDict, result: Result) -> None QWikiSubjectNlÞf = QWikiSubjectNlÞgf = QWikiSubjectNlNf -def QWikiPrevSubjectNf(node: Node, params: QueryStateDict, result: Result) -> None: +def QWikiPrevSubjectNf(node: Node, params: ParamList, result: Result) -> None: """Reference to previous result, usually via personal pronouns ('Hvað segir Wikipedía um hann/hana/það?').""" q: Optional[Query] = result.state.get("query") @@ -157,12 +157,12 @@ def QWikiPrevSubjectNf(node: Node, params: QueryStateDict, result: Result) -> No QWikiPrevSubjectÞgf = QWikiPrevSubjectÞf = QWikiPrevSubjectNf -def EfLiður(node: Node, params: QueryStateDict, result: Result) -> None: +def EfLiður(node: Node, params: ParamList, result: Result) -> None: """Don't change the case of possessive clauses""" result._nominative = result._text -def FsMeðFallstjórn(node: Node, params: QueryStateDict, result: Result) -> None: +def FsMeðFallstjórn(node: Node, params: ParamList, result: Result) -> None: """Don't change the case of prepositional clauses""" result._nominative = result._text diff --git a/queries/yulelads.py b/queries/yulelads.py index 0eea4dbc..1d1fbdc8 100755 --- a/queries/yulelads.py +++ b/queries/yulelads.py @@ -29,7 +29,7 @@ from datetime import datetime from query import Query, QueryStateDict -from tree import Result, Node, TerminalNode +from tree import ParamList, Result, Node, TerminalNode from queries import read_grammar_file from queries.num import numbers_to_ordinal @@ -148,34 +148,34 @@ def help_text(lemma: str) -> str: ) -def QYuleDate(node: Node, params: QueryStateDict, result: Result) -> None: +def QYuleDate(node: Node, params: ParamList, result: Result) -> None: """Query for date when a particular yule lad appears""" result.qtype = "YuleDate" result.qkey = result.yule_lad -def QYuleLad(node: Node, params: QueryStateDict, result: Result) -> None: +def QYuleLad(node: Node, params: ParamList, result: Result) -> None: """Query for which yule lad appears on a particular date""" result.qtype = "YuleLad" result.qkey = str(result.lad_date) -def QYuleLadFirst(node: Node, params: QueryStateDict, result: Result) -> None: +def QYuleLadFirst(node: Node, params: ParamList, result: Result) -> None: result.yule_lad = "Stekkjarstaur" result.lad_date = 12 -def QYuleLadLast(node: Node, params: QueryStateDict, result: Result) -> None: +def QYuleLadLast(node: Node, params: ParamList, result: Result) -> None: result.yule_lad = "Kertasníkir" result.lad_date = 24 -def QYuleLadName(node: Node, params: QueryStateDict, result: Result) -> None: +def QYuleLadName(node: Node, params: ParamList, result: Result) -> None: result.yule_lad = result._nominative result.lad_date = _YULE_LADS_BY_NAME[result.yule_lad] -def QYuleNumberOrdinal(node: Node, params: QueryStateDict, result: Result) -> None: +def QYuleNumberOrdinal(node: Node, params: ParamList, result: Result) -> None: ordinal = node.first_child(lambda n: True) if ordinal is not None: result.lad_date = int(ordinal.contained_number or 0) @@ -190,7 +190,7 @@ def QYuleNumberOrdinal(node: Node, params: QueryStateDict, result: Result) -> No result.invalid_date = True -def QYuleValidOrdinal(node: Node, params: QueryStateDict, result: Result) -> None: +def QYuleValidOrdinal(node: Node, params: ParamList, result: Result) -> None: result.lad_date = _ORDINAL_TO_DATE[result._text] if 11 <= result.lad_date <= 23: # If asking about December 11, reply with the @@ -199,23 +199,23 @@ def QYuleValidOrdinal(node: Node, params: QueryStateDict, result: Result) -> Non result.yule_lad = _YULE_LADS_BY_DATE.get(result.lad_date) -def QYuleInvalidOrdinal(node: Node, params: QueryStateDict, result: Result) -> None: +def QYuleInvalidOrdinal(node: Node, params: ParamList, result: Result) -> None: result.lad_date = _ORDINAL_TO_DATE[result._text] result.yule_lad = None result.invalid_date = True -def QYuleDay23(node: Node, params: QueryStateDict, result: Result) -> None: +def QYuleDay23(node: Node, params: ParamList, result: Result) -> None: result.lad_date = 24 # Yes, correct result.yule_lad = _YULE_LADS_BY_DATE.get(result.lad_date) -def QYuleDay24(node: Node, params: QueryStateDict, result: Result) -> None: +def QYuleDay24(node: Node, params: ParamList, result: Result) -> None: result.lad_date = 24 # Yes, correct result.yule_lad = _YULE_LADS_BY_DATE.get(result.lad_date) -def QYuleToday(node: Node, params: QueryStateDict, result: Result) -> None: +def QYuleToday(node: Node, params: ParamList, result: Result) -> None: result.yule_lad = None result.lad_date = datetime.utcnow().day if not (11 <= result.lad_date <= 24): @@ -228,7 +228,7 @@ def QYuleToday(node: Node, params: QueryStateDict, result: Result) -> None: result.yule_lad = _YULE_LADS_BY_DATE.get(result.lad_date) -def QYuleTomorrow(node: Node, params: QueryStateDict, result: Result) -> None: +def QYuleTomorrow(node: Node, params: ParamList, result: Result) -> None: result.yule_lad = None result.lad_date = datetime.utcnow().day + 1 if not (11 <= result.lad_date <= 24): @@ -241,11 +241,11 @@ def QYuleTomorrow(node: Node, params: QueryStateDict, result: Result) -> None: result.yule_lad = _YULE_LADS_BY_DATE.get(result.lad_date) -def QYuleTwentyPart(node: Node, params: QueryStateDict, result: Result) -> None: +def QYuleTwentyPart(node: Node, params: ParamList, result: Result) -> None: result.twenty_part = _TWENTY_PART[result._text] -def QYuleTwentyOrdinal(node: Node, params: QueryStateDict, result: Result) -> None: +def QYuleTwentyOrdinal(node: Node, params: ParamList, result: Result) -> None: result.yule_lad = None result.lad_date = 0 num_node = node.first_child(lambda n: True) @@ -267,7 +267,7 @@ def QYuleTwentyOrdinal(node: Node, params: QueryStateDict, result: Result) -> No result.yule_lad = _YULE_LADS_BY_DATE.get(result.lad_date) -def QYuleDateRel(node: Node, params: QueryStateDict, result: Result) -> None: +def QYuleDateRel(node: Node, params: ParamList, result: Result) -> None: result.yule_lad = None daterel = node.first_child(lambda n: True) if daterel is not None: From 662f8a8d74cfc3dde89689bf9d7809db0f9136d6 Mon Sep 17 00:00:00 2001 From: Logi E Date: Thu, 22 Sep 2022 14:50:22 +0000 Subject: [PATCH 360/371] working on improved serialization of dialogue data --- db/models.py | 3 +- queries/extras/dialogue.py | 91 ++++++--- queries/extras/dialogue_db.py | 149 +++++++++++++++ queries/extras/resources.py | 267 +++++++++++++-------------- queries/fruitseller.py | 60 ++---- queries/grammars/fruitseller.grammar | 6 +- queries/theater.py | 14 +- query.py | 121 ++++-------- requirements.txt | 1 + 9 files changed, 405 insertions(+), 307 deletions(-) create mode 100644 queries/extras/dialogue_db.py diff --git a/db/models.py b/db/models.py index e632bcd0..8c8f9c78 100755 --- a/db/models.py +++ b/db/models.py @@ -23,13 +23,12 @@ """ -from db import Session from typing import Any, Optional, cast from datetime import datetime from sqlalchemy import text from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import relationship, backref +from sqlalchemy.orm import relationship, backref, Session from sqlalchemy import ( Column, Integer, diff --git a/queries/extras/dialogue.py b/queries/extras/dialogue.py index 09e48bcf..7058fc9c 100644 --- a/queries/extras/dialogue.py +++ b/queries/extras/dialogue.py @@ -28,11 +28,13 @@ Set, List, Optional, + Union, cast, ) from typing_extensions import TypedDict import json +import logging import datetime from pathlib import Path @@ -41,7 +43,11 @@ except ModuleNotFoundError: import tomli as tomllib # Used for Python <3.11 +from db import SessionContext +from db.models import DialogueData + import queries.extras.resources as res +import queries.extras.dialogue_db as dialogue_db from queries import AnswerTuple @@ -54,7 +60,7 @@ # TODO: Add "needs_confirmation" to TOML files (skip fulfilled, go straight to confirmed) _TOML_FOLDER_NAME = "dialogues" -_DEFAULT_EXPIRATION_TIME = 30 * 60 # a dialogue expires after 30 minutes +_DEFAULT_EXPIRATION_TIME = 30 * 60 # By default a dialogue expires after 30 minutes _FINAL_RESOURCE_NAME = "Final" # Functions for generating prompts/answers @@ -62,20 +68,14 @@ AnsweringFunctionType = Callable[..., Optional[AnswerTuple]] # Difficult to type this correctly as the -# Callable type is contravariant in the parameters +# Callable type is contravariant in its arguments parameter AnsweringFunctionMap = Mapping[str, AnsweringFunctionType] +# Filter functions for filtering nodes +# when searching resource graph FilterFuncType = Callable[[res.Resource, int], bool] _ALLOW_ALL_FILTER: FilterFuncType = lambda r, i: True -################################ -# DIALOGUE STATE MANAGER # -################################ - - -class ResourceNotFoundError(Exception): - ... - class ResourceGraphItem(TypedDict): """Type for a node in the resource graph.""" @@ -104,28 +104,27 @@ class DialogueTOMLStructure(TypedDict, total=False): _EXPIRATION_TIME_KEY = "expiration_time" -# Dialogue data -DialogueDataDict = Dict[str, str] +# List of active dialogues, kept in querydata table +ActiveDialogueList = List[str] -class DialogueDBStructure(TypedDict): - """ - Representation of the dialogue structure, - as it is saved to the database. - """ +class ResourceNotFoundError(Exception): + ... - resources: Dict[str, res.Resource] - modified: datetime.datetime - extras: Dict[str, Any] + +################################ +# DIALOGUE STATE MANAGER # +################################ class DialogueStateManager: - DIALOGUE_DATA_KEY = "dialogue" + ACTIVE_DIALOGUE_KEY = "dialogue" - def __init__(self, dialogue_data: DialogueDataDict) -> None: - self._dialogue_data: DialogueDataDict = dialogue_data + def __init__(self, client_id: Optional[str]) -> None: + self._client_id = client_id def load_dialogue(self, dialogue_name: str): + # TODO: This should load dialogue data from dialoguedata table self._dialogue_name: str = dialogue_name # Dict mapping resource name to resource instance self._resources: Dict[str, res.Resource] = {} @@ -139,18 +138,16 @@ def load_dialogue(self, dialogue_name: str): self._current_resource: Optional[res.Resource] = None # Dependency graph for the resources self._resource_graph: ResourceGraph = {} - # Database data for this dialogue, if any - self._saved_state: Optional[DialogueDBStructure] = None # Whether this dialogue is finished (successful/cancelled) or not self._finished: bool = False self._expiration_time: int = _DEFAULT_EXPIRATION_TIME self._timed_out: bool = False self._initial_resource = None - dialogue_saved_state: Optional[str] = self._dialogue_data.get( - dialogue_name, None + dialogue_saved_state: Optional[DialogueDatabaseDict] = self.dialogue_data( + dialogue_name ) - if isinstance(dialogue_saved_state, str): + if dialogue_saved_state: self._saved_state = cast( DialogueDBStructure, json.loads(dialogue_saved_state, cls=res.DialogueJSONDecoder), @@ -218,7 +215,11 @@ def _initialize_resources(self, filename: str) -> None: _EXPIRATION_TIME_KEY, _DEFAULT_EXPIRATION_TIME ) else: - p = Path(__file__).parent.parent.resolve() / _TOML_FOLDER_NAME / f"{filename}.toml" + p = ( + Path(__file__).parent.parent.resolve() + / _TOML_FOLDER_NAME + / f"{filename}.toml" + ) f = p.read_text() # Read TOML file containing a list of resources for the dialogue obj: DialogueTOMLStructure = tomllib.loads(f) # type: ignore @@ -624,3 +625,35 @@ def serialize_data(self) -> Dict[str, Optional[str]]: # (due to custom JSON serialization) cd: Dict[str, Optional[str]] = {self._dialogue_name: ds_json} return cd + + ################################ + # Database functions # + ################################ + def dialogue_data( + self, dialogue_key: Optional[str] + ) -> Optional[DialogueDatabaseDict]: + """ + Fetch client_id-associated dialogue data stored + in the dialoguedata table based on the dialogue key + """ + if not self._client_id or not dialogue_key: + return None + with SessionContext(read_only=True) as session: + try: + dialogue_data = ( + session.query(DialogueData) + .filter(DialogueData.dialogue_key == dialogue_key) + .filter(DialogueData.client_id == self._client_id) + ).one_or_none() + return ( + None + if dialogue_data is None + else cast(DialogueDatabaseDict, dialogue_data.data) + ) + except Exception as e: + logging.error( + "Error fetching client '{0}' query data for key '{1}' from db: {2}".format( + self._client_id, dialogue_key, e + ) + ) + return None diff --git a/queries/extras/dialogue_db.py b/queries/extras/dialogue_db.py new file mode 100644 index 00000000..f4cf5e9b --- /dev/null +++ b/queries/extras/dialogue_db.py @@ -0,0 +1,149 @@ +""" + + Greynir: Natural language processing for Icelandic + + Copyright (C) 2022 Miðeind ehf. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see http://www.gnu.org/licenses/. + + + Serialization/Deserialization functions for dialogue data and resources. + +""" +from typing import Any, Callable, Dict, List, Mapping, Type, Union, cast +from typing_extensions import Required, TypedDict + +import datetime +import json +import logging + +import queries.extras.resources as res + +_json_types = Union[None, int, bool, str, List["_json_types"], Dict[str, "_json_types"]] + + +class ResourceSerialized(Dict[str, _json_types], TypedDict, total=False): + """ + Representation of a serialized resource + and the required keys. + """ + + name: Required[str] + type: Required[str] + state: Required[res.ResourceState] + + +class DialogueDeserialized(TypedDict): + """ + Representation of the dialogue structure, + after it is loaded from the database and parsed. + """ + + resources: List[res.Resource] + extras: Dict[str, _json_types] + + +class DialogueSerialized(TypedDict): + """ + Representation of the dialogue structure, + before it is saved to the database. + """ + + resources: List[ResourceSerialized] + extras: str + + +def dialogue_serializer(data: DialogueDeserialized) -> DialogueSerialized: + """ + Prepare the dialogue data for writing into the database. + """ + return { + "resources": [ + cast(ResourceSerialized, res.RESOURCE_SCHEMAS[s.type]().dump(s)) + for s in data["resources"] + ], + # We just dump the entire extras dict as a string + "extras": json.dumps(data["extras"], cls=ExtendedJSONEncoder), + } + + +def dialogue_deserializer(data: DialogueSerialized) -> DialogueDeserialized: + """ + Prepare the dialogue data for working with + after it has been loaded from the database. + """ + return { + "resources": [ + cast(res.Resource, res.RESOURCE_SCHEMAS[s["type"]]().load(s)) + for s in data["resources"] + ], + "extras": json.loads(data["extras"], cls=ExtendedJSONDecoder), + } + + +class ExtendedJSONEncoder(json.JSONEncoder): + # Map types other than resources to their serialized forms + _serializer_functions: Mapping[Type[Any], Callable[[Any], _json_types]] = { + datetime.datetime: lambda o: { + "__type__": "datetime", + "year": o.year, + "month": o.month, + "day": o.day, + "hour": o.hour, + "minute": o.minute, + "second": o.second, + "microsecond": o.microsecond, + }, + datetime.date: lambda o: { + "__type__": "date", + "year": o.year, + "month": o.month, + "day": o.day, + }, + datetime.time: lambda o: { + "__type__": "time", + "hour": o.hour, + "minute": o.minute, + "second": o.second, + "microsecond": o.microsecond, + }, + } + + def default(self, o: Any) -> Any: + f = ExtendedJSONEncoder._serializer_functions.get(type(o)) + return f(o) if f else json.JSONEncoder.default(self, o) + + +class ExtendedJSONDecoder(json.JSONDecoder): + _type_conversions: Mapping[str, Type[Any]] = { + "datetime": datetime.datetime, + "date": datetime.date, + "time": datetime.time, + } + + def __init__(self, *args: Any, **kwargs: Any): + json.JSONDecoder.__init__( + self, object_hook=self.dialogue_decoding, *args, **kwargs + ) + + def dialogue_decoding(self, d: Dict[Any, Any]) -> Any: + if "__type__" not in d: + return d + t = d.pop("__type__") + + c = self._type_conversions.get(t) + if c: + return c(**d) + logging.warning(f"No class found for __type__: {t}") + d["__type__"] = t + return d diff --git a/queries/extras/resources.py b/queries/extras/resources.py index 186e18c5..3419dca0 100644 --- a/queries/extras/resources.py +++ b/queries/extras/resources.py @@ -23,18 +23,21 @@ """ from typing import ( Any, - Callable, Dict, Mapping, List, + MutableMapping, Optional, Type, + Union, ) -import json import datetime from enum import IntFlag, auto -from dataclasses import dataclass, field +from dataclasses import dataclass, field as data_field +from marshmallow import Schema, fields, post_load + +_json_types = Union[None, int, bool, str, List["_json_types"], Dict[str, "_json_types"]] class ResourceState(IntFlag): @@ -60,6 +63,10 @@ class ResourceState(IntFlag): ) +# Map resource name to type (for encoding/decoding) +RESOURCE_MAP: MutableMapping[str, Type["Resource"]] = {} +RESOURCE_SCHEMAS: MutableMapping[str, Type["ResourceSchema"]] = {} + ########################## # RESOURCE CLASSES # ########################## @@ -75,15 +82,15 @@ class Resource: # Name of resource name: str = "" # Type (child class) of Resource - type: str = "" + type: str = "Resource" # Contained data data: Any = None # Resource state (unfulfilled, partially fulfilled, etc.) state: ResourceState = ResourceState.UNFULFILLED # Resources that must be confirmed before moving on to this resource - requires: List[str] = field(default_factory=list) + requires: List[str] = data_field(default_factory=list) # Dictionary containing different prompts/responses - prompts: Mapping[str, str] = field(default_factory=dict) + prompts: Mapping[str, str] = data_field(default_factory=dict) # When this resource's state is changed, change all parent resource states as well cascade_state: bool = False # When set to True, this resource will be used @@ -95,7 +102,7 @@ class Resource: # Used for comparing states (which one is earlier/later in the dialogue) order_index: int = 0 # Extra variables to be used for specific situations - extras: Dict[str, Any] = field(default_factory=dict) + extras: Dict[str, Any] = data_field(default_factory=dict) @property def is_unfulfilled(self) -> bool: @@ -130,13 +137,6 @@ def update(self, new_data: Optional["Resource"]) -> None: if new_data: self.__dict__.update(new_data.__dict__) - def format_data(self, format_func: Optional[Callable[[Any], str]] = None) -> str: - """ - Function to format data for display, - optionally taking in a formatting function. - """ - return format_func(self.data) if format_func else self.data - def __hash__(self) -> int: return hash(self.name) @@ -150,25 +150,66 @@ def __str__(self) -> str: return f"<{self.name}>" +class ResourceSchema(Schema): + """ + Marshmallow schema for validation and + serialization/deserialization of a resource class. + """ + + name = fields.Str(required=True) + type = fields.Str(required=True) + data = fields.Raw() + state = fields.Enum(IntFlag, by_value=True, required=True) + requires = fields.List(fields.Str(), required=True) + prompts = fields.Mapping(fields.Str(), fields.Str()) + cascade_state = fields.Bool() + prefer_over_wrapper = fields.Bool() + needs_confirmation = fields.Bool() + order_index = fields.Int() + extras = fields.Dict(fields.Str(), fields.Inferred()) + + @post_load + def instantiate(self, data: Dict[str, Any], **kwargs: Dict[str, Any]): + return RESOURCE_MAP[data["type"]](**data) + + +# Add resource to RESOURCE_MAP, +# should always be done for new Resource classes +RESOURCE_MAP[Resource.__name__] = Resource +# Add schema to RESOURCE_SCHEMAS, +# should also be done for new Resource classes +RESOURCE_SCHEMAS[Resource.__name__] = ResourceSchema + + @dataclass(eq=False, repr=False) class ListResource(Resource): """Resource representing a list of items.""" - data: List[Any] = field(default_factory=list) + data: List[Any] = data_field(default_factory=list) + + +class ListResourceSchema(ResourceSchema): + data = fields.List(fields.Inferred()) - def format_data(self, format_func: Optional[Callable[[Any], str]] = None) -> str: - if format_func: - return format_func(self.data) - return ",".join(str(x) for x in self.data) + +RESOURCE_MAP[ListResource.__name__] = ListResource +RESOURCE_SCHEMAS[ListResource.__name__] = ListResourceSchema @dataclass(eq=False, repr=False) class DictResource(Resource): """Resource representing a dictionary of items.""" - data: Dict[str, Any] = field(default_factory=dict) + data: Dict[str, Any] = data_field(default_factory=dict) + + +class DictResourceSchema(ResourceSchema): + data = fields.Dict(fields.Str(), fields.Inferred()) +RESOURCE_MAP[DictResource.__name__] = DictResource +RESOURCE_SCHEMAS[DictResource.__name__] = DictResourceSchema + # TODO: ? # ExactlyOneResource (choose one resource from options) # SetResource (a set of resources)? @@ -182,65 +223,43 @@ class YesNoResource(Resource): data: bool = False - def set_yes(self): - self.data = True - self.state = ResourceState.CONFIRMED - - def set_no(self): - self.data = False - self.state = ResourceState.CONFIRMED - - def format_data(self, format_func: Optional[Callable[[Any], str]] = None) -> str: - if format_func: - return format_func(self.data) - return "já" if self.data else "nei" +class YesNoResourceSchema(ResourceSchema): + data = fields.Bool() -# @dataclass(eq=False, repr=False) -# class ConfirmResource(YesNoResource): -# """Resource representing a confirmation of other resources.""" -# def set_no(self): -# self.data = False -# self.state = ResourceState.CANCELLED # TODO: ? +RESOURCE_MAP[YesNoResource.__name__] = YesNoResource +RESOURCE_SCHEMAS[YesNoResource.__name__] = YesNoResourceSchema @dataclass(eq=False, repr=False) class DateResource(Resource): """Resource representing a date.""" - data: datetime.date = field(default_factory=datetime.date.today) + data: datetime.date = data_field(default_factory=datetime.date.today) - @property - def date(self) -> Optional[datetime.date]: - return self.data if not self.is_unfulfilled else None - def set_date(self, new_date: datetime.date) -> None: - self.data = new_date +class DateResourceSchema(ResourceSchema): + data = fields.Date() + - def format_data(self, format_func: Optional[Callable[[Any], str]] = None) -> str: - if format_func: - return format_func(self.data) - return self.data.strftime("%x") +RESOURCE_MAP[DateResource.__name__] = DateResource +RESOURCE_SCHEMAS[DateResource.__name__] = DateResourceSchema @dataclass(eq=False, repr=False) class TimeResource(Resource): """Resource representing a time (00:00-23:59).""" - data: datetime.time = field(default_factory=datetime.time) + data: datetime.time = data_field(default_factory=datetime.time) - @property - def time(self) -> Optional[datetime.time]: - return self.data if self.is_unfulfilled else None - def set_time(self, new_time: datetime.time) -> None: - self.data = new_time +class TimeResourceSchema(ResourceSchema): + data = fields.Time() - def format_data(self, format_func: Optional[Callable[[Any], str]] = None) -> str: - if format_func: - return format_func(self.data) - return self.data.strftime("%X") + +RESOURCE_MAP[TimeResource.__name__] = TimeResource +RESOURCE_SCHEMAS[TimeResource.__name__] = TimeResourceSchema @dataclass(eq=False, repr=False) @@ -250,6 +269,14 @@ class DatetimeResource(Resource): ... +class DatetimeResourceSchema(ResourceSchema): + data = fields.NaiveDateTime() + + +RESOURCE_MAP[DatetimeResource.__name__] = DatetimeResource +RESOURCE_SCHEMAS[DatetimeResource.__name__] = DatetimeResourceSchema + + @dataclass(eq=False, repr=False) class NumberResource(Resource): """Resource representing a number.""" @@ -257,6 +284,14 @@ class NumberResource(Resource): data: int = 0 +class NumberResourceSchema(ResourceSchema): + data = fields.Int() + + +RESOURCE_MAP[NumberResource.__name__] = NumberResource +RESOURCE_SCHEMAS[NumberResource.__name__] = NumberResourceSchema + + @dataclass(eq=False, repr=False) class StringResource(Resource): """Resource representing a string.""" @@ -264,17 +299,40 @@ class StringResource(Resource): data: str = "" -@dataclass(eq=False, repr=False) # Wrapper when multiple resources are required +class StringResourceSchema(ResourceSchema): + data = fields.Str() + + +RESOURCE_MAP[StringResource.__name__] = StringResource +RESOURCE_SCHEMAS[StringResource.__name__] = StringResourceSchema + +# Wrapper, when multiple resources are required +@dataclass(eq=False, repr=False) class WrapperResource(Resource): # Wrappers by default prefer to be the current # resource rather than a wrapper parent prefer_over_wrapper: bool = True +class WrapperResourceSchema(ResourceSchema): + ... + + +RESOURCE_MAP[WrapperResource.__name__] = WrapperResource +RESOURCE_SCHEMAS[WrapperResource.__name__] = WrapperResourceSchema + + @dataclass(eq=False, repr=False) class OrResource(WrapperResource): - exclusive: bool = False # Only one of the resources should be fulfilled - # TODO: Add choose_resource() method to skip other options + ... + + +class OrResourceSchema(ResourceSchema): + ... + + +RESOURCE_MAP[OrResource.__name__] = OrResource +RESOURCE_SCHEMAS[OrResource.__name__] = OrResourceSchema @dataclass(eq=False, repr=False) @@ -284,84 +342,9 @@ class FinalResource(Resource): data: Any = None -################################### -# ENCODING/DECODING CLASSES # -################################### - - -# Add any new resource types here (for encoding/decoding) -RESOURCE_MAP: Mapping[str, Type[Resource]] = { - "Resource": Resource, - "DateResource": DateResource, - "DatetimeResource": DatetimeResource, - "FinalResource": FinalResource, - "ListResource": ListResource, - "DictResource": DictResource, - "NumberResource": NumberResource, - "OrResource": OrResource, - "TimeResource": TimeResource, - "WrapperResource": WrapperResource, - "YesNoResource": YesNoResource, - "StringResource": StringResource, -} - - -class DialogueJSONEncoder(json.JSONEncoder): - def default(self, o: Any) -> Any: - # Add JSON encoding for any new classes here - - if isinstance(o, Resource): - # CLASSES THAT INHERIT FROM RESOURCE - d = o.__dict__.copy() - for key in list(d.keys()): - # Skip serializing attributes that start with an underscore - if key.startswith("_"): - del d[key] - d["__type__"] = o.__class__.__name__ - return d - if isinstance(o, datetime.datetime): - return { - "__type__": "datetime", - "year": o.year, - "month": o.month, - "day": o.day, - "hour": o.hour, - "minute": o.minute, - "second": o.second, - "microsecond": o.microsecond, - } - if isinstance(o, datetime.date): - return { - "__type__": "date", - "year": o.year, - "month": o.month, - "day": o.day, - } - if isinstance(o, datetime.time): - return { - "__type__": "time", - "hour": o.hour, - "minute": o.minute, - "second": o.second, - "microsecond": o.microsecond, - } - return json.JSONEncoder.default(self, o) - - -class DialogueJSONDecoder(json.JSONDecoder): - def __init__(self, *args: Any, **kwargs: Any): - json.JSONDecoder.__init__( - self, object_hook=self.dialogue_decoding, *args, **kwargs - ) - - def dialogue_decoding(self, d: Dict[Any, Any]) -> Any: - if "__type__" not in d: - return d - t = d.pop("__type__") - if t == "datetime": - return datetime.datetime(**d) - if t == "date": - return datetime.date(**d) - if t == "time": - return datetime.time(**d) - return RESOURCE_MAP[t](**d) +class FinalResourceSchema(ResourceSchema): + ... + + +RESOURCE_MAP[FinalResource.__name__] = FinalResource +RESOURCE_SCHEMAS[FinalResource.__name__] = FinalResourceSchema diff --git a/queries/fruitseller.py b/queries/fruitseller.py index 009e1e60..3d6dd468 100644 --- a/queries/fruitseller.py +++ b/queries/fruitseller.py @@ -1,10 +1,11 @@ -from typing import Any, List, Optional, Set, cast +from typing import Any, List, Optional, cast + import json import logging import datetime from query import Query, QueryStateDict -from tree import Result, Node, TerminalNode +from tree import ParamList, Result, Node, TerminalNode from reynir import NounPhrase from queries import ( gen_answer, @@ -28,19 +29,13 @@ WrapperResource, ) -_START_DIALOGUE_QTYPE = "QFruitStartQuery" -_DIALOGUE_NAME = "fruitseller" - # Indicate that this module wants to handle dialogue parse trees for queries, # as opposed to simple literal text strings HANDLE_DIALOGUE = True - DIALOGUE_NAME = "fruitseller" -HOTWORD_NONTERMINALS = {"QFruitStartQuery"} # The grammar nonterminals this module wants to handle -QUERY_NONTERMINALS = {"QFruitSeller"}.union(HOTWORD_NONTERMINALS) - +QUERY_NONTERMINALS = {"QFruitSeller"} # The context-free grammar for the queries recognized by this plug-in module GRAMMAR = read_grammar_file("fruitseller") @@ -51,7 +46,7 @@ def banned_nonterminals(q: Query) -> None: Returns a set of nonterminals that are not allowed due to the state of the dialogue """ - if q.active_dialogue != DIALOGUE_NAME: + if not q.in_dialogue(DIALOGUE_NAME): q.ban_nonterminal("QFruitSellerQuery") return resource: Resource = q.dsm.current_resource @@ -150,12 +145,11 @@ def _list_items(items: Any) -> str: return natlang_seq(item_list) -def QFruitStartQuery(node: Node, params: QueryStateDict, result: Result): - result.qtype = _START_DIALOGUE_QTYPE +def QFruitStartQuery(node: Node, params: ParamList, result: Result): Query.get_dsm(result).hotword_activated() -def QAddFruitQuery(node: Node, params: QueryStateDict, result: Result): +def QAddFruitQuery(node: Node, params: ParamList, result: Result): result.qtype = "QAddFruitQuery" dsm: DialogueStateManager = Query.get_dsm(result) resource: ListResource = cast(ListResource, dsm.get_resource("Fruits")) @@ -176,7 +170,7 @@ def QAddFruitQuery(node: Node, params: QueryStateDict, result: Result): dsm.set_resource_state(resource.name, ResourceState.PARTIALLY_FULFILLED) -def QRemoveFruitQuery(node: Node, params: QueryStateDict, result: Result): +def QRemoveFruitQuery(node: Node, params: ParamList, result: Result): result.qtype = "QRemoveFruitQuery" dsm: DialogueStateManager = Query.get_dsm(result) resource: ListResource = cast(ListResource, dsm.get_resource("Fruits")) @@ -195,7 +189,7 @@ def QRemoveFruitQuery(node: Node, params: QueryStateDict, result: Result): dsm.set_resource_state(resource.name, ResourceState.PARTIALLY_FULFILLED) -def QFruitCancelOrder(node: Node, params: QueryStateDict, result: Result): +def QFruitCancelOrder(node: Node, params: ParamList, result: Result): dsm: DialogueStateManager = Query.get_dsm(result) dsm.set_resource_state("Final", ResourceState.CANCELLED) dsm.set_answer(gen_answer(dsm.get_resource("Final").prompts["cancelled"])) @@ -203,13 +197,13 @@ def QFruitCancelOrder(node: Node, params: QueryStateDict, result: Result): result.qtype = "QFruitCancel" -def QFruitOptionsQuery(node: Node, params: QueryStateDict, result: Result): +def QFruitOptionsQuery(node: Node, params: ParamList, result: Result): result.qtype = "QFruitOptionsQuery" result.answer_key = ("Fruits", "options") result.fruitOptions = True -def QFruitYes(node: Node, params: QueryStateDict, result: Result): +def QFruitYes(node: Node, params: ParamList, result: Result): result.qtype = "QFruitYes" dsm: DialogueStateManager = Query.get_dsm(result) @@ -223,7 +217,7 @@ def QFruitYes(node: Node, params: QueryStateDict, result: Result): dsm.get_resource(rname).state = ResourceState.CONFIRMED -def QFruitNo(node: Node, params: QueryStateDict, result: Result): +def QFruitNo(node: Node, params: ParamList, result: Result): result.qtype = "QFruitNo" dsm: DialogueStateManager = Query.get_dsm(result) resource = dsm.current_resource @@ -234,7 +228,7 @@ def QFruitNo(node: Node, params: QueryStateDict, result: Result): resource.state = ResourceState.PARTIALLY_FULFILLED -def QFruitNumOfFruit(node: Node, params: QueryStateDict, result: Result): +def QFruitNumOfFruit(node: Node, params: ParamList, result: Result): if "queryfruits" not in result: result["queryfruits"] = [] if "fruitnumber" not in result: @@ -243,7 +237,7 @@ def QFruitNumOfFruit(node: Node, params: QueryStateDict, result: Result): result.queryfruits.append([result.fruitnumber, result.fruit]) -def QFruitNum(node: Node, params: QueryStateDict, result: Result): +def QFruitNum(node: Node, params: ParamList, result: Result): fruitnumber = int(parse_num(node, result._nominative)) if fruitnumber is not None: result.fruitnumber = fruitnumber @@ -251,7 +245,7 @@ def QFruitNum(node: Node, params: QueryStateDict, result: Result): result.fruitnumber = 1 -def QFruit(node: Node, params: QueryStateDict, result: Result): +def QFruit(node: Node, params: ParamList, result: Result): fruit = result._root if fruit is not None: result.fruit = fruit @@ -271,7 +265,7 @@ def _add_date( datetime_resource.state = ResourceState.PARTIALLY_FULFILLED -def QFruitDate(node: Node, params: QueryStateDict, result: Result) -> None: +def QFruitDate(node: Node, params: ParamList, result: Result) -> None: result.qtype = "bull" datenode = node.first_child(lambda n: True) assert isinstance(datenode, TerminalNode) @@ -309,7 +303,7 @@ def _add_time( datetime_resource.state = ResourceState.PARTIALLY_FULFILLED -def QFruitTime(node: Node, params: QueryStateDict, result: Result): +def QFruitTime(node: Node, params: ParamList, result: Result): result.qtype = "bull" # Extract time from time terminal nodes tnode = cast(TerminalNode, node.first_child(lambda n: n.has_t_base("tími"))) @@ -325,7 +319,7 @@ def QFruitTime(node: Node, params: QueryStateDict, result: Result): result["parse_error"] = True -def QFruitDateTime(node: Node, params: QueryStateDict, result: Result) -> None: +def QFruitDateTime(node: Node, params: ParamList, result: Result) -> None: result.qtype = "bull" datetimenode = node.first_child(lambda n: True) assert isinstance(datetimenode, TerminalNode) @@ -347,7 +341,7 @@ def QFruitDateTime(node: Node, params: QueryStateDict, result: Result) -> None: _add_time(cast(TimeResource, dsm.get_resource("Time")), dsm, result) -def QFruitInfoQuery(node: Node, params: QueryStateDict, result: Result): +def QFruitInfoQuery(node: Node, params: ParamList, result: Result): result.qtype = "QFruitInfo" dsm: DialogueStateManager = Query.get_dsm(result) at = dsm.get_answer(_ANSWERING_FUNCTIONS, result) @@ -376,24 +370,8 @@ def sentence(state: QueryStateDict, result: Result) -> None: # Successfully matched a query type try: - # if result.qtype == "QFruitInfo": - # # Example info handling functionality - # # ans = "Ávaxtapöntunin þín er bara flott. " - # # f = dsm.get_resource("Fruits") - # # ans += str(f.data) - # ans = dsm.get_answer() - # if not ans: - # print("No answer generated") - # q.set_error("E_QUERY_NOT_UNDERSTOOD") - # return - - # q.set_answer(*ans) - # return - ans = dsm.get_answer(_ANSWERING_FUNCTIONS, result) - print("FRUIT ANS: ", ans) if not ans: - print("No answer generated") q.set_error("E_QUERY_NOT_UNDERSTOOD") return diff --git a/queries/grammars/fruitseller.grammar b/queries/grammars/fruitseller.grammar index 5cb54a43..2f359e25 100644 --- a/queries/grammars/fruitseller.grammar +++ b/queries/grammars/fruitseller.grammar @@ -1,8 +1,9 @@ Query → - QFruitStartQuery | QFruitSeller + QFruitSeller '?'? QFruitSeller → QFruitSellerQuery + | QFruitHotWord QFruitSellerQuery → QFruitQuery '?'? @@ -12,12 +13,13 @@ QFruitSellerQuery → QFruitInfoQuery → "hver"? "er"? "staðan" "á"? "ávaxtapöntuninni"? -QFruitStartQuery → +QFruitHotWord → "ávöxtur" '?'? | "postur" '?'? | "póstur" '?'? | "ég" "vill" "kaupa"? "ávexti" '?'? | "ég" "vil" "kaupa"? "ávexti" '?'? + | "ég" "vil" "panta"? "ávexti" '?'? | "mig" "langar" "að" "kaupa" "ávexti" "hjá"? "þér"? '?'? | "mig" "langar" "að" "panta" "ávexti" "hjá"? "þér"? '?'? | "get" "ég" "keypt" "ávexti" "hjá" "þér" '?'? diff --git a/queries/theater.py b/queries/theater.py index 725bb4ca..8be7cf7e 100644 --- a/queries/theater.py +++ b/queries/theater.py @@ -95,7 +95,7 @@ def banned_nonterminals(q: Query) -> None: return resource: Resource = q.dsm.current_resource if resource.name == "Show": - q.ban_nonterminals( + q.ban_nonterminal_set( { "QTheaterShowDateQuery", "QTheaterMoreDates", @@ -108,14 +108,14 @@ def banned_nonterminals(q: Query) -> None: } ) if resource.is_unfulfilled: - q.ban_nonterminals( + q.ban_nonterminal_set( { "QTheaterShowLength", "QTheaterShowPrice", } ) elif resource.name == "ShowDateTime": - q.ban_nonterminals( + q.ban_nonterminal_set( { "QTheaterShowSeatCountQuery", "QTheaterShowLocationQuery", @@ -125,7 +125,7 @@ def banned_nonterminals(q: Query) -> None: } ) elif resource.name == "ShowSeatCount": - q.ban_nonterminals( + q.ban_nonterminal_set( { "QTheaterShowLocationQuery", "QTheaterRowOptions", @@ -136,7 +136,7 @@ def banned_nonterminals(q: Query) -> None: } ) elif resource.name == "ShowSeatRow": - q.ban_nonterminals( + q.ban_nonterminal_set( { "QTheaterShowSeats", "QTheaterSeatCountNum", @@ -146,7 +146,7 @@ def banned_nonterminals(q: Query) -> None: } ) elif resource.name == "ShowSeatNumber": - q.ban_nonterminals( + q.ban_nonterminal_set( { "QTheaterSeatCountNum", "QTheaterRowNum", @@ -154,7 +154,7 @@ def banned_nonterminals(q: Query) -> None: } ) if resource.is_unfulfilled: - q.ban_nonterminals( + q.ban_nonterminal_set( { "QTheaterYes", "QTheaterNo", diff --git a/query.py b/query.py index 008c35e8..63a39c68 100755 --- a/query.py +++ b/query.py @@ -54,17 +54,16 @@ import random from collections import defaultdict from itertools import takewhile -from queries.extras.dialogue import ( - DialogueStateManager as DSM, - ResourceNotFoundError, - DialogueDataDict, -) from settings import Settings from db import SessionContext, Session, desc from db.models import Query as QueryRow from db.models import QueryData, QueryLog, DialogueData +from queries.extras.dialogue import ( + ActiveDialogueList, + DialogueStateManager as DSM, +) from reynir import TOK, Tok, tokenize, correct_spaces from reynir.fastparser import ( @@ -108,9 +107,6 @@ # Client data ClientDataDict = Dict[str, Union[str, int, float, bool, Dict[str, str]]] -# Dialogue data -DialogueDatabaseDict = Dict[str, Union[str, int, float, bool, Dict[str, str]]] - # Answer tuple (corresponds to parameter list of Query.set_answer()) AnswerTuple = Tuple[ResponseType, str, Optional[str]] @@ -289,9 +285,10 @@ def calc_score(w: SPPF_Node) -> ResultDict: if ch is not None: rd = calc_score(ch) d = child_scores[fam_ix] - d["sc"] += rd["sc"] if "ban" in rd: # Carry ban status up the tree d["ban"] = rd["ban"] # type: ignore + else: # Otherwise modify score + d["sc"] += rd["sc"] # Carry information about contained verbs ("so") up the tree for key in ("so", "sl"): if key in rd: @@ -584,16 +581,13 @@ def __init__( # Banned nonterminals for this query, # dynamically generated from query modules self._banned_nonterminals: Set[str] = set() - # TODO: Simplify this + # Dialogue state manager and dialogue data, used for dialogue modules - # The name of the active dialogue, None if no dialogue is active - active_dialogue_dict: Optional[DialogueDataDict] = self.client_data( - DSM.DIALOGUE_DATA_KEY - ) - self._active_dialogue: Optional[str] = ( - None - if not active_dialogue_dict - else (active_dialogue_dict.get("active_dialogue")) + # The dialogue state manager + self._dsm: DSM = DSM(self.client_id) + # The names of the active dialogues, empty list if no dialogue is active + self._active_dialogues: ActiveDialogueList = cast( + ActiveDialogueList, self.client_data(DSM.ACTIVE_DIALOGUE_KEY) or list() ) # The active dialogue data, empty dict if no dialogue is active self._dialogue_data: DialogueDataDict = ( @@ -602,11 +596,9 @@ def __init__( ) or dict() ) - # The dialogue state manager - self._dsm: DSM = DSM(self._dialogue_data) # Load the dialogue for the active dialogue if present - if self._active_dialogue: - self._dsm.load_dialogue(self._active_dialogue) + if self._active_dialogues: + self._dsm.load_dialogue(self._active_dialogues[0]) def _preprocess_query_string(self, q: str) -> str: """Preprocess the query string prior to further analysis""" @@ -873,46 +865,13 @@ def execute_from_dialogue(self) -> bool: continue dialogue_name: Optional[str] = getattr(processor, "DIALOGUE_NAME", None) if dialogue_name: - # This processor uses dialogue functionality, - # check if it wants to process this query - hotword_nonterminals: Set[str] = getattr( - processor, "HOTWORD_NONTERMINALS", set() - ) - # Dialogue modules must have at least one - # hotword nonterminal for activating dialogue - assert isinstance(hotword_nonterminals, set) - assert len(hotword_nonterminals) > 0 - print( - "CHECKING WHETHER PROCESSOR IS INTERESTED IN DIALOGUE:", - not ( - self._tree.query_nonterminals.isdisjoint( - hotword_nonterminals - ) - and dialogue_name != self._active_dialogue - ), + # This processor uses dialogue functionality + + self._dialogue_data = ( + cast(DialogueDataDict, self.dialogue_data(dialogue_name)) + or dict() ) - # Fetch saved dialogue state - if ( - self._tree.query_nonterminals.isdisjoint(hotword_nonterminals) - and dialogue_name != self._active_dialogue - ): - print("NOT ACTIVE DIALOGUE AND NO HOTWORDS MATCHED") - # Query's parse forest doesn't contain hotwords - # and has no saved data for this dialogue, - # not interested in this query - continue - if not self._tree.query_nonterminals.isdisjoint( - hotword_nonterminals - ): - print("HOTWORDS MATCHED: ", dialogue_name) - # Query's parse forest contains hotwords - # set this as the active dialogue - self._active_dialogue = dialogue_name - self._dialogue_data = ( - cast(DialogueDataDict, self.dialogue_data(dialogue_name)) - or dict() - ) - self._dsm = DSM(self._dialogue_data) + self._dsm = DSM(self._dialogue_data) # Query matches this dialogue processor, start DialogueStateManager self.dsm.load_dialogue(dialogue_name) print("DIALOGUE LOADED: ", dialogue_name) @@ -1142,7 +1101,7 @@ def ban_nonterminal(self, nonterminal: str) -> None: ) self._banned_nonterminals.add(nonterminal) - def ban_nonterminals(self, nonterminals: Set[str]) -> None: + def ban_nonterminal_set(self, nonterminals: Set[str]) -> None: """ Add a set of nonterminals to the set of banned nonterminals for this query. @@ -1212,25 +1171,17 @@ def dsm(self) -> DSM: @property def active_dialogue(self) -> Optional[str]: - if self._active_dialogue is None: - # Fetch the active dialogue for the given client, or set it to None - active_dialogue_dict: Optional[DialogueDataDict] = self.client_data( - DSM.DIALOGUE_DATA_KEY - ) - if active_dialogue_dict: - self._active_dialogue = active_dialogue_dict.get("active_dialogue") - else: - self._active_dialogue = None - return self._active_dialogue + """Return last dialogue in active dialogues list, otherwise None.""" + return self._active_dialogues[-1] if self._active_dialogues else None def update_dialogue_data(self) -> None: """Update the dialogue data for the given client if a dialogue module was used""" if self._dsm is not None: # Save the dialogue state when a dialogue module query # is successfully processed - if self._active_dialogue: + if self.active_dialogue: self.set_dialogue_data( - self._active_dialogue, + self.active_dialogue, cast(DialogueDatabaseDict, self._dsm.serialize_data()), update_in_place=True, ) @@ -1270,7 +1221,7 @@ def set_context(self, ctx: ContextDict) -> None: to the next query from the same client""" self._context = ctx - def client_data(self, key: str) -> Optional[DialogueDataDict]: + def client_data(self, key: str) -> Optional[ClientDataDict]: """Fetch client_id-associated data stored in the querydata table""" if not self.client_id: return None @@ -1284,7 +1235,7 @@ def client_data(self, key: str) -> Optional[DialogueDataDict]: return ( None if client_data is None - else cast(DialogueDataDict, client_data.data) + else cast(ClientDataDict, client_data.data) ) except Exception as e: logging.error( @@ -1362,12 +1313,14 @@ def store_query_data( row.data = data # type: ignore row.modified = now # type: ignore # The session is auto-committed upon exit from the context manager - print("return True") return True except Exception as e: logging.error("Error storing query data in db: {0}".format(e)) return False + def in_dialogue(self, dialogue_name: str) -> bool: + return self.active_dialogue == dialogue_name + def dialogue_data( self, dialogue_key: Optional[str] ) -> Optional[DialogueDatabaseDict]: @@ -1403,15 +1356,15 @@ def set_dialogue_data( data: DialogueDatabaseDict, *, update_in_place: bool = False, - ) -> None: + ) -> bool: """ Setter for client dialogue data. Also sets the active dialogue in the query data. """ if not self.client_id or not dialogue_key: logging.warning("Couldn't save query data, no client ID or key") - return - Query.store_dialogue_data( + return False + return Query.store_dialogue_data( self.client_id, dialogue_key, data, update_in_place=update_in_place ) # Query.store_query_data( @@ -1452,7 +1405,7 @@ def store_dialogue_data( session.add(row) Query.store_query_data( client_id, - DSM.DIALOGUE_DATA_KEY, + DSM.ACTIVE_DIALOGUE_KEY, {"active_dialogue": dialogue_key}, update_in_place=True, ) @@ -1468,7 +1421,7 @@ def store_dialogue_data( return True Query.store_query_data( client_id=client_id, - key=DSM.DIALOGUE_DATA_KEY, + key=DSM.ACTIVE_DIALOGUE_KEY, data={"active_dialogue": dialogue_key}, update_in_place=True, ) @@ -1507,7 +1460,7 @@ def update_active_dialogue_data( latest_row = row active_row = ( session.query(QueryData) - .filter(QueryData.key == DSM.DIALOGUE_DATA_KEY) + .filter(QueryData.key == DSM.ACTIVE_DIALOGUE_KEY) .filter(QueryData.client_id == client_id) ).one_or_none() @@ -1516,7 +1469,7 @@ def update_active_dialogue_data( if latest_row: active_row = QueryData( client_id=client_id, - key=DSM.DIALOGUE_DATA_KEY, + key=DSM.ACTIVE_DIALOGUE_KEY, created=datetime.utcnow(), modified=datetime.utcnow(), data={"active_dialogue": latest_row.dialogue_key}, diff --git a/requirements.txt b/requirements.txt index 350ae7bf..8cdef2df 100644 --- a/requirements.txt +++ b/requirements.txt @@ -26,5 +26,6 @@ rjsmin>=1.1.0 odfpy>=1.4.1 pdfminer.six>=20201018 python-youtube>=0.8.1 +marshmallow>=3.18.0 tomli >= 1.1.0 ; python_version < "3.11" azure-cognitiveservices-speech>=1.19.0 From 9b9b3633f07145c6406c15ffed279b32ea9013a7 Mon Sep 17 00:00:00 2001 From: Logi E Date: Thu, 22 Sep 2022 15:15:58 +0000 Subject: [PATCH 361/371] cleanup --- queries/extras/sonos.py | 57 ++--------------------------------- queries/extras/spotify.py | 39 +----------------------- templates/hue-connection.html | 11 +------ 3 files changed, 4 insertions(+), 103 deletions(-) diff --git a/queries/extras/sonos.py b/queries/extras/sonos.py index d6480808..90c0bb79 100644 --- a/queries/extras/sonos.py +++ b/queries/extras/sonos.py @@ -127,23 +127,17 @@ def __init__( self._radio_name = radio_name self._encoded_credentials = read_api_key("SonosEncodedCredentials") self._code = self._device_data["sonos"]["credentials"]["code"] - print("code :", self._code) self._timestamp = ( self._device_data.get("sonos").get("credentials").get("timestamp") ) - print("device data :", self._device_data) try: - print("Trying to get access token") self._access_token = self._device_data["sonos"]["credentials"][ "access_token" ] - print("access token :", self._access_token) self._refresh_token = self._device_data["sonos"]["credentials"][ "refresh_token" ] - print("refresh token :", self._refresh_token) except (KeyError, TypeError): - print("No access token found for Sonos.") self._create_token() self._check_token_expiration() self._households = self._get_households() @@ -164,7 +158,6 @@ def _check_token_expiration(self) -> None: try: timestamp = self._device_data["sonos"]["credentials"]["timestamp"] except (KeyError, TypeError): - print("No timestamp found for Sonos token.") return timestamp = datetime.strptime(timestamp, "%Y-%m-%d %H:%M:%S.%f") if (datetime.now() - timestamp) > timedelta(hours=24): @@ -174,7 +167,6 @@ def _update_sonos_token(self) -> None: """ Updates the access token """ - print("update sonos token") self._encoded_credentials = read_api_key("SonosEncodedCredentials") self._refresh_expired_token() sonos_dict = { @@ -192,7 +184,6 @@ def _refresh_expired_token(self) -> Union[None, List[Any], Dict[str, Any]]: """ Helper function for updating the access token. """ - print("_refresh_expired_token") url = f"https://api.sonos.com/login/v3/oauth/access?grant_type=refresh_token&refresh_token={self._refresh_token}" headers = {"Authorization": f"Basic {self._encoded_credentials}"} @@ -207,7 +198,6 @@ def _create_token(self) -> Union[None, List[Any], Dict[str, Any]]: """ Creates a token given a code """ - print("_create_token") host = str(flask.request.host) url = f"https://api.sonos.com/login/v3/oauth/access?grant_type=authorization_code&code={self._code}&redirect_uri=http://{host}/connect_sonos.api" headers = { @@ -215,9 +205,7 @@ def _create_token(self) -> Union[None, List[Any], Dict[str, Any]]: } response = post_to_json_api(url, headers=headers) - print("Reponse :", response) self._access_token = response.get("access_token") - print("access token :", self._access_token) self._refresh_token = response.get("refresh_token") self._timestamp = str(datetime.now()) return response @@ -226,7 +214,6 @@ def _get_households(self) -> Dict[str, str]: """ Returns the list of households of the user """ - print("get households") url = f"https://api.ws.sonos.com/control/api/v1/households" headers = {"Authorization": f"Bearer {self._access_token}"} @@ -237,7 +224,6 @@ def _get_household_id(self) -> str: """ Returns the household id for the given query """ - print("get household id") url = f"https://api.ws.sonos.com/control/api/v1/households" headers = { "Content-Type": "application/json", @@ -251,7 +237,6 @@ def _get_groups(self) -> Dict[str, str]: """ Returns the list of groups of the user """ - print("get groups") for i in range(len(self._households)): url = f"https://api.ws.sonos.com/control/api/v1/households/{self._household_id}/groups" headers = {"Authorization": f"Bearer {self._access_token}"} @@ -264,25 +249,16 @@ def _get_group_id(self) -> str: """ Returns the group id for the given query """ - print("get group_id") try: if self._group_name is not None: - print("GROUP NAME NOT NONE") translated_group_name = self._translate_group_name() - print("Self groups :", self._groups) - print("GROUP NAME :", self._group_name) - print("GROUPS NAME :", self._group_name) group_id = self._groups.get(translated_group_name.casefold()) - print("GROUP ID :", group_id) return group_id else: - print("GROUP NAME IS NONE") if len(self._groups) == 1: - print("LEN 1") group_name = iter(self._groups[0]) return self._groups[0][group_name] except (KeyError, TypeError): - print("GROUP EXCEPT") url = f"https://api.ws.sonos.com/control/api/v1/households/{self._household_id}/groups" headers = { "Content-Type": "application/json", @@ -296,10 +272,8 @@ def _translate_group_name(self) -> str: """ Translates the group name to the correct group name """ - print("Translate group name") try: english_group_name = _GROUPS_DICT[self._group_name] - print("TRANSLATED GROUP NAME :", english_group_name) return english_group_name except (KeyError, TypeError): return self._group_name @@ -308,7 +282,6 @@ def _get_players(self) -> Dict[str, str]: """ Returns the list of groups of the user """ - print("get players") for i in range(len(self._households)): url = f"https://api.ws.sonos.com/control/api/v1/households/{self._household_id}/groups" headers = {"Authorization": f"Bearer {self._access_token}"} @@ -321,7 +294,6 @@ def _get_player_id(self) -> str: """ Returns the player id for the given query """ - print("get player_id") try: player_id = self._players[0]["id"] return player_id @@ -337,7 +309,6 @@ def _get_player_id(self) -> str: return response["players"][0]["id"] def _create_data_dict(self) -> Dict[str, str]: - print("_create_data_dict") data_dict = {"households": self._households} for i in range(len(self._households)): groups_dict = self._groups @@ -348,7 +319,6 @@ def _create_data_dict(self) -> Dict[str, str]: return data_dict def _create_cred_dict(self) -> Dict[str, str]: - print("_create_cred_dict") cred_dict = {} cred_dict.update( { @@ -360,7 +330,6 @@ def _create_cred_dict(self) -> Dict[str, str]: return cred_dict def _store_data_and_credentials(self) -> None: - print("_store_data_and_credentials") cred_dict = self._create_cred_dict() sonos_dict = {} sonos_dict["sonos"] = {"credentials": cred_dict} @@ -371,44 +340,37 @@ def _store_data(self, data: Dict) -> None: Query.store_query_data(self._client_id, "iot", new_dict, update_in_place=True) def _create_groupdict_for_db(self, groups: list) -> Dict[str, str]: - print("create_groupdict_for_db") groups_dict = {} for i in range(len(groups)): groups_dict[groups[i]["name"].casefold()] = groups[i]["id"] return groups_dict def _create_playerdict_for_db(self, players: list) -> Dict[str, str]: - print("create_playerdict_for_db") players_dict = {} for i in range(len(players)): players_dict[players[i]["name"]] = players[i]["id"] return players_dict def _create_or_join_session(self, recursion=None) -> Optional[str]: - print("_create_or_join_session") url = f"https://api.ws.sonos.com/control/api/v1/groups/{self._group_id}/playbackSession/joinOrCreate" - payload = json.dumps({"appId": "com.mideind.embla", "appContext": "embla123"}) + payload = json.dumps({"appId": "com.mideind.embla", "appContext": "embla123"}) # FIXME: Use something else than embla123 headers = { "Content-Type": "application/json", "Authorization": f"Bearer {self._access_token}", } response = post_to_json_api(url, form_data=payload, headers=headers) - print(response) if response is None: self.toggle_pause() if recursion is None: response = self._create_or_join_session(recursion=True) else: return None - print("response was none , so we created a new session") session_id = response else: session_id = response["sessionId"] - print("response after loop:", response) - print("session_id :", session_id) return session_id """ @@ -416,17 +378,14 @@ def _create_or_join_session(self, recursion=None) -> Optional[str]: """ def play_radio_stream(self, radio_url: str) -> Optional[str]: - print("play radio stream") session_id = self._create_or_join_session() - print("exited create or join session") if radio_url is None: try: radio_url = self._device_data["sonos"]["data"]["last_radio_url"] except KeyError: radio_url = "http://netradio.ruv.is/rondo.mp3" - url = f"https://api.ws.sonos.com/control/api/v1//playbackSessions/{session_id}/playbackSession/loadStreamUrl?" - print("RADIO URL :", radio_url) + url = f"https://api.ws.sonos.com/control/api/v1/groups/playbackSessions/{session_id}/playbackSession/loadStreamUrl?" payload = json.dumps( { "streamUrl": f"{radio_url}", @@ -445,10 +404,8 @@ def play_radio_stream(self, radio_url: str) -> Optional[str]: return "Group not found" data_dict = {"sonos": {"data": {"last_radio_url": radio_url}}} self._store_data(data_dict) - print(response.get("text")) def increase_volume(self) -> None: - print("increase_volume") url = f"https://api.ws.sonos.com/control/api/v1/groups/{self._group_id}/groupVolume/relative" payload = json.dumps({"volumeDelta": 10}) @@ -460,10 +417,8 @@ def increase_volume(self) -> None: response = post_to_json_api(url, form_data=payload, headers=headers) if response is None: self._refresh_data("increase_volume") - print(response.get("text")) def decrease_volume(self) -> None: - print("decrease volume") group_id = self._get_group_id() url = f"https://api.ws.sonos.com/control/api/v1/groups/{group_id}/groupVolume/relative" @@ -476,14 +431,11 @@ def decrease_volume(self) -> None: response = post_to_json_api(url, form_data=payload, headers=headers) if response is None: return "Group not found" - print(response.get("text")) def toggle_play(self) -> Union[None, List[Any], Dict[str, Any]]: """ Toggles play/pause of a group """ - print("toggle playpause") - print("exited group_id") url = f"https://api.ws.sonos.com/control/api/v1/groups/{self._group_id}/playback/play" headers = { "Content-Type": "application/json", @@ -491,7 +443,6 @@ def toggle_play(self) -> Union[None, List[Any], Dict[str, Any]]: } response = post_to_json_api(url, headers=headers) - print("response :", response) if response is None: return "Group not found" @@ -501,9 +452,7 @@ def toggle_pause(self) -> Union[None, List[Any], Dict[str, Any]]: """ Toggles play/pause of a group """ - print("toggle playpause") # group_id = self._get_group_id() - print("exited group_id") url = f"https://api.ws.sonos.com/control/api/v1/groups/{self._group_id}/playback/pause" headers = { "Content-Type": "application/json", @@ -512,7 +461,6 @@ def toggle_pause(self) -> Union[None, List[Any], Dict[str, Any]]: # response = requests.request("POST", url, headers=headers, data=payload) response = post_to_json_api(url, headers=headers) - print("response :", response) if response is None: return "Group not found" @@ -524,7 +472,6 @@ def play_audio_clip( """ Plays an audioclip from link to .mp3 file """ - print("play_audio_clip") player_id = self._get_player_id() url = f"https://api.ws.sonos.com/control/api/v1/players/{player_id}/audioClip" diff --git a/queries/extras/spotify.py b/queries/extras/spotify.py index c53778fd..263e6384 100644 --- a/queries/extras/spotify.py +++ b/queries/extras/spotify.py @@ -116,9 +116,7 @@ def __init__( self._album_name = album_name self._song_url = None self._album_url = None - print("code :", self._code) self._timestamp = self._device_data.get("credentials").get("timestamp") - print("device data :", self._device_data) try: self._access_token = self._device_data["credentials"]["access_token"] self._refresh_token = self._device_data["credentials"]["refresh_token"] @@ -150,29 +148,24 @@ def _check_token_expiration(self) -> None: """ Checks if access token is expired, and calls a function to refresh it if necessary. """ - print("check token expiration") try: timestamp = self._device_data["credentials"]["timestamp"] except (KeyError, TypeError): - print("No timestamp found for spotify token.") return timestamp = datetime.strptime(timestamp, "%Y-%m-%d %H:%M:%S.%f") if (datetime.now() - timestamp) > timedelta(hours=1): - print("more than 1 hour") self._update_spotify_token() def _update_spotify_token(self) -> None: """ Updates the access token """ - print("update spotify token") self._refresh_expired_token() def _refresh_expired_token(self) -> None: """ Helper function for updating the access token. """ - print("_refresh_expired_token") url = f"https://accounts.spotify.com/api/token?grant_type=refresh_token&refresh_token={self._refresh_token}" @@ -187,12 +180,10 @@ def _refresh_expired_token(self) -> None: self._timestamp = str(datetime.now()) def _store_credentials(self) -> None: - print("_store_spotify_cred") cred_dict = self._create_cred_dict() self._store_data(cred_dict) def _create_cred_dict(self) -> Dict[str, str]: - print("_create_spotify_cred_dict") cred_dict = {} cred_dict.update( { @@ -207,14 +198,12 @@ def _store_data(self, data: Dict[str, str]) -> None: Query.store_query_data(self._client_id, "iot", new_dict, update_in_place=True) def _store_credentials(self) -> None: - print("_store_spotify credentials") cred_dict = self._create_cred_dict() spotify_dict = {} spotify_dict["credentials"] = cred_dict self._store_data(spotify_dict) def _create_cred_dict(self) -> Dict[str, str]: - print("_create_spotify_cred_dict") cred_dict = {} cred_dict.update( { @@ -226,14 +215,9 @@ def _create_cred_dict(self) -> Dict[str, str]: return cred_dict def get_song_by_artist(self) -> Optional[str]: - print("get song by artist") - print("accesss token get song; ", self._access_token) - song_name = self._song_name.replace(" ", "%20") + song_name = self._song_name.replace(" ", "%20") # FIXME: URL encode this artist_name = self._artist_name.replace(" ", "%20") - print("song name: ", song_name) - print("artist name: ", artist_name) url = f"{self._api_url}/search?type=track&q={song_name}+{artist_name}" - print("url: ", url) payload = "" headers = { @@ -245,21 +229,14 @@ def get_song_by_artist(self) -> Optional[str]: self._song_url = response["tracks"]["items"][0]["external_urls"]["spotify"] self._song_uri = response["tracks"]["items"][0]["uri"] except IndexError: - print("No song found.") return - print("SONG URI: ", self._song_url) return self._song_url def get_album_by_artist(self) -> Optional[str]: - print("get albuym by artist") - print("accesss token get song; ", self._access_token) album_name = self._album_name.replace(" ", "%20") artist_name = self._artist_name.replace(" ", "%20") - print("song name: ", album_name) - print("artist name: ", artist_name) url = f"{self._api_url}/search?type=album&q={album_name}+{artist_name}" - print("url: ", url) payload = "" headers = { @@ -272,21 +249,14 @@ def get_album_by_artist(self) -> Optional[str]: self._album_url = response["albums"]["items"][0]["external_urls"]["spotify"] self._album_uri = response["albums"]["items"][0]["uri"] except IndexError: - print("No song found.") return - print("ALBUM URI: ", self._album_url) return self._album_url def get_first_track_on_album(self) -> Optional[str]: - print("get first track on album") - print("accesss token get song; ", self._access_token) album_name = self._album_name.replace(" ", "%20") artist_name = self._artist_name.replace(" ", "%20") - print("song name: ", album_name) - print("artist name: ", artist_name) url = f"{self._api_url}/albums/{self._album_id}/tracks" - print("url: ", url) payload = "" headers = { @@ -300,16 +270,11 @@ def get_first_track_on_album(self) -> Optional[str]: "spotify" ] except IndexError: - print("No song found.") return - print("ALBUM URI: ", self._first_album_track_url) return self._first_album_track_url def play_song_on_device(self) -> Union[None, List[Any], Dict[str, Any]]: - print("play song from device") - print("accesss token play song; ", self._access_token) - print("exited get devices") url = f"{self._api_url}/me/player/play" payload = json.dumps( @@ -324,7 +289,6 @@ def play_song_on_device(self) -> Union[None, List[Any], Dict[str, Any]]: response = put_to_json_api(url, payload, headers) - print(response) return response def play_album_on_device(self) -> Union[None, List[Any], Dict[str, Any]]: @@ -342,5 +306,4 @@ def play_album_on_device(self) -> Union[None, List[Any], Dict[str, Any]]: response = put_to_json_api(url, payload, headers) - print(response) return response diff --git a/templates/hue-connection.html b/templates/hue-connection.html index 91481355..ef21a27e 100644 --- a/templates/hue-connection.html +++ b/templates/hue-connection.html @@ -47,14 +47,6 @@

    Leiðbeiningar

    // "use strict"; async function findHub() { - // let hubArr = []; - // let hubObj = new Object(); - // hubObj.id = "ecb5fafffe1be1a4"; - // hubObj.internalipaddress = "192.168.1.68"; - // hubObj.port = "443"; - // console.log(hubObj); - // hubArr.push(hubObj); - // return hubArr[0]; return fetch(`https://discovery.meethue.com`) .then((resp) => { if (!resp.ok) { @@ -82,7 +74,7 @@

    Leiðbeiningar

    async function createNewDeveloper(ipAddress) { console.log("create new developer"); const body = JSON.stringify({ - devicetype: "mideind_hue_communication#smartdevice", + devicetype: "Embla", }); return fetch(`http://${ipAddress}/api`, { method: "POST", @@ -115,7 +107,6 @@

    Leiðbeiningar

    }); } - // clientID = "82AD3C91-7DA2-4502-BB17-075CEC090B14", requestURL = "192.168.1.68") async function connectHub(clientID, requestURL, hubIpAddress = null) { console.log("connect hub"); let deviceInfo; From 5042c0065e91592e66d5c60376a560c5299840be Mon Sep 17 00:00:00 2001 From: Logi E Date: Fri, 23 Sep 2022 15:48:05 +0000 Subject: [PATCH 362/371] Large fixes to DSM inner functionality and usage in query.py --- db/models.py | 14 +- queries/dialogues/fruitseller.toml | 1 + queries/extras/dialogue.py | 548 +++++++++++++++++++++-------- queries/extras/dialogue_db.py | 149 -------- queries/extras/resources.py | 9 +- queries/fruitseller.py | 28 +- queries/grammars/pizza.grammar | 13 +- queries/grammars/theater.grammar | 20 +- queries/pizza.py | 9 +- queries/theater.py | 35 +- query.py | 390 +++++--------------- 11 files changed, 555 insertions(+), 661 deletions(-) delete mode 100644 queries/extras/dialogue_db.py diff --git a/db/models.py b/db/models.py index 8c8f9c78..9a126314 100755 --- a/db/models.py +++ b/db/models.py @@ -747,7 +747,7 @@ class DialogueData(Base): client_id = Column(String(256), nullable=False) - # Dialogue key to distinguish between different dialogues that can be stored + # Dialogue key/name to distinguish between different dialogues that can be stored dialogue_key = Column(String(64), nullable=False) # Created timestamp @@ -759,9 +759,17 @@ class DialogueData(Base): # JSON data data = Column(JSONB, nullable=False) + # Expires at timestamp + expires_at = Column(DateTime, nullable=False) + def __repr__(self): - return "DialogueData(client_id='{0}', created='{1}', modified='{2}', dialogue_key='{3}', data='{4}')".format( - self.client_id, self.created, self.modified, self.dialogue_key, self.data + return "DialogueData(client_id='{0}', created='{1}', modified='{2}', dialogue_key='{3}', data='{4}', expires_at='{5}')".format( + self.client_id, + self.created, + self.modified, + self.dialogue_key, + self.data, + self.expires_at, ) diff --git a/queries/dialogues/fruitseller.toml b/queries/dialogues/fruitseller.toml index b2e0faf7..108105fb 100644 --- a/queries/dialogues/fruitseller.toml +++ b/queries/dialogues/fruitseller.toml @@ -1,3 +1,4 @@ +[extras] expiration_time = 900 # 15 minutes [[resources]] diff --git a/queries/extras/dialogue.py b/queries/extras/dialogue.py index 7058fc9c..a8dd56dd 100644 --- a/queries/extras/dialogue.py +++ b/queries/extras/dialogue.py @@ -24,37 +24,38 @@ Any, Callable, Dict, + Iterable, Mapping, Set, List, Optional, + Tuple, + Type, Union, cast, ) -from typing_extensions import TypedDict +from typing_extensions import Required, TypedDict import json import logging import datetime from pathlib import Path +from functools import lru_cache try: import tomllib # type: ignore (module not available in Python <3.11) except ModuleNotFoundError: import tomli as tomllib # Used for Python <3.11 -from db import SessionContext -from db.models import DialogueData +from db import SessionContext, Session +from db.models import DialogueData as DB_DialogueData, QueryData as DB_QueryData import queries.extras.resources as res -import queries.extras.dialogue_db as dialogue_db from queries import AnswerTuple # TODO:? Delegate answering from a resource to another resource or to another dialogue # TODO:? í ávaxtasamtali "ég vil panta flug" "viltu að ég geymi ávaxtapöntunina eða eyði henni?" ... -# TODO: Add timezone info to json encoding/decoding? -# TODO: FIX TYPE HINTS (esp. 'Any') # TODO: Add specific prompt handling to DSM to remove result from DSM. # TODO: Add try-except blocks where appropriate # TODO: Add "needs_confirmation" to TOML files (skip fulfilled, go straight to confirmed) @@ -63,13 +64,177 @@ _DEFAULT_EXPIRATION_TIME = 30 * 60 # By default a dialogue expires after 30 minutes _FINAL_RESOURCE_NAME = "Final" +_JSONTypes = Union[None, int, bool, str, List["_JSONTypes"], Dict[str, "_JSONTypes"]] +_TOMLTypes = Union[ + int, + float, + bool, + str, + datetime.datetime, + datetime.date, + datetime.time, + List["_TOMLTypes"], + Dict[str, "_TOMLTypes"], +] + + +class _ExtrasType(Dict[str, _TOMLTypes], TypedDict, total=False): + """Structure of 'extras' key in dialogue TOML files.""" + + expiration_time: int + + +class DialogueTOMLStructure(TypedDict): + """Structure of a dialogue TOML file.""" + + resources: Required[List[Dict[str, _TOMLTypes]]] + dynamic_resources: List[Dict[str, _TOMLTypes]] + extras: _ExtrasType + + +# Keys for accessing saved client data for dialogues +_ACTIVE_DIALOGUE_KEY = "dialogue" +_RESOURCES_KEY = "resources" +_DYNAMIC_RESOURCES_KEY = "dynamic_resources" +_MODIFIED_KEY = "modified" +_EXTRAS_KEY = "extras" +_EXPIRATION_TIME_KEY = "expiration_time" + +# List of active dialogues, kept in querydata table +# (newer dialogues have higher indexes) +ActiveDialogueList = List[Tuple[str, str]] + + +class SerializedResource(Dict[str, _JSONTypes], TypedDict, total=False): + """ + Representation of the required keys of a serialized resource. + """ + + name: Required[str] + type: Required[str] + state: Required[int] + + +class DialogueDeserialized(TypedDict): + """ + Representation of the dialogue structure, + after it is loaded from the database and parsed. + """ + + resources: Iterable[res.Resource] + extras: _ExtrasType + + +class DialogueSerialized(TypedDict): + """ + Representation of the dialogue structure, + before it is saved to the database. + """ + + resources: Iterable[SerializedResource] + extras: str + + +class DialogueDataRow(TypedDict): + data: DialogueSerialized + expires_at: datetime.datetime + + +def _dialogue_serializer(data: DialogueDeserialized) -> DialogueSerialized: + """ + Prepare the dialogue data for writing into the database. + """ + return { + _RESOURCES_KEY: [ + cast(SerializedResource, res.RESOURCE_SCHEMAS[s.type]().dump(s)) + for s in data[_RESOURCES_KEY] + ], + # We just dump the entire extras dict as a string + _EXTRAS_KEY: json.dumps(data[_EXTRAS_KEY], cls=TOMLtoJSONEncoder), + } + + +def _dialogue_deserializer(data: DialogueSerialized) -> DialogueDeserialized: + """ + Prepare the dialogue data for working with + after it has been loaded from the database. + """ + return { + _RESOURCES_KEY: [ + cast(res.Resource, res.RESOURCE_SCHEMAS[s["type"]]().load(s)) + for s in data[_RESOURCES_KEY] + ], + # Load the extras dictionary from a JSON serialized string + _EXTRAS_KEY: json.loads(data[_EXTRAS_KEY], cls=JSONtoTOMLDecoder), + } + + +class TOMLtoJSONEncoder(json.JSONEncoder): + # Map TOML type to a JSON serialized form + _serializer_functions: Mapping[Type[Any], Callable[[Any], _JSONTypes]] = { + datetime.datetime: lambda o: { + "__type__": "datetime", + "year": o.year, + "month": o.month, + "day": o.day, + "hour": o.hour, + "minute": o.minute, + "second": o.second, + "microsecond": o.microsecond, + }, + datetime.date: lambda o: { + "__type__": "date", + "year": o.year, + "month": o.month, + "day": o.day, + }, + datetime.time: lambda o: { + "__type__": "time", + "hour": o.hour, + "minute": o.minute, + "second": o.second, + "microsecond": o.microsecond, + }, + } + + def default(self, o: _TOMLTypes) -> _JSONTypes: + f = self._serializer_functions.get(type(o)) + return f(o) if f else json.JSONEncoder.default(self, o) + + +class JSONtoTOMLDecoder(json.JSONDecoder): + # Map __type__ to nonserialized form + _type_conversions: Mapping[str, Type[_TOMLTypes]] = { + "datetime": datetime.datetime, + "date": datetime.date, + "time": datetime.time, + } + + def __init__(self, *args: Any, **kwargs: Any): + json.JSONDecoder.__init__( + self, object_hook=self.dialogue_decoding, *args, **kwargs + ) + + def dialogue_decoding(self, d: Dict[str, Any]) -> _TOMLTypes: + if "__type__" not in d: + return d + t: str = d.pop("__type__") + + c = self._type_conversions.get(t) + if c: + return c(**d) + logging.warning(f"No class found for __type__: {t}") + d["__type__"] = t + return d + + # Functions for generating prompts/answers # Arguments: resource, DSM, result object -AnsweringFunctionType = Callable[..., Optional[AnswerTuple]] +_AnsweringFunctionType = Callable[..., Optional[AnswerTuple]] # Difficult to type this correctly as the # Callable type is contravariant in its arguments parameter -AnsweringFunctionMap = Mapping[str, AnsweringFunctionType] +AnsweringFunctionMap = Mapping[str, _AnsweringFunctionType] # Filter functions for filtering nodes # when searching resource graph @@ -88,50 +253,45 @@ class ResourceGraphItem(TypedDict): ResourceGraph = Dict[res.Resource, ResourceGraphItem] -class DialogueTOMLStructure(TypedDict, total=False): - """Structure of a dialogue TOML file.""" - - resources: List[Dict[str, Any]] - dynamic_resources: List[Dict[str, Any]] - - -# Keys for accessing saved client data for dialogues -# (must match typed dict attributes below) -_RESOURCES_KEY = "resources" -_DYNAMIC_RESOURCES_KEY = "dynamic_resources" -_MODIFIED_KEY = "modified" -_EXTRAS_KEY = "extras" -_EXPIRATION_TIME_KEY = "expiration_time" - - -# List of active dialogues, kept in querydata table -ActiveDialogueList = List[str] - - -class ResourceNotFoundError(Exception): - ... - - ################################ # DIALOGUE STATE MANAGER # ################################ class DialogueStateManager: - ACTIVE_DIALOGUE_KEY = "dialogue" - - def __init__(self, client_id: Optional[str]) -> None: + def __init__(self, client_id: Optional[str], db_session: Session) -> None: + """Initialize DSM instance and fetch tthe active dialogues for a client.""" self._client_id = client_id + self._db_session = db_session # Database session of parent Query class + # Fetch active dialogues for this client + self._active_dialogues: ActiveDialogueList = self._get_active_dialogues() - def load_dialogue(self, dialogue_name: str): - # TODO: This should load dialogue data from dialoguedata table + def get_next_active_resource(self, dialogue_name: str) -> str: + """ + Fetch the next current resource for a given dialogue. + Used for banning nonterminals. + """ + for x, y in self._active_dialogues: + if x == dialogue_name: + return y + raise ValueError( + "get_last_active_resource called " + f"for non-active dialogue: {dialogue_name}" + ) + + def prepare_dialogue(self, dialogue_name: str): + """ + Prepare DSM instance for a specific dialogue. + Fetches saved state from database if dialogue is active. + """ + print("PREPARING DIALOGUE!") self._dialogue_name: str = dialogue_name # Dict mapping resource name to resource instance self._resources: Dict[str, res.Resource] = {} # Boolean indicating if the client is in this dialogue self._in_this_dialogue: bool = False # Extra information saved with the dialogue state - self._extras: Dict[str, Any] = {} + self._extras: _ExtrasType = {} # Answer for the current query self._answer_tuple: Optional[AnswerTuple] = None # Latest non-confirmed resource @@ -144,46 +304,76 @@ def load_dialogue(self, dialogue_name: str): self._timed_out: bool = False self._initial_resource = None - dialogue_saved_state: Optional[DialogueDatabaseDict] = self.dialogue_data( - dialogue_name + # If dialogue is active, the saved state is loaded, + # otherwise wait for hotword_activated() to be called + if self._dialogue_name in (x for x, _ in self._active_dialogues): + print("loading saved state...") + self._in_this_dialogue = True + self._load_saved_state() + print("done preparing dialogue!") + + @lru_cache(maxsize=30) + def _read_toml_file(self, dialogue_name: str) -> DialogueTOMLStructure: + """Read TOML file for given dialogue.""" + p = ( + Path(__file__).parent.parent.resolve() + / _TOML_FOLDER_NAME + / f"{dialogue_name}.toml" ) - if dialogue_saved_state: - self._saved_state = cast( - DialogueDBStructure, - json.loads(dialogue_saved_state, cls=res.DialogueJSONDecoder), - ) + f = p.read_text() - # Check that we have saved data for this dialogue and that it is not expired - if self._saved_state[_RESOURCES_KEY]: - self._in_this_dialogue = True - self.setup_resources() - else: - print("NO DIALOGUE DATA FOR", dialogue_name) + obj: DialogueTOMLStructure = tomllib.loads(f) # type: ignore + return obj - def setup_resources(self) -> None: + def _initialize_resources(self) -> None: """ - Load dialogue resources from TOML file and update their state from database data. + Loads dialogue structure from TOML file and + fills self._resources with empty Resource instances. """ - # TODO: Only initialize if not hotword activated - # Fetch empty resources from TOML - self._initialize_resources(self._dialogue_name) - if self._saved_state: - time_from_last_interaction = ( - datetime.datetime.now() - self._saved_state[_MODIFIED_KEY] + print("Reading TOML file...") + # Read TOML file containing a list of resources for the dialogue + obj: DialogueTOMLStructure = self._read_toml_file(self._dialogue_name) + assert ( + _RESOURCES_KEY in obj + ), f"No resources found in TOML file {self._dialogue_name}.toml" + print("creating resources...") + # Create resource instances from TOML data and return as a dict + for i, resource in enumerate(obj[_RESOURCES_KEY]): + assert "name" in resource, f"Name missing for resource {i+1}" + if "type" not in resource: + resource["type"] = "Resource" + # Create instances of Resource classes (and its subclasses) + # TODO: Maybe fix the type hinting + self._resources[resource["name"]] = res.RESOURCE_MAP[resource["type"]]( # type: ignore + **resource, order_index=i ) - # The dialogue timed out, nothing should be done - if time_from_last_interaction.total_seconds() >= self._expiration_time: - self._timed_out = True - return - # Update empty resources with data from database - for rname, resource in self._resources.items(): - if self._saved_state and rname in self._saved_state["resources"]: - resource.update(self._saved_state[_RESOURCES_KEY][rname]) - # Change from int to enum type - resource.state = res.ResourceState(resource.state) - # Set extra data from database - if self._saved_state and _EXTRAS_KEY in self._saved_state: - self._extras = self._saved_state.get(_EXTRAS_KEY) or self._extras + print(f"Resources created: {self._resources}") + # TODO: Create dynamic resource blueprints (factory)!!!!! + self._extras = obj.get(_EXTRAS_KEY, dict()) + # Get expiration time duration for this dialogue + self._expiration_time = self._extras.get( + _EXPIRATION_TIME_KEY, _DEFAULT_EXPIRATION_TIME + ) + # Create resource dependency relationship graph + self._initialize_resource_graph() + + def _load_saved_state(self) -> None: + """ + Fetch saved data from database for this + dialogue and restore resource class instances. + """ + saved_row = self._dialogue_data() + assert saved_row is not None + self._timed_out: bool = datetime.datetime.now() > saved_row["expires_at"] + if self._timed_out: + # TODO: Do something when a dialogue times out + logging.warning("THIS DIALOGUE IS TIMED OUT!!!") + return + saved_state: DialogueDeserialized = _dialogue_deserializer(saved_row["data"]) + # Load resources from saved state + self._resources = {r.name: r for r in saved_state["resources"]} + self._extras = saved_state["extras"] + # Create resource dependency relationship graph self._initialize_resource_graph() @@ -193,6 +383,7 @@ def _initialize_resource_graph(self) -> None: resource having children and parents according to what each resource requires. """ + print("Creating resource graph...") for resource in self._resources.values(): if resource.order_index == 0 and self._initial_resource is None: self._initial_resource = resource @@ -201,60 +392,15 @@ def _initialize_resource_graph(self) -> None: for req in resource.requires: self._resource_graph[self._resources[req]]["parents"].append(resource) self._resource_graph[resource]["children"].append(self._resources[req]) - - def _initialize_resources(self, filename: str) -> None: - """ - Loads dialogue structure from TOML file and - fills self._resources with empty Resource instances. - """ - if self._saved_state: - self._resources = {} - for rname, resource in self._saved_state[_RESOURCES_KEY].items(): - self._resources[rname] = resource - self._expiration_time = self._saved_state.get( - _EXPIRATION_TIME_KEY, _DEFAULT_EXPIRATION_TIME - ) - else: - p = ( - Path(__file__).parent.parent.resolve() - / _TOML_FOLDER_NAME - / f"{filename}.toml" - ) - f = p.read_text() - # Read TOML file containing a list of resources for the dialogue - obj: DialogueTOMLStructure = tomllib.loads(f) # type: ignore - assert _RESOURCES_KEY in obj, f"No resources found in TOML file {f}" - # Create resource instances from TOML data and return as a dict - for i, resource in enumerate(obj[_RESOURCES_KEY]): - assert "name" in resource, f"Name missing for resource {i+1}" - if "type" not in resource: - resource["type"] = "Resource" - # Create instances of Resource classes (and its subclasses) - self._resources[resource["name"]] = res.RESOURCE_MAP[resource["type"]]( - **resource, order_index=i - ) - self._expiration_time = obj.get( - _EXPIRATION_TIME_KEY, _DEFAULT_EXPIRATION_TIME - ) + print("Finished resource graph!") def add_dynamic_resource(self, resource_name: str, parent_name: str) -> None: """ Adds a dynamic resource to the dialogue from TOML file and updates the requirements of it's parents. """ - # TODO: should dynamic resources be loaded from TOML at initialization? - # Loading dynamic resources from TOML - p = ( - Path(__file__).parent.parent.resolve() - / _TOML_FOLDER_NAME - / f"{self._dialogue_name}.toml" - ) - f = p.read_text() - - obj: DialogueTOMLStructure = tomllib.loads(f) # type: ignore - assert ( - _DYNAMIC_RESOURCES_KEY in obj - ), f"No dynamic resources found in TOML file {f}" + raise NotImplementedError() + # TODO: Create separate blueprint factory class for creating dynamic resources parent_resource: res.Resource = self.get_resource(parent_name) order_index: int = parent_resource.order_index dynamic_resources: Dict[str, res.Resource] = {} @@ -312,6 +458,7 @@ def _add_child_resource(resource: res.Resource) -> None: self._find_current_resource() def duplicate_dynamic_resource(self, original: res.Resource) -> None: + raise NotImplementedError() suffix = ( len( [ @@ -348,9 +495,12 @@ def _recursive_deep_copy(resource: res.Resource) -> None: self._find_current_resource() def hotword_activated(self) -> None: + # TODO: Add some checks if we accidentally go into this while the dialogue is ongoing self._in_this_dialogue = True - print("In hotword activated") - self.setup_resources() + # Set up resources for working with them + self._initialize_resources() + # Set dialogue as newest active dialogue + self._active_dialogues.append((self._dialogue_name, self.current_resource.name)) def pause_dialogue(self) -> None: ... # TODO @@ -368,6 +518,10 @@ def dialogue_name(self) -> Optional[str]: return self._dialogue_name return None + @property + def active_dialogue(self) -> Optional[str]: + return self._active_dialogues[-1][0] if self._active_dialogues else None + @property def current_resource(self) -> res.Resource: if self._current_resource is None: @@ -376,10 +530,7 @@ def current_resource(self) -> res.Resource: return self._current_resource def get_resource(self, name: str) -> res.Resource: - try: - return self._resources[name] - except KeyError: - raise ResourceNotFoundError(f"Resource {name} not found") + return self._resources[name] @property def extras(self) -> Dict[str, Any]: @@ -616,10 +767,8 @@ def serialize_data(self) -> Dict[str, Optional[str]]: ds_json = json.dumps( { _RESOURCES_KEY: self._resources, - _MODIFIED_KEY: datetime.datetime.now(), _EXTRAS_KEY: self._extras, }, - cls=res.DialogueJSONEncoder, ) # Wrap data before saving dialogue state into client data # (due to custom JSON serialization) @@ -629,31 +778,138 @@ def serialize_data(self) -> Dict[str, Optional[str]]: ################################ # Database functions # ################################ - def dialogue_data( - self, dialogue_key: Optional[str] - ) -> Optional[DialogueDatabaseDict]: + + def _get_active_dialogues(self) -> ActiveDialogueList: + """Get list of active dialogues from database for current client.""" + assert self._client_id, "_get_active_dialogues() called without client ID!" + + active: ActiveDialogueList = [] + with SessionContext(session=self._db_session, read_only=True) as session: + try: + row: Optional[DB_QueryData] = ( + session.query(DB_QueryData) + .filter(DB_QueryData.client_id == self._client_id) # type: ignore + .filter(DB_QueryData.key == _ACTIVE_DIALOGUE_KEY) + ).one_or_none() + if row is not None: + active = cast(ActiveDialogueList, row.data) + except Exception as e: + logging.error( + "Error fetching client '{0}' query data for key '{1}' from db: {2}".format( + self._client_id, _ACTIVE_DIALOGUE_KEY, e + ) + ) + return active + + def _dialogue_data(self) -> Optional[DialogueDataRow]: """ Fetch client_id-associated dialogue data stored - in the dialoguedata table based on the dialogue key + in the dialoguedata table based on the dialogue key. """ - if not self._client_id or not dialogue_key: - return None - with SessionContext(read_only=True) as session: + assert ( + self._client_id and self._dialogue_name + ), "_dialogue_data() called without client ID or dialogue name!" + + with SessionContext(session=self._db_session, read_only=True) as session: try: - dialogue_data = ( - session.query(DialogueData) - .filter(DialogueData.dialogue_key == dialogue_key) - .filter(DialogueData.client_id == self._client_id) + row: Optional[DB_DialogueData] = ( + session.query(DB_DialogueData) + .filter(DB_DialogueData.dialogue_key == self._dialogue_name) # type: ignore + .filter(DB_DialogueData.client_id == self._client_id) ).one_or_none() - return ( - None - if dialogue_data is None - else cast(DialogueDatabaseDict, dialogue_data.data) - ) + if row: + return { + "data": row.data, + "expires_at": row.expires_at, + } except Exception as e: logging.error( - "Error fetching client '{0}' query data for key '{1}' from db: {2}".format( - self._client_id, dialogue_key, e + "Error fetching client '{0}' dialogue data for key '{1}' from db: {2}".format( + self._client_id, self._dialogue_name, e ) ) return None + + def update_dialogue_data(self) -> None: + """ + Save current state of dialogue to dialoguedata table in database, + along with updating list of active dialogues in querydata table. + """ + assert ( + self._client_id and self._dialogue_name + ), "_dialogue_data() called without client ID or dialogue name!" + + now = datetime.datetime.now() + expires_at = now + datetime.timedelta(seconds=self._expiration_time) + with SessionContext(session=self._db_session, commit=True) as session: + try: + existing_dd_row: Optional[DB_DialogueData] = session.get( # type: ignore + DB_DialogueData, (self._client_id, self._dialogue_name) + ) + # Write data to dialoguedata table + if existing_dd_row: + # UPDATE existing row + existing_dd_row.modified = now # type: ignore + existing_dd_row.data = _dialogue_serializer( # type: ignore + { + _RESOURCES_KEY: self._resources.values(), + _EXTRAS_KEY: self._extras, + } + ) + existing_dd_row.expires_at = expires_at # type: ignore + else: + # INSERT new row + dialogue_row = DB_DialogueData( + client_id=self._client_id, + dialogue_key=self._dialogue_name, + created=now, + modified=now, + data=_dialogue_serializer( + { + _RESOURCES_KEY: self._resources.values(), + _EXTRAS_KEY: self._extras, + } + ), + expires_at=expires_at, + ) + session.add(dialogue_row) # type: ignore + except Exception as e: + logging.error( + "Error upserting client '{0}' dialogue data for key '{1}' into db: {2}".format( + self._client_id, self._dialogue_name, e + ) + ) + try: + # Write active dialogues to querydata table + existing_qd_row: Optional[DB_QueryData] = session.get( # type: ignore + DB_QueryData, (self._client_id, _ACTIVE_DIALOGUE_KEY) + ) + if existing_qd_row: + # TODO: Move this into some prettier place + # Make sure the (dialogue name, current resource) pair is up to date for this dialogue + self._active_dialogues = [ + (x, y) + if x != self._dialogue_name + else (x, self.current_resource.name) + for x, y in self._active_dialogues + ] + # UPDATE existing row + existing_qd_row.data = self._active_dialogues # type: ignore + existing_qd_row.modified = now # type: ignore + else: + # INSERT new row + querydata_row = DB_QueryData( + client_id=self._client_id, + key=_ACTIVE_DIALOGUE_KEY, + created=now, + modified=now, + data=self._active_dialogues, + ) + session.add(querydata_row) # type: ignore + except Exception as e: + logging.error( + "Error upserting client '{0}' dialogue data for key '{1}' into db: {2}".format( + self._client_id, self._dialogue_name, e + ) + ) + return diff --git a/queries/extras/dialogue_db.py b/queries/extras/dialogue_db.py deleted file mode 100644 index f4cf5e9b..00000000 --- a/queries/extras/dialogue_db.py +++ /dev/null @@ -1,149 +0,0 @@ -""" - - Greynir: Natural language processing for Icelandic - - Copyright (C) 2022 Miðeind ehf. - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see http://www.gnu.org/licenses/. - - - Serialization/Deserialization functions for dialogue data and resources. - -""" -from typing import Any, Callable, Dict, List, Mapping, Type, Union, cast -from typing_extensions import Required, TypedDict - -import datetime -import json -import logging - -import queries.extras.resources as res - -_json_types = Union[None, int, bool, str, List["_json_types"], Dict[str, "_json_types"]] - - -class ResourceSerialized(Dict[str, _json_types], TypedDict, total=False): - """ - Representation of a serialized resource - and the required keys. - """ - - name: Required[str] - type: Required[str] - state: Required[res.ResourceState] - - -class DialogueDeserialized(TypedDict): - """ - Representation of the dialogue structure, - after it is loaded from the database and parsed. - """ - - resources: List[res.Resource] - extras: Dict[str, _json_types] - - -class DialogueSerialized(TypedDict): - """ - Representation of the dialogue structure, - before it is saved to the database. - """ - - resources: List[ResourceSerialized] - extras: str - - -def dialogue_serializer(data: DialogueDeserialized) -> DialogueSerialized: - """ - Prepare the dialogue data for writing into the database. - """ - return { - "resources": [ - cast(ResourceSerialized, res.RESOURCE_SCHEMAS[s.type]().dump(s)) - for s in data["resources"] - ], - # We just dump the entire extras dict as a string - "extras": json.dumps(data["extras"], cls=ExtendedJSONEncoder), - } - - -def dialogue_deserializer(data: DialogueSerialized) -> DialogueDeserialized: - """ - Prepare the dialogue data for working with - after it has been loaded from the database. - """ - return { - "resources": [ - cast(res.Resource, res.RESOURCE_SCHEMAS[s["type"]]().load(s)) - for s in data["resources"] - ], - "extras": json.loads(data["extras"], cls=ExtendedJSONDecoder), - } - - -class ExtendedJSONEncoder(json.JSONEncoder): - # Map types other than resources to their serialized forms - _serializer_functions: Mapping[Type[Any], Callable[[Any], _json_types]] = { - datetime.datetime: lambda o: { - "__type__": "datetime", - "year": o.year, - "month": o.month, - "day": o.day, - "hour": o.hour, - "minute": o.minute, - "second": o.second, - "microsecond": o.microsecond, - }, - datetime.date: lambda o: { - "__type__": "date", - "year": o.year, - "month": o.month, - "day": o.day, - }, - datetime.time: lambda o: { - "__type__": "time", - "hour": o.hour, - "minute": o.minute, - "second": o.second, - "microsecond": o.microsecond, - }, - } - - def default(self, o: Any) -> Any: - f = ExtendedJSONEncoder._serializer_functions.get(type(o)) - return f(o) if f else json.JSONEncoder.default(self, o) - - -class ExtendedJSONDecoder(json.JSONDecoder): - _type_conversions: Mapping[str, Type[Any]] = { - "datetime": datetime.datetime, - "date": datetime.date, - "time": datetime.time, - } - - def __init__(self, *args: Any, **kwargs: Any): - json.JSONDecoder.__init__( - self, object_hook=self.dialogue_decoding, *args, **kwargs - ) - - def dialogue_decoding(self, d: Dict[Any, Any]) -> Any: - if "__type__" not in d: - return d - t = d.pop("__type__") - - c = self._type_conversions.get(t) - if c: - return c(**d) - logging.warning(f"No class found for __type__: {t}") - d["__type__"] = t - return d diff --git a/queries/extras/resources.py b/queries/extras/resources.py index 3419dca0..a4ee8163 100644 --- a/queries/extras/resources.py +++ b/queries/extras/resources.py @@ -29,7 +29,6 @@ MutableMapping, Optional, Type, - Union, ) import datetime @@ -37,8 +36,6 @@ from dataclasses import dataclass, field as data_field from marshmallow import Schema, fields, post_load -_json_types = Union[None, int, bool, str, List["_json_types"], Dict[str, "_json_types"]] - class ResourceState(IntFlag): """Enum representing the different states a dialogue resource can be in.""" @@ -158,10 +155,10 @@ class ResourceSchema(Schema): name = fields.Str(required=True) type = fields.Str(required=True) - data = fields.Raw() + data = fields.Raw(allow_none=True) state = fields.Enum(IntFlag, by_value=True, required=True) requires = fields.List(fields.Str(), required=True) - prompts = fields.Mapping(fields.Str(), fields.Str()) + prompts = fields.Mapping(fields.Str(), fields.Str(), allow_none=True) cascade_state = fields.Bool() prefer_over_wrapper = fields.Bool() needs_confirmation = fields.Bool() @@ -270,7 +267,7 @@ class DatetimeResource(Resource): class DatetimeResourceSchema(ResourceSchema): - data = fields.NaiveDateTime() + data = fields.NaiveDateTime(allow_none=True) RESOURCE_MAP[DatetimeResource.__name__] = DatetimeResource diff --git a/queries/fruitseller.py b/queries/fruitseller.py index 3d6dd468..67dd2c50 100644 --- a/queries/fruitseller.py +++ b/queries/fruitseller.py @@ -29,9 +29,8 @@ WrapperResource, ) -# Indicate that this module wants to handle dialogue parse trees for queries, -# as opposed to simple literal text strings -HANDLE_DIALOGUE = True +HANDLE_TREE = True + DIALOGUE_NAME = "fruitseller" # The grammar nonterminals this module wants to handle @@ -49,16 +48,9 @@ def banned_nonterminals(q: Query) -> None: if not q.in_dialogue(DIALOGUE_NAME): q.ban_nonterminal("QFruitSellerQuery") return - resource: Resource = q.dsm.current_resource - if resource.name == "Fruits": + resource_name: str = q.dsm.get_next_active_resource(DIALOGUE_NAME) + if resource_name == "Fruits": q.ban_nonterminal("QFruitDateQuery") - if resource.is_unfulfilled: - q.ban_nonterminal("QFruitYes") - q.ban_nonterminal("QFruitNo") - elif resource.name == "DateTime": - if resource.is_unfulfilled: - q.ban_nonterminal("QFruitYes") - q.ban_nonterminal("QFruitNo") def _generate_fruit_answer( @@ -145,7 +137,9 @@ def _list_items(items: Any) -> str: return natlang_seq(item_list) -def QFruitStartQuery(node: Node, params: ParamList, result: Result): +def QFruitHotWord(node: Node, params: ParamList, result: Result): + result.qtype = "QFRUITACTIVE" + print("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABSFDDFS") Query.get_dsm(result).hotword_activated() @@ -255,7 +249,7 @@ def _add_date( resource: DateResource, dsm: DialogueStateManager, result: Result ) -> None: if dsm.get_resource("Fruits").is_confirmed: - resource.set_date(result["delivery_date"]) + resource.data = result["delivery_date"] resource.state = ResourceState.FULFILLED time_resource = dsm.get_resource("Time") datetime_resource = dsm.get_resource("DateTime") @@ -293,7 +287,7 @@ def _add_time( resource: TimeResource, dsm: DialogueStateManager, result: Result ) -> None: if dsm.get_resource("Fruits").is_confirmed: - resource.set_time(result["delivery_time"]) + resource.data = result["delivery_time"] resource.state = ResourceState.FULFILLED date_resource = dsm.get_resource("Date") datetime_resource = dsm.get_resource("DateTime") @@ -363,14 +357,16 @@ def sentence(state: QueryStateDict, result: Result) -> None: """Called when sentence processing is complete""" q: Query = state["query"] dsm: DialogueStateManager = q.dsm - + print("Wtf, checking if im in this dialogue") if dsm.not_in_dialogue(): q.set_error("E_QUERY_NOT_UNDERSTOOD") return # Successfully matched a query type try: + print("Getting answer...") ans = dsm.get_answer(_ANSWERING_FUNCTIONS, result) + print("GOT IT...") if not ans: q.set_error("E_QUERY_NOT_UNDERSTOOD") return diff --git a/queries/grammars/pizza.grammar b/queries/grammars/pizza.grammar index cd06cc77..fc9e4a67 100644 --- a/queries/grammars/pizza.grammar +++ b/queries/grammars/pizza.grammar @@ -10,21 +10,20 @@ /þgf = þgf /ef = ef -# Currently, the Hotword needs to be top-level, because the QPizza nonterminal is banned. Query → - QPizzaHotWord - | QPizza + QPizza '?'? QPizza → - QPizzaQuery '?'? + QPizzaQuery + | QPizzaHotWord QPizzaQuery → - QPizzaDialogue '?'? + QPizzaDialogue # Hotwords are used to initialize the conversation. QPizzaHotWord → - QPizzaWord/nf '?'? # e.g. "Pítsa" - | QPizzaRequestBare '?'? # e.g. "Ég vil panta pizzu." + QPizzaWord/nf # e.g. "Pítsa" + | QPizzaRequestBare # e.g. "Ég vil panta pizzu." # Doesn't allow for any order specification, e.g. the number of pizzas, only "Ég vil pizzu." QPizzaRequestBare → diff --git a/queries/grammars/theater.grammar b/queries/grammars/theater.grammar index a7c02f3c..241bbc4a 100644 --- a/queries/grammars/theater.grammar +++ b/queries/grammars/theater.grammar @@ -1,18 +1,20 @@ Query → - QTheaterHotWord | QTheater + QTheater '?'? -QTheater → QTheaterQuery +QTheater → + QTheaterQuery + | QTheaterHotWord QTheaterQuery → - QTheaterDialogue '?'? + QTheaterDialogue QTheaterHotWord → - QTheaterNames '?'? - | QTheaterEgVil? QTheaterKaupaFaraFaPanta "leikhúsmiða" '?'? - | QTheaterEgVil? QTheaterKaupaFaraFaPanta "miða" "í" QTheaterNames '?'? - | QTheaterEgVil? QTheaterKaupaFaraFaPanta "miða" "á" QTheaterNames "sýningu" '?'? - | QTheaterEgVil? QTheaterKaupaFaraFaPanta QTheaterNames '?'? - | QTheaterEgVil? QTheaterKaupaFaraFaPanta "leikhússýningu" '?'? + QTheaterNames + | QTheaterEgVil? QTheaterKaupaFaraFaPanta "leikhúsmiða" + | QTheaterEgVil? QTheaterKaupaFaraFaPanta "miða" "í" QTheaterNames + | QTheaterEgVil? QTheaterKaupaFaraFaPanta "miða" "á" QTheaterNames "sýningu" + | QTheaterEgVil? QTheaterKaupaFaraFaPanta QTheaterNames + | QTheaterEgVil? QTheaterKaupaFaraFaPanta "leikhússýningu" QTheaterNames → 'leikhús' diff --git a/queries/pizza.py b/queries/pizza.py index e82d6640..b722a9ba 100644 --- a/queries/pizza.py +++ b/queries/pizza.py @@ -47,7 +47,6 @@ DialogueStateManager, ) -_DIALOGUE_NAME = "pizza" _PIZZA_QTYPE = "pizza" _START_DIALOGUE_QTYPE = "pizza_start" @@ -62,15 +61,13 @@ def help_text(lemma: str) -> str: ) -# This module wants to handle dialogue parse trees for queries -HANDLE_DIALOGUE = True +HANDLE_TREE = True # This module involves dialogue functionality DIALOGUE_NAME = "pizza" -HOTWORD_NONTERMINALS = {"QPizzaHotWord"} # The grammar nonterminals this module wants to handle -QUERY_NONTERMINALS = {"QPizza"}.union(HOTWORD_NONTERMINALS) +QUERY_NONTERMINALS = {"QPizza"} # The context-free grammar for the queries recognized by this plug-in module GRAMMAR = read_grammar_file("pizza") @@ -82,7 +79,7 @@ def banned_nonterminals(q: Query) -> None: allowed due to the state of the dialogue """ # TODO: Implement this - if q.active_dialogue != DIALOGUE_NAME: + if not q.in_dialogue(DIALOGUE_NAME): q.ban_nonterminal("QPizzaQuery") diff --git a/queries/theater.py b/queries/theater.py index 8be7cf7e..357f2214 100644 --- a/queries/theater.py +++ b/queries/theater.py @@ -70,15 +70,13 @@ def help_text(lemma: str) -> str: ) -# This module wants to handle dialogue parse trees for queries -HANDLE_DIALOGUE = True +HANDLE_TREE = True # This module involves dialogue functionality DIALOGUE_NAME = "theater" -HOTWORD_NONTERMINALS = {"QTheaterHotWord"} # The grammar nonterminals this module wants to handle -QUERY_NONTERMINALS = {"QTheater"}.union(HOTWORD_NONTERMINALS) +QUERY_NONTERMINALS = {"QTheater"} # The context-free grammar for the queries recognized by this plug-in module GRAMMAR = read_grammar_file("theater") @@ -90,11 +88,11 @@ def banned_nonterminals(q: Query) -> None: allowed due to the state of the dialogue """ # TODO: Put this back in when the dsm has access to the active dialogue again. - if q.active_dialogue != DIALOGUE_NAME: + if not q.in_dialogue(DIALOGUE_NAME): q.ban_nonterminal("QTheaterQuery") return - resource: Resource = q.dsm.current_resource - if resource.name == "Show": + resource:str = q.dsm.get_next_active_resource(DIALOGUE_NAME) + if resource == "Show": q.ban_nonterminal_set( { "QTheaterShowDateQuery", @@ -107,14 +105,7 @@ def banned_nonterminals(q: Query) -> None: "QTheaterSeatOptions", } ) - if resource.is_unfulfilled: - q.ban_nonterminal_set( - { - "QTheaterShowLength", - "QTheaterShowPrice", - } - ) - elif resource.name == "ShowDateTime": + elif resource == "ShowDateTime": q.ban_nonterminal_set( { "QTheaterShowSeatCountQuery", @@ -124,7 +115,7 @@ def banned_nonterminals(q: Query) -> None: "QTheaterShowOnlyName", } ) - elif resource.name == "ShowSeatCount": + elif resource == "ShowSeatCount": q.ban_nonterminal_set( { "QTheaterShowLocationQuery", @@ -135,7 +126,7 @@ def banned_nonterminals(q: Query) -> None: "QTheaterShowOnlyName", } ) - elif resource.name == "ShowSeatRow": + elif resource == "ShowSeatRow": q.ban_nonterminal_set( { "QTheaterShowSeats", @@ -145,7 +136,7 @@ def banned_nonterminals(q: Query) -> None: "QTheaterShowOnlyName", } ) - elif resource.name == "ShowSeatNumber": + elif resource == "ShowSeatNumber": q.ban_nonterminal_set( { "QTheaterSeatCountNum", @@ -153,14 +144,6 @@ def banned_nonterminals(q: Query) -> None: "QTheaterShowOnlyName", } ) - if resource.is_unfulfilled: - q.ban_nonterminal_set( - { - "QTheaterYes", - "QTheaterNo", - } - ) - class ShowType(TypedDict): title: str diff --git a/query.py b/query.py index 63a39c68..028fee41 100755 --- a/query.py +++ b/query.py @@ -468,6 +468,12 @@ def process_queries( # But this processor is not interested in any of the nonterminals # in this query's parse forest: don't waste more cycles on it return False + + # Prepare dialogue state manager before processing + dialogue_name: Optional[str] = getattr(processor, "DIALOGUE_NAME", None) + if dialogue_name: + query.dsm.prepare_dialogue(dialogue_name) + with self.context(session, processor, query=query) as state: for query_tree in self._query_trees: print( @@ -483,7 +489,7 @@ def process_queries( self.process_sentence(state, query_tree) if query.has_answer(): # The processor successfully answered the query: We're done - # Also save any changes to dialogue data, if needed + query.dsm.update_dialogue_data() return True return False @@ -581,24 +587,8 @@ def __init__( # Banned nonterminals for this query, # dynamically generated from query modules self._banned_nonterminals: Set[str] = set() - - # Dialogue state manager and dialogue data, used for dialogue modules - # The dialogue state manager - self._dsm: DSM = DSM(self.client_id) - # The names of the active dialogues, empty list if no dialogue is active - self._active_dialogues: ActiveDialogueList = cast( - ActiveDialogueList, self.client_data(DSM.ACTIVE_DIALOGUE_KEY) or list() - ) - # The active dialogue data, empty dict if no dialogue is active - self._dialogue_data: DialogueDataDict = ( - cast( - DialogueDataDict, self.dialogue_data(dialogue_key=self.active_dialogue) - ) - or dict() - ) - # Load the dialogue for the active dialogue if present - if self._active_dialogues: - self._dsm.load_dialogue(self._active_dialogues[0]) + # The dialogue state manager, used for dialogue modules + self._dsm: DSM = DSM(self._client_id, self._session) def _preprocess_query_string(self, q: str) -> str: """Preprocess the query string prior to further analysis""" @@ -621,7 +611,7 @@ def init_class(cls) -> None: all_procs: List[ModuleType] = [] tree_procs: List[Tuple[int, ModuleType]] = [] text_procs: List[Tuple[int, Callable[["Query"], bool]]] = [] - dialogue_procs: List[Tuple[int, ModuleType]] = [] + # Load the query processor modules found in the # queries directory. The modules can be tree and/or text processors, # and we sort them into two lists, accordingly. @@ -636,10 +626,6 @@ def init_class(cls) -> None: # This is a tree processor is_proc = True tree_procs.append((priority, m)) - if getattr(m, "HANDLE_DIALOGUE", False): - # This is a dialogue processor - is_proc = True - dialogue_procs.append((priority, m)) handle_plain_text = getattr(m, "handle_plain_text", None) if handle_plain_text is not None: # This is a text processor: @@ -656,13 +642,10 @@ def init_class(cls) -> None: # so that the higher-priority ones get invoked bfore the lower-priority ones cls._tree_processors = [t[1] for t in sorted(tree_procs, key=lambda x: -x[0])] cls._text_processors = [t[1] for t in sorted(text_procs, key=lambda x: -x[0])] - cls._dialogue_processors = [ - t[1] for t in sorted(dialogue_procs, key=lambda x: -x[0]) - ] # Obtain query grammar fragments from the tree processors grammar_fragments: List[str] = [] - for processor in cls._dialogue_processors + cls._tree_processors: + for processor in cls._tree_processors: # Check whether this dialogue/tree processor supplies a query grammar fragment fragment = getattr(processor, "GRAMMAR", None) if fragment and isinstance(fragment, str): @@ -787,8 +770,8 @@ def parse(self, result: ResponseDict) -> bool: # Log the query string as seen by the parser print("Query is: '{0}'".format(actual_q)) - # Ban certain nonterminals for this query - for t in Query._dialogue_processors: + # Fetch banned nonterminals for this query + for t in self._tree_processors: ban_func = getattr(t, "banned_nonterminals", None) if ban_func is not None: ban_func(self) @@ -839,82 +822,82 @@ def execute_from_plain_text(self) -> bool: handle_plain_text(self) for handle_plain_text in self._text_processors ) - def execute_from_dialogue(self) -> bool: - """Execute the query or queries contained in the previously parsed tree; - return True if successful""" - if self._tree is None: - self.set_error("E_QUERY_NOT_PARSED") - return False - # Try each dialogue processor in turn, in priority order (highest priority first) - for processor in self._dialogue_processors: - self._error = None - self._qtype = None - # Process the dialogue, which has only one sentence, but may - # have multiple matching query nonterminals - # (children of Query in the grammar) - try: - # Note that passing query=self here means that the - # "query" field of the TreeStateDict is populated, - # turning it into a QueryStateDict. - processor_query_types: Set[str] = getattr( - processor, "QUERY_NONTERMINALS", set() - ) - if self._tree.query_nonterminals.isdisjoint(processor_query_types): - # But this processor is not interested in any of the nonterminals - # in this query's parse forest: don't waste more cycles on it - continue - dialogue_name: Optional[str] = getattr(processor, "DIALOGUE_NAME", None) - if dialogue_name: - # This processor uses dialogue functionality - - self._dialogue_data = ( - cast(DialogueDataDict, self.dialogue_data(dialogue_name)) - or dict() - ) - self._dsm = DSM(self._dialogue_data) - # Query matches this dialogue processor, start DialogueStateManager - self.dsm.load_dialogue(dialogue_name) - print("DIALOGUE LOADED: ", dialogue_name) - print("DSM DATA: ", self._dialogue_data) - if self.dsm.timed_out: - # TODO: If the DialogueStateManager timed out, - # set active_dialogue to the last active dialogue - # - timed_out_ans = self.dsm.get_resource("Final").prompts[ - "timed_out" - ] - ans = (dict(answer=timed_out_ans), timed_out_ans, timed_out_ans) - self.set_answer(*ans) - self.update_dialogue_data() - return True - with self._tree.context(self._session, processor, query=self) as state: - for query_tree in self._tree._query_trees: - # Is the processor interested in the root nonterminal - # of this query tree? - if query_tree.string_self() in processor_query_types: - # Yes: hand the query tree over to the processor - try: - self._tree.process_sentence(state, query_tree) - except ResourceNotFoundError as e: - print("Resource not found: ", e) - pass - print( - "DO WE HAVE AN ANSWER?", self.has_answer(), self._error - ) - if self.has_answer(): - print("HAS ANSWER") - # The processor successfully answered the query: We're done - # Also save any changes to dialogue data, if needed - self.update_dialogue_data() - print("DIALOGUE DATA UPDATED") - return True - except Exception as e: - logging.error( - f"Exception in execute_from_dialogue('{processor.__name__}') " - f"for query '{self._query}': {repr(e)}" - ) - # No processor was able to answer the query - return False + # def execute_from_dialogue(self) -> bool: + # """Execute the query or queries contained in the previously parsed tree; + # return True if successful""" + # if self._tree is None: + # self.set_error("E_QUERY_NOT_PARSED") + # return False + # # Try each dialogue processor in turn, in priority order (highest priority first) + # for processor in self._dialogue_processors: + # self._error = None + # self._qtype = None + # # Process the dialogue, which has only one sentence, but may + # # have multiple matching query nonterminals + # # (children of Query in the grammar) + # try: + # # Note that passing query=self here means that the + # # "query" field of the TreeStateDict is populated, + # # turning it into a QueryStateDict. + # processor_query_types: Set[str] = getattr( + # processor, "QUERY_NONTERMINALS", set() + # ) + # if self._tree.query_nonterminals.isdisjoint(processor_query_types): + # # But this processor is not interested in any of the nonterminals + # # in this query's parse forest: don't waste more cycles on it + # continue + # dialogue_name: Optional[str] = getattr(processor, "DIALOGUE_NAME", None) + # if dialogue_name: + # # This processor uses dialogue functionality + + # self._dialogue_data = ( + # cast(DialogueDataDict, self.dialogue_data(dialogue_name)) + # or dict() + # ) + # self._dsm = DSM(self._dialogue_data) + # # Query matches this dialogue processor, start DialogueStateManager + # self._dsm.load_dialogue(dialogue_name) + # print("DIALOGUE LOADED: ", dialogue_name) + # print("DSM DATA: ", self._dialogue_data) + # if self.dsm.timed_out: + # # TODO: If the DialogueStateManager timed out, + # # set active_dialogue to the last active dialogue + # # + # timed_out_ans = self.dsm.get_resource("Final").prompts[ + # "timed_out" + # ] + # ans = (dict(answer=timed_out_ans), timed_out_ans, timed_out_ans) + # self.set_answer(*ans) + # self.update_dialogue_data() + # return True + # with self._tree.context(self._session, processor, query=self) as state: + # for query_tree in self._tree._query_trees: + # # Is the processor interested in the root nonterminal + # # of this query tree? + # if query_tree.string_self() in processor_query_types: + # # Yes: hand the query tree over to the processor + # try: + # self._tree.process_sentence(state, query_tree) + # except ResourceNotFoundError as e: + # print("Resource not found: ", e) + # pass + # print( + # "DO WE HAVE AN ANSWER?", self.has_answer(), self._error + # ) + # if self.has_answer(): + # print("HAS ANSWER") + # # The processor successfully answered the query: We're done + # # Also save any changes to dialogue data, if needed + # self.update_dialogue_data() + # print("DIALOGUE DATA UPDATED") + # return True + # except Exception as e: + # logging.error( + # f"Exception in execute_from_dialogue('{processor.__name__}') " + # f"for query '{self._query}': {repr(e)}" + # ) + # # No processor was able to answer the query + # return False def execute_from_tree(self) -> bool: """Execute the query or queries contained in the previously parsed tree; @@ -922,6 +905,12 @@ def execute_from_tree(self) -> bool: if self._tree is None: self.set_error("E_QUERY_NOT_PARSED") return False + + # TODO: We should probably try the best tree on all processors + # before trying the worse trees, instead of all trees per processor, + # as this sometimes causes issues (e.g. similar parse trees for hue and sonos). + # TODO: Create generator function which dynamically prioritizes processors + # Try each tree processor in turn, in priority order (highest priority first) for processor in self._tree_processors: self._error = None @@ -1169,23 +1158,6 @@ def get_dsm(result: Result) -> DSM: def dsm(self) -> DSM: return self._dsm - @property - def active_dialogue(self) -> Optional[str]: - """Return last dialogue in active dialogues list, otherwise None.""" - return self._active_dialogues[-1] if self._active_dialogues else None - - def update_dialogue_data(self) -> None: - """Update the dialogue data for the given client if a dialogue module was used""" - if self._dsm is not None: - # Save the dialogue state when a dialogue module query - # is successfully processed - if self.active_dialogue: - self.set_dialogue_data( - self.active_dialogue, - cast(DialogueDatabaseDict, self._dsm.serialize_data()), - update_in_place=True, - ) - def response(self) -> Optional[ResponseType]: """Return the detailed query answer""" return self._response @@ -1319,175 +1291,7 @@ def store_query_data( return False def in_dialogue(self, dialogue_name: str) -> bool: - return self.active_dialogue == dialogue_name - - def dialogue_data( - self, dialogue_key: Optional[str] - ) -> Optional[DialogueDatabaseDict]: - """ - Fetch client_id-associated dialogue data stored - in the dialoguedata table based on the dialogue key - """ - if not self.client_id or not dialogue_key: - return None - with SessionContext(read_only=True) as session: - try: - dialogue_data = ( - session.query(DialogueData) - .filter(DialogueData.dialogue_key == dialogue_key) - .filter(DialogueData.client_id == self.client_id) - ).one_or_none() - return ( - None - if dialogue_data is None - else cast(DialogueDatabaseDict, dialogue_data.data) - ) - except Exception as e: - logging.error( - "Error fetching client '{0}' query data for key '{1}' from db: {2}".format( - self.client_id, dialogue_key, e - ) - ) - return None - - def set_dialogue_data( - self, - dialogue_key: str, - data: DialogueDatabaseDict, - *, - update_in_place: bool = False, - ) -> bool: - """ - Setter for client dialogue data. - Also sets the active dialogue in the query data. - """ - if not self.client_id or not dialogue_key: - logging.warning("Couldn't save query data, no client ID or key") - return False - return Query.store_dialogue_data( - self.client_id, dialogue_key, data, update_in_place=update_in_place - ) - # Query.store_query_data( - # self.client_id, - # DSM.DIALOGUE_DATA_KEY, - # {"active_dialogue": dialogue_key}, - # update_in_place=True, - # ) - - @staticmethod - def store_dialogue_data( - client_id: str, - dialogue_key: str, - data: DialogueDatabaseDict, - *, - update_in_place: bool = False, - ) -> bool: - """Save client dialogue data in the database, under the given dialogue key""" - if not client_id or not dialogue_key: - return False - now = datetime.utcnow() - try: - with SessionContext(commit=True) as session: - row = ( - session.query(DialogueData) - .filter(DialogueData.dialogue_key == dialogue_key) - .filter(DialogueData.client_id == client_id) - ).one_or_none() - if row is None: - # Not already present: insert - row = DialogueData( - client_id=client_id, - dialogue_key=dialogue_key, - created=now, - modified=now, - data=data, - ) - session.add(row) - Query.store_query_data( - client_id, - DSM.ACTIVE_DIALOGUE_KEY, - {"active_dialogue": dialogue_key}, - update_in_place=True, - ) - else: - print("In else with dialogue key: {0}".format(dialogue_key)) - if data.get(dialogue_key) is None: - # Data is empty, delete the row - session.delete(row) - # Update the active dialogue in the query data - Query.update_active_dialogue_data( - client_id=client_id, old_dialogue_key=dialogue_key - ) - return True - Query.store_query_data( - client_id=client_id, - key=DSM.ACTIVE_DIALOGUE_KEY, - data={"active_dialogue": dialogue_key}, - update_in_place=True, - ) - if update_in_place: - stored_data = deepcopy(row.data) - data = _merge_two_dicts(stored_data, data) - # Already present: update - row.data = data # type: ignore - row.modified = now # type: ignore - # The session is auto-committed upon exit from the context manager - return True - except Exception as e: - logging.error("Error storing dialogue data in db: {0}".format(e)) - return False - - @staticmethod - def update_active_dialogue_data( - client_id: str, - old_dialogue_key: str, - ) -> bool: - """ - Update the active dialogue data in the query data. - """ - if not client_id: - return False - try: - with SessionContext(commit=True) as session: - rows = ( - session.query(DialogueData) - .filter(DialogueData.client_id == client_id) - .filter(DialogueData.dialogue_key != old_dialogue_key) - ).all() - latest_row = None - for row in rows: - if latest_row is None or row.modified > latest_row.modified: - latest_row = row - active_row = ( - session.query(QueryData) - .filter(QueryData.key == DSM.ACTIVE_DIALOGUE_KEY) - .filter(QueryData.client_id == client_id) - ).one_or_none() - - if active_row is None: - # Not already present: insert if there is a row in the dialogue data table - if latest_row: - active_row = QueryData( - client_id=client_id, - key=DSM.ACTIVE_DIALOGUE_KEY, - created=datetime.utcnow(), - modified=datetime.utcnow(), - data={"active_dialogue": latest_row.dialogue_key}, - ) - session.add(active_row) - elif latest_row is None: - # No row in the dialogue data table: delete the active dialogue data if it exists - if active_row: - session.delete(active_row) - else: - # Update the active dialogue in the query data - active_row.data = {"active_dialogue": latest_row.dialogue_key} - active_row.modified = datetime.utcnow() - # The session is auto-committed upon exit from the context manager - return True - except Exception as e: - logging.error("Error storing dialogue data in db: {0}".format(e)) - return False + return self._dsm.active_dialogue == dialogue_name @staticmethod def delete_iot_data(client_id: str, iot_group: str, iot_name: str) -> bool: @@ -1574,7 +1378,7 @@ def execute(self) -> ResponseDict: result["error"] = err result["valid"] = False return result - if not self.execute_from_dialogue() and not self.execute_from_tree(): + if not self.execute_from_tree(): # This is a query, but its execution failed for some reason: # return the error # if Settings.DEBUG: From 382535310143f4135987a958022a2839ec4c8b12 Mon Sep 17 00:00:00 2001 From: Logi E Date: Sun, 25 Sep 2022 12:31:21 +0000 Subject: [PATCH 363/371] Removed comment --- query.py | 77 -------------------------------------------------------- 1 file changed, 77 deletions(-) diff --git a/query.py b/query.py index 028fee41..a20d0ad0 100755 --- a/query.py +++ b/query.py @@ -822,83 +822,6 @@ def execute_from_plain_text(self) -> bool: handle_plain_text(self) for handle_plain_text in self._text_processors ) - # def execute_from_dialogue(self) -> bool: - # """Execute the query or queries contained in the previously parsed tree; - # return True if successful""" - # if self._tree is None: - # self.set_error("E_QUERY_NOT_PARSED") - # return False - # # Try each dialogue processor in turn, in priority order (highest priority first) - # for processor in self._dialogue_processors: - # self._error = None - # self._qtype = None - # # Process the dialogue, which has only one sentence, but may - # # have multiple matching query nonterminals - # # (children of Query in the grammar) - # try: - # # Note that passing query=self here means that the - # # "query" field of the TreeStateDict is populated, - # # turning it into a QueryStateDict. - # processor_query_types: Set[str] = getattr( - # processor, "QUERY_NONTERMINALS", set() - # ) - # if self._tree.query_nonterminals.isdisjoint(processor_query_types): - # # But this processor is not interested in any of the nonterminals - # # in this query's parse forest: don't waste more cycles on it - # continue - # dialogue_name: Optional[str] = getattr(processor, "DIALOGUE_NAME", None) - # if dialogue_name: - # # This processor uses dialogue functionality - - # self._dialogue_data = ( - # cast(DialogueDataDict, self.dialogue_data(dialogue_name)) - # or dict() - # ) - # self._dsm = DSM(self._dialogue_data) - # # Query matches this dialogue processor, start DialogueStateManager - # self._dsm.load_dialogue(dialogue_name) - # print("DIALOGUE LOADED: ", dialogue_name) - # print("DSM DATA: ", self._dialogue_data) - # if self.dsm.timed_out: - # # TODO: If the DialogueStateManager timed out, - # # set active_dialogue to the last active dialogue - # # - # timed_out_ans = self.dsm.get_resource("Final").prompts[ - # "timed_out" - # ] - # ans = (dict(answer=timed_out_ans), timed_out_ans, timed_out_ans) - # self.set_answer(*ans) - # self.update_dialogue_data() - # return True - # with self._tree.context(self._session, processor, query=self) as state: - # for query_tree in self._tree._query_trees: - # # Is the processor interested in the root nonterminal - # # of this query tree? - # if query_tree.string_self() in processor_query_types: - # # Yes: hand the query tree over to the processor - # try: - # self._tree.process_sentence(state, query_tree) - # except ResourceNotFoundError as e: - # print("Resource not found: ", e) - # pass - # print( - # "DO WE HAVE AN ANSWER?", self.has_answer(), self._error - # ) - # if self.has_answer(): - # print("HAS ANSWER") - # # The processor successfully answered the query: We're done - # # Also save any changes to dialogue data, if needed - # self.update_dialogue_data() - # print("DIALOGUE DATA UPDATED") - # return True - # except Exception as e: - # logging.error( - # f"Exception in execute_from_dialogue('{processor.__name__}') " - # f"for query '{self._query}': {repr(e)}" - # ) - # # No processor was able to answer the query - # return False - def execute_from_tree(self) -> bool: """Execute the query or queries contained in the previously parsed tree; return True if successful""" From c95c7f28793133a9167057778b6be19632ec87e0 Mon Sep 17 00:00:00 2001 From: Logi E Date: Wed, 28 Sep 2022 10:45:59 +0000 Subject: [PATCH 364/371] Refactoring Hue and Sonos functionality Some refactoring to iot_hue and iot_speaker modules, simplified module grammars. Improvements to grammar generator (now works around format strings). Hotfix for small bug in dialogue.py, raised in query.py, will be fixed better in coming commits to dialogue functionality. --- queries/extras/dialogue.py | 47 +- queries/grammars/iot_hue.grammar | 501 +++++++++--------- ...t_speakers.grammar => iot_speaker.grammar} | 50 +- queries/iot_hue.py | 225 +++----- queries/{iot_speakers.py => iot_speaker.py} | 9 +- query.py | 13 +- tools/grammar_gen.py | 42 +- 7 files changed, 420 insertions(+), 467 deletions(-) rename queries/grammars/{iot_speakers.grammar => iot_speaker.grammar} (94%) rename queries/{iot_speakers.py => iot_speaker.py} (99%) diff --git a/queries/extras/dialogue.py b/queries/extras/dialogue.py index a8dd56dd..174446ab 100644 --- a/queries/extras/dialogue.py +++ b/queries/extras/dialogue.py @@ -263,7 +263,7 @@ def __init__(self, client_id: Optional[str], db_session: Session) -> None: """Initialize DSM instance and fetch tthe active dialogues for a client.""" self._client_id = client_id self._db_session = db_session # Database session of parent Query class - # Fetch active dialogues for this client + # Fetch active dialogues for this client (empty list if no client ID provided) self._active_dialogues: ActiveDialogueList = self._get_active_dialogues() def get_next_active_resource(self, dialogue_name: str) -> str: @@ -284,7 +284,6 @@ def prepare_dialogue(self, dialogue_name: str): Prepare DSM instance for a specific dialogue. Fetches saved state from database if dialogue is active. """ - print("PREPARING DIALOGUE!") self._dialogue_name: str = dialogue_name # Dict mapping resource name to resource instance self._resources: Dict[str, res.Resource] = {} @@ -364,7 +363,7 @@ def _load_saved_state(self) -> None: """ saved_row = self._dialogue_data() assert saved_row is not None - self._timed_out: bool = datetime.datetime.now() > saved_row["expires_at"] + self._timed_out = datetime.datetime.now() > saved_row["expires_at"] if self._timed_out: # TODO: Do something when a dialogue times out logging.warning("THIS DIALOGUE IS TIMED OUT!!!") @@ -781,24 +780,24 @@ def serialize_data(self) -> Dict[str, Optional[str]]: def _get_active_dialogues(self) -> ActiveDialogueList: """Get list of active dialogues from database for current client.""" - assert self._client_id, "_get_active_dialogues() called without client ID!" - active: ActiveDialogueList = [] - with SessionContext(session=self._db_session, read_only=True) as session: - try: - row: Optional[DB_QueryData] = ( - session.query(DB_QueryData) - .filter(DB_QueryData.client_id == self._client_id) # type: ignore - .filter(DB_QueryData.key == _ACTIVE_DIALOGUE_KEY) - ).one_or_none() - if row is not None: - active = cast(ActiveDialogueList, row.data) - except Exception as e: - logging.error( - "Error fetching client '{0}' query data for key '{1}' from db: {2}".format( - self._client_id, _ACTIVE_DIALOGUE_KEY, e + + if self._client_id: + with SessionContext(session=self._db_session, read_only=True) as session: + try: + row: Optional[DB_QueryData] = ( + session.query(DB_QueryData) + .filter(DB_QueryData.client_id == self._client_id) # type: ignore + .filter(DB_QueryData.key == _ACTIVE_DIALOGUE_KEY) + ).one_or_none() + if row is not None: + active = cast(ActiveDialogueList, row.data) + except Exception as e: + logging.error( + "Error fetching client '{0}' query data for key '{1}' from db: {2}".format( + self._client_id, _ACTIVE_DIALOGUE_KEY, e + ) ) - ) return active def _dialogue_data(self) -> Optional[DialogueDataRow]: @@ -819,8 +818,8 @@ def _dialogue_data(self) -> Optional[DialogueDataRow]: ).one_or_none() if row: return { - "data": row.data, - "expires_at": row.expires_at, + "data": cast(DialogueSerialized, row.data), + "expires_at": cast(datetime.datetime, row.expires_at), } except Exception as e: logging.error( @@ -835,9 +834,9 @@ def update_dialogue_data(self) -> None: Save current state of dialogue to dialoguedata table in database, along with updating list of active dialogues in querydata table. """ - assert ( - self._client_id and self._dialogue_name - ), "_dialogue_data() called without client ID or dialogue name!" + if not self._client_id or not self._dialogue_name: + # Need both client ID and dialogue name to save any state + return now = datetime.datetime.now() expires_at = now + datetime.timedelta(seconds=self._expiration_time) diff --git a/queries/grammars/iot_hue.grammar b/queries/grammars/iot_hue.grammar index b28bd632..53e4f92e 100644 --- a/queries/grammars/iot_hue.grammar +++ b/queries/grammars/iot_hue.grammar @@ -1,180 +1,189 @@ +/þf = þf /þgf = þgf /ef = ef -Query → - QIoT '?'? - -QIoT → - QIoTQuery - -QIoTQuery -> - QIoTMakeVerb? QIoTMakeRest - | QIoTSetVerb QIoTSetRest - | QIoTChangeVerb QIoTChangeRest - | QIoTLetVerb QIoTLetRest - | QIoTTurnOnVerb QIoTTurnOnRest - | QIoTTurnOffVerb QIoTTurnOffRest - | QIoTIncreaseOrDecreaseVerb QIoTIncreaseOrDecreaseRest - -QIoTMakeVerb -> - 'gera:so'_bh - -QIoTSetVerb -> - 'setja:so'_bh - | 'stilla:so'_bh - -QIoTChangeVerb -> - 'breyta:so'_bh - -QIoTLetVerb -> - 'láta:so'_bh - -QIoTTurnOnVerb -> - 'kveikja:so'_bh - -QIoTTurnOffVerb -> - 'slökkva:so'_bh - -QIoTIncreaseOrDecreaseVerb -> - QIoTIncreaseVerb - | QIoTDecreaseVerb - -QIoTIncreaseVerb -> - 'hækka:so'_bh - | 'auka:so'_bh - -QIoTDecreaseVerb -> - 'lækka:so'_bh - | 'minnka:so'_bh - -QIoTMakeRest -> - QIoTSubject/þf QIoTHvar? QIoTHvernigMake - | QIoTSubject/þf QIoTHvernigMake QIoTHvar? - | QIoTHvar? QIoTSubject/þf QIoTHvernigMake - | QIoTHvar? QIoTHvernigMake QIoTSubject/þf - | QIoTHvernigMake QIoTSubject/þf QIoTHvar? - | QIoTHvernigMake QIoTHvar? QIoTSubject/þf +Query → QHue +QHue → QHueQuery '?'? +QHueQuery → + QHueTurnOnLights + | QHueTurnOffLights + | QHueIncreaseBrightness + | QHueDecreaseBrightness + | QHueChangeColor + | QHueChangeScene + +# Some verbs +QHueKveiktu → "kveiktu" | "kveikja" +QHueSlökktu → "slökktu" | "slökkva" +QHueGerðu → "gerðu" | "gera" +QHueStilltuSettu → "stilltu" | "settu" | "stilla" | "setja" +QHueBreyttuSkiptu → "breyttu" | "skiptu" | "breyta" | "skipta" +QHueLáttu → "láttu" | "láta" +QHueHækkaðuAuktu → "hækkaðu" | "auktu" | "hækka" | "auka" +QHueLækkaðuMinnkaðu → "lækkaðu" | "minnkaðu" | "lækka" | "minnka" + +# Commands for turning on lights +QHueTurnOnLights → QHueKveiktu QHueKveiktuX + +# Commands for turning off lights +QHueTurnOffLights → QHueSlökktu QHueSlökktuX + +QHueMeiri → "meiri" | "meira" + +# Commands for increasing light brightness +QHueIncreaseBrightness → + QHueHækkaðuAuktu QHueHækkaðuX + | QHueGerðu QHueMeiri QHueBrightnessWord/þf QHueHvar + | QHueGerðu QHueBrightnessWord/þf QHueMeiri QHueHvar? + | QHueGerðu QHueBrightnessWord/þf QHueHvar QHueMeiri + +QHueMinni → "minni" | "minna" + +# Commands for decreasing light brightness +QHueDecreaseBrightness → + QHueLækkaðuMinnkaðu QHueLækkaðuX + | QHueGerðu QHueBrightnessWord/þf QHueMinni QHueHvar? + | QHueGerðu QHueBrightnessWord/þf QHueHvar QHueMinni + | QHueGerðu QHueMinni QHueBrightnessWord/þf QHueHvar + +QHueÁLitinn → "á" "litinn" | "á" "lit" + +# Commands for changing the current color +QHueChangeColor → + QHueGerðu QHueLight/þf QHueHvar? QHueColorName/þf QHueÁLitinn? + | QHueGerðu QHueLight/þf QHueColorName/þf QHueÁLitinn? QHueHvar + | QHueLáttu QHueLight/þf QHueHvar? QHueVeraVerða QHueColorName/þf QHueÁLitinn? + | QHueLáttu QHueLight/þf QHueVeraVerða QHueColorName/þf QHueÁLitinn? QHueHvar + | QHueStilltuSettu "á" QHueNewColor/þf QHueHvar + | QHueBreyttuSkiptu "yfir"? "í" QHueNewColor/þf QHueHvar + | QHueBreyttuSkiptu "litnum" "á" QHueLight/þgf QHueHvar? "í" "litinn"? QHueColorName/þf + +# Commands for changing the current scene +QHueChangeScene → + QHueKveiktu "á" QHueNewScene/þgf QHueHvar? + | QHueStilltuSettu "á" QHueNewScene/þf + | QHueBreyttuSkiptu "yfir"? "í" QHueNewScene/þf + +QHueKveiktuX → + QHueLight/þf? QHueHvar # ... ljósið í eldhúsinu + | "á" QHueLight/þgf QHueHvar? # ... á lampanum í stofunni + +QHueSlökktuX → + QHueLight/þf? QHueHvar # ... ljósið í eldhúsinu + | "á" QHueLight/þgf QHueHvar? # ... á lampanum í stofunni + +QHueHækkaðuX → + QHueLjósTegund/þf QHueHvar? + | QHueBrightnessSubject/þf QHueHvar? + +QHueLækkaðuX → + QHueLight/þf QHueHvar? + | QHueBrightnessSubject/þf QHueHvar? + +QHueGerðuX → + QHueSubject/þf QHueHvar? QHueHvernigMake + | QHueSubject/þf QHueHvernigMake QHueHvar? + | QHueHvar? QHueSubject/þf QHueHvernigMake + | QHueHvar? QHueHvernigMake QHueSubject/þf + | QHueHvernigMake QHueSubject/þf QHueHvar? + | QHueHvernigMake QHueHvar? QHueSubject/þf # TODO: Add support for "stilltu rauðan lit á ljósið í eldhúsinu" -QIoTSetRest -> - QIoTSubject/þf QIoTHvar? QIoTHvernigSet - | QIoTSubject/þf QIoTHvernigSet QIoTHvar? - | QIoTHvar? QIoTSubject/þf QIoTHvernigSet - | QIoTHvar? QIoTHvernigSet QIoTSubject/þf - | QIoTHvernigSet QIoTSubject/þf QIoTHvar? - | QIoTHvernigSet QIoTHvar? QIoTSubject/þf - -QIoTChangeRest -> - QIoTSubjectOne/þgf QIoTHvar? QIoTHvernigChange - | QIoTSubjectOne/þgf QIoTHvernigChange QIoTHvar? - | QIoTHvar? QIoTSubjectOne/þgf QIoTHvernigChange - | QIoTHvar? QIoTHvernigChange QIoTSubjectOne/þgf - | QIoTHvernigChange QIoTSubjectOne/þgf QIoTHvar? - | QIoTHvernigChange QIoTHvar? QIoTSubjectOne/þgf - -QIoTLetRest -> - QIoTSubject/þf QIoTHvar? QIoTHvernigLet - | QIoTSubject/þf QIoTHvernigLet QIoTHvar? - | QIoTHvar? QIoTSubject/þf QIoTHvernigLet - # | QIoTHvar? QIoTHvernigLet QIoTSubject/þf - # | QIoTHvernigLet QIoTSubject/þf QIoTHvar? - # | QIoTHvernigLet QIoTHvar? QIoTSubject/þf - -QIoTTurnOnRest -> - QIoTTurnOnLightsRest - | QIoTAHverju QIoTHvar? - | QIoTHvar? QIoTAHverju - -QIoTTurnOnLightsRest -> - QIoTLightSubject/þf QIoTHvar? - | QIoTHvar QIoTLightSubject/þf? - -# Would be good to add "slökktu á rauða litnum" functionality -QIoTTurnOffRest -> - QIoTTurnOffLightsRest - -QIoTTurnOffLightsRest -> - QIoTLightSubject/þf QIoTHvar? - | QIoTHvar QIoTLightSubject/þf? - -# TODO: Make the subject categorization cleaner -QIoTIncreaseOrDecreaseRest -> - QIoTLightSubject/þf QIoTHvar? - | QIoTBrightnessSubject/þf QIoTHvar? - -QIoTSubject/fall -> - QIoTSubjectOne/fall - | QIoTSubjectTwo/fall +QHueSettuX → + QHueSubject/þf QHueHvar? QHueHvernigSet + | QHueSubject/þf QHueHvernigSet QHueHvar? + | QHueHvar? QHueSubject/þf QHueHvernigSet + | QHueHvar? QHueHvernigSet QHueSubject/þf + | QHueHvernigSet QHueSubject/þf QHueHvar? + | QHueHvernigSet QHueHvar? QHueSubject/þf + +QHueBreyttuX → + QHueSubjectOne/þgf QHueHvar? QHueHvernigChange + | QHueSubjectOne/þgf QHueHvernigChange QHueHvar? + | QHueHvar? QHueSubjectOne/þgf QHueHvernigChange + | QHueHvar? QHueHvernigChange QHueSubjectOne/þgf + | QHueHvernigChange QHueSubjectOne/þgf QHueHvar? + | QHueHvernigChange QHueHvar? QHueSubjectOne/þgf + +QHueLáttuX → + QHueSubject/þf QHueHvar? QHueHvernigLet + | QHueSubject/þf QHueHvernigLet QHueHvar? + | QHueHvar? QHueSubject/þf QHueHvernigLet + # | QHueHvar? QHueHvernigLet QHueSubject/þf + # | QHueHvernigLet QHueSubject/þf QHueHvar? + # | QHueHvernigLet QHueHvar? QHueSubject/þf + +QHueSubject/fall → + QHueSubjectOne/fall + | QHueSubjectTwo/fall # TODO: Decide whether LightSubject/þgf should be accepted -QIoTSubjectOne/fall -> - QIoTLightSubject/fall - | QIoTColorSubject/fall - | QIoTBrightnessSubject/fall - | QIoTSceneSubject/fall +QHueSubjectOne/fall → + QHueLight/fall + | QHueColorSubject/fall + | QHueBrightnessSubject/fall + | QHueSceneSubject/fall -QIoTSubjectTwo/fall -> - QIoTGroupNameSubject/fall # á bara að styðja "gerðu eldhúsið rautt", "gerðu eldhúsið rómó" "gerðu eldhúsið bjartara", t.d. +QHueSubjectTwo/fall → + QHueGroupNameSubject/fall # á bara að styðja "gerðu eldhúsið rautt", "gerðu eldhúsið rómó" "gerðu eldhúsið bjartara", t.d. -QIoTHvar -> - QIoTLocationPreposition QIoTGroupName/þgf +QHueHvar → + QHueLocationPreposition QHueGroupName/þgf -QIoTHvernigMake -> - QIoTAnnadAndlag # gerðu litinn rauðan í eldhúsinu EÐA gerðu birtuna meiri í eldhúsinu - | QIoTAdHverju # gerðu litinn að rauðum í eldhúsinu - | QIoTThannigAd +QHueHvernigMake → + QHueAnnaðAndlag # gerðu litinn rauðan í eldhúsinu EÐA gerðu birtuna meiri í eldhúsinu + | QHueAðHverju # gerðu litinn að rauðum í eldhúsinu + | QHueÞannigAð -QIoTHvernigSet -> - QIoTAHvad - | QIoTThannigAd +QHueHvernigSet → + QHueÁHvað + | QHueÞannigAð -QIoTHvernigChange -> - QIoTIHvad - | QIoTThannigAd +QHueHvernigChange → + QHueÍHvað + | QHueÞannigAð -QIoTHvernigLet -> - QIoTBecome QIoTSomethingOrSomehow - | QIoTBe QIoTSomehow +QHueHvernigLet → + QHueVerða QHueSomethingOrSomehow + | QHueVera QHueSomehow -QIoTThannigAd -> - "þannig" "að"? pfn_nf QIoTBeOrBecomeSubjunctive QIoTAnnadAndlag +QHueÞannigAð → + "þannig" "að"? pfn_nf QHueBeOrBecomeSubjunctive QHueAnnaðAndlag # I think these verbs only appear in these forms. # In which case these terminals should be deleted and a direct reference should be made in the relevant non-terminals. -QIoTBe -> +QHueVeraVerða → + QHueVera | QHueVerða + +QHueVera → "vera" -QIoTBecome -> +QHueVerða → "verða" -QIoTBeOrBecomeSubjunctive -> +QHueBeOrBecomeSubjunctive → "verði" | "sé" -QIoTLightSubject/fall -> - QIoTLight/fall +QHueColorSubject/fall → + QHueColorWord/fall QHueLight/ef? + | QHueColorWord/fall "á" QHueLight/þgf -QIoTColorSubject/fall -> - QIoTColorWord/fall QIoTLight/ef? - | QIoTColorWord/fall "á" QIoTLight/þgf +QHueBrightnessSubject/fall → + QHueBrightnessWord/fall QHueLight/ef? + | QHueBrightnessWord/fall "á" QHueLight/þgf -QIoTBrightnessSubject/fall -> - QIoTBrightnessWord/fall QIoTLight/ef? - | QIoTBrightnessWord/fall "á" QIoTLight/þgf +QHueSceneSubject/fall → QHueSceneWord/fall -QIoTSceneSubject/fall -> - QIoTSceneWord/fall +QHueGroupNameSubject/fall → QHueGroupName/fall -QIoTGroupNameSubject/fall -> - QIoTGroupName/fall - -QIoTLocationPreposition -> - QIoTLocationPrepositionFirstPart? QIoTLocationPrepositionSecondPart +QHueLocationPreposition → + QHueLocationPrepositionFirstPart? QHueLocationPrepositionSecondPart # The latter proverbs are grammatically incorrect, but common errors, both in speech and transcription. # The list provided is taken from StefnuAtv in Greynir.grammar. That includes "aftur:ao", which is not applicable here. -QIoTLocationPrepositionFirstPart -> +QHueLocationPrepositionFirstPart → StaðarAtv | "fram:ao" | "inn:ao" @@ -182,168 +191,168 @@ QIoTLocationPrepositionFirstPart -> | "upp:ao" | "út:ao" -QIoTLocationPrepositionSecondPart -> +QHueLocationPrepositionSecondPart → "á" | "í" -QIoTGroupName/fall -> - # QIoTLightsBanwords/fall - no/fall - | sérnafn/fall +QHueGroupName/fall → + no/fall #| QHueBanwords/fall + # | sérnafn/fall -QIoTLightName/fall -> - # QIoTLightsBanwords/fall - no/fall - | sérnafn/fall +QHueLightName/fall → + no/fall #| QHueBanwords/fall + # | sérnafn/fall -QIoTColorName -> +QHueColorName/fall → {color_names} -$score(+100) QIoTColorName - -QIoTSceneName -> - # QIoTLightsBanwords/fall - no - | lo - | sérnafn/fall +QHueSceneName → + no | lo #| QHueBanwords/fall + # | sérnafn/fall -QIoTAnnadAndlag -> - QIoTNewSetting/nf - | QIoTSpyrjaHuldu/nf +QHueAnnaðAndlag → + QHueNewSetting/nf + | QHueSpyrjaHuldu/nf -QIoTAdHverju -> - "að" QIoTNewSetting/þgf +QHueAðHverju → + "að" QHueNewSetting/þgf -QIoTAHvad -> - "á" QIoTNewSetting/þf +QHueÁHvað → + "á" QHueNewSetting/þf -QIoTIHvad -> - "í" QIoTNewSetting/þf +QHueÍHvað → + "í" QHueNewSetting/þf -QIoTAHverju -> - "á" QIoTLight/þgf - | "á" QIoTNewSetting/þgf +QHueÁHverju → + "á" QHueLight/þgf + # | "á" QHueNewSetting/þgf -QIoTSomethingOrSomehow -> - QIoTAnnadAndlag - | QIoTAdHverju +QHueSomethingOrSomehow → + QHueAnnaðAndlag + | QHueAðHverju -QIoTSomehow -> - QIoTAnnadAndlag - | QIoTThannigAd +QHueSomehow → + QHueAnnaðAndlag + | QHueÞannigAð -QIoTLight/fall -> - QIoTLightName/fall - | QIoTLightWord/fall +QHueLight/fall → + QHueLjósTegund/fall + | QHueLightName/fall -# Should 'birta' be included -QIoTLightWord/fall -> +QHueLjósTegund/fall → 'ljós'/fall - | 'lýsing'/fall - | 'birta'/fall - | 'Birta'/fall - -QIoTColorWord/fall -> + | 'lampi'/fall + # | 'útiljós'/fall + # | 'leslampi'/fall + # | 'borðlampi'/fall + # | 'gólflampi'/fall + # | 'lýsing'/fall + # | 'birta'/fall + # | 'Birta'/fall + +QHueColorWord/fall → 'litur'/fall | 'litblær'/fall | 'blær'/fall -QIoTBrightnessWords/fall -> +QHueBrightnessWords/fall → 'bjartur'/fall - | QIoTBrightnessWord/fall + | QHueBrightnessWord/fall -QIoTBrightnessWord/fall -> - 'birta'/fall +QHueBrightnessWord/fall → + 'birta:kvk'/fall | 'Birta'/fall | 'birtustig'/fall -QIoTSceneWord/fall -> +QHueSceneWord/fall → 'sena'/fall | 'stemning'/fall | 'stemming'/fall | 'stemmning'/fall - | 'sina'/fall - | 'Sena'/fall - | "Sena" + # | 'sina'/fall + # | 'Sena'/fall + # | "Sena" # Need to ask Hulda how this works. -QIoTSpyrjaHuldu/fall -> - # QIoTHuldaColor/fall - QIoTHuldaBrightness/fall - # | QIoTHuldaScene/fall - | QIoTHuldaTemp/fall +QHueSpyrjaHuldu/fall → + # QHueHuldaColor/fall + QHueHuldaBrightness/fall + # | QHueHuldaScene/fall + | QHueHuldaTemp/fall # Do I need a "new light state" non-terminal? -QIoTNewSetting/fall -> - QIoTNewColor/fall - | QIoTNewBrightness/fall - | QIoTNewScene/fall - -# Missing "meira dimmt" -QIoTHuldaBrightness/fall -> - QIoTMoreBrighterOrHigher/fall QIoTBrightnessWords/fall? - | QIoTLessDarkerOrLower/fall QIoTBrightnessWords/fall? - -QIoTHuldaTemp/fall -> - QIoTWarmer/fall - | QIoTCooler/fall - -#Unsure about whether to include /fall after QIoTColorName -QIoTNewColor/fall -> - QIoTColorWord/fall QIoTColorName - | QIoTColorName QIoTColorWord/fall? - -QIoTNewBrightness/fall -> - 'sá'/fall? QIoTBrightestOrDarkest/fall - | QIoTBrightestOrDarkest/fall QIoTBrightnessOrSettingWord/fall - -QIoTNewScene/fall -> - QIoTSceneWord/fall QIoTSceneName - | QIoTSceneName QIoTSceneWord/fall? - -QIoTMoreBrighterOrHigher/fall -> +QHueNewSetting/fall → + QHueNewColor/fall + | QHueNewBrightness/fall + | QHueNewScene/fall + +# TODO: Missing "meira dimmt" +QHueHuldaBrightness/fall → + QHueMoreBrighterOrHigher/fall QHueBrightnessWords/fall? + | QHueLessDarkerOrLower/fall QHueBrightnessWords/fall? + +QHueHuldaTemp/fall → + QHueWarmer/fall + | QHueCooler/fall + +# TODO: Unsure about whether to include /fall after QHueColorName +QHueNewColor/fall → + QHueColorWord/fall QHueColorName/fall + | QHueColorName/fall QHueColorWord/fall? + +QHueNewBrightness/fall → + 'sá'/fall? QHueBrightestOrDarkest/fall + | QHueBrightestOrDarkest/fall QHueBrightnessOrSettingWord/fall + +QHueNewScene/fall → + QHueSceneWord/fall QHueSceneName + | QHueSceneName QHueSceneWord/fall? + +QHueMoreBrighterOrHigher/fall → 'mikill:lo'_mst/fall | 'bjartur:lo'_mst/fall | 'ljós:lo'_mst/fall | 'hár:lo'_mst/fall -QIoTLessDarkerOrLower/fall -> +QHueLessDarkerOrLower/fall → 'lítill:lo'_mst/fall | 'dökkur:lo'_mst/fall | 'dimmur:lo'_mst/fall | 'lágur:lo'_mst/fall -QIoTWarmer/fall -> +QHueWarmer/fall → 'hlýr:lo'_mst/fall | 'heitur:lo'_mst/fall | "hlýri" - -QIoTCooler/fall -> +QHueCooler/fall → 'kaldur:lo'_mst/fall -QIoTBrightestOrDarkest/fall -> - QIoTBrightest/fall - | QIoTDarkest/fall +QHueBrightestOrDarkest/fall → + QHueBrightest/fall + | QHueDarkest/fall -QIoTBrightest/fall -> +QHueBrightest/fall → 'bjartur:lo'_evb | 'bjartur:lo'_esb | 'ljós:lo'_evb | 'ljós:lo'_esb -QIoTDarkest/fall -> +QHueDarkest/fall → 'dimmur:lo'_evb | 'dimmur:lo'_esb | 'dökkur:lo'_evb | 'dökkur:lo'_esb -QIoTBrightnessOrSettingWord/fall -> - QIoTBrightnessWord/fall - | QIoTSettingWord/fall +QHueBrightnessOrSettingWord/fall → + QHueBrightnessWord/fall + | QHueStilling/fall -QIoTSettingWord/fall -> +QHueStilling/fall → 'stilling'/fall # Catching hotwords from iot_speakers -# QIoTLightsBanwords/fall -> -# QIoTSpeakerHotwords/fall \ No newline at end of file +# QHueBanwords/fall → +# QIoTSpeakerHotwords/fall + +# $tag(keep) QHueBanwords/fall +# $tag(module_banwords) QHueBanwords/fall diff --git a/queries/grammars/iot_speakers.grammar b/queries/grammars/iot_speaker.grammar similarity index 94% rename from queries/grammars/iot_speakers.grammar rename to queries/grammars/iot_speaker.grammar index 8ae4643f..67b402c0 100644 --- a/queries/grammars/iot_speakers.grammar +++ b/queries/grammars/iot_speaker.grammar @@ -23,19 +23,10 @@ QIoTSpeakerQuery → | QIoTSpeakerSkipVerb | QIoTSpeakerNewSetting/fall -QIoTSpeakerMakeVerb → - 'gera:so'_bh - -QIoTSpeakerSetVerb → - 'setja:so'_bh - | 'stilla:so'_bh - | 'láta:so'_bh - -QIoTSpeakerChangeVerb → - 'breyta:so'_bh - -QIoTSpeakerLetVerb → - 'láta:so'_bh +QIoTSpeakerMakeVerb → "gerðu" +QIoTSpeakerSetVerb → "settu" | "stilltu" | "láttu" +QIoTSpeakerChangeVerb → "breyttu" +QIoTSpeakerLetVerb → "láttu" QIoTSpeakerTurnOnOrOffVerb -> QIoTSpeakerTurnOnVerb @@ -45,36 +36,29 @@ QIoTSpeakerTurnOnVerb → 'kveikja:so'_bh | "kveiktu" -QIoTSpeakerTurnOffVerb → - 'slökkva:so'_bh +QIoTSpeakerTurnOffVerb → 'slökkva:so'_bh QIoTSpeakerPlayOrPauseVerb → QIoTSpeakerPlayVerb | QIoTSpeakerPauseVerb -QIoTSpeakerPlayVerb → - 'spila:so'_bh +QIoTSpeakerPlayVerb → 'spila:so'_bh # "pása", as a verb, is not recognized by Árnastofnun's database, but is relatively common in casual speech. QIoTSpeakerPauseVerb → - 'stöðva:so'_bh - | 'stoppa:so'_bh + "stöðvaðu" + | "stoppaðu" | "pásaðu" QIoTSpeakerIncreaseOrDecreaseVerb → QIoTSpeakerIncreaseVerb | QIoTSpeakerDecreaseVerb -QIoTSpeakerIncreaseVerb → - 'hækka:so'_bh - | 'auka:so'_bh +QIoTSpeakerIncreaseVerb → "hækkaðu" | "auktu" -QIoTSpeakerDecreaseVerb → - 'lækka:so'_bh - | 'minnka:so'_bh +QIoTSpeakerDecreaseVerb → "lækkaðu" | "minnkaðu" -QIoTSpeakerSkipVerb -> - 'skippa:so'_bh +QIoTSpeakerSkipVerb -> "skippaðu" QIoTSpeakerMakeRest → QIoTSpeakerMusicOrSoundPhrase/þf QIoTSpeakerComparative/nf QIoTSpeakerHvar @@ -263,11 +247,9 @@ QIoTSpeakerBeOrBecome → QIoTSpeakerBe | QIoTSpeakerBecome -QIoTSpeakerBe → - 'vera:so'_nh +QIoTSpeakerBe → "vera" -QIoTSpeakerBecome → - 'verða:so'_nh +QIoTSpeakerBecome → "verða" QIoTSpeakerRadioStationName → QIoTSpeakerBylgjan @@ -420,11 +402,9 @@ QIoTSpeakerIslenskaBylgjan → | "íslensku" "bylgjunni" | "íslensku" "bylgjunnar" -QIoTSpeakerApparatid → - 'apparat'_gr +QIoTSpeakerApparatid → "apparatið" -QIoTSpeakerFmExtra → - "fm" "extra" +QIoTSpeakerFmExtra → "fm" "extra" QIoTSpeakerUtvarpSudurland → "útvarp"? "suðurland" diff --git a/queries/iot_hue.py b/queries/iot_hue.py index 80f4805b..2fe40d44 100755 --- a/queries/iot_hue.py +++ b/queries/iot_hue.py @@ -35,7 +35,7 @@ # TODO: Add functionality for robot-like commands "ljós í eldhúsinu", "rautt í eldhúsinu" # TODO: Mistakes 'gerðu ljósið kaldara' for the scene 'köld' -from typing import Dict, List, Optional, cast, FrozenSet +from typing import Any, Callable, Dict, List, Optional, cast, FrozenSet from typing_extensions import TypedDict import logging @@ -44,29 +44,37 @@ from pathlib import Path from query import Query, QueryStateDict -from queries import gen_answer, read_jsfile, read_grammar_file +from queries import read_jsfile, read_grammar_file from tree import ParamList, Result, Node, TerminalNode -class SmartLights(TypedDict): - selected_light: str - philips_hue: Dict[str, str] +class _Creds(TypedDict): + username: str + ip_address: str -class DeviceData(TypedDict): - smartlights: SmartLights +class _PhilipsHueData(TypedDict): + credentials: _Creds -_IoT_QTYPE = "IoT" +class _IoTDeviceData(TypedDict): + philips_hue: _PhilipsHueData + + +_HUE_QTYPE = "Hue" TOPIC_LEMMAS = [ "ljós", + "lampi", + "útiljós", "kveikja", + "slökkva", "litur", "birta", "hækka", - "stemmning", + "lækka", "sena", + "stemmning", "stemming", "stemning", ] @@ -78,11 +86,11 @@ def help_text(lemma: str) -> str: return "Ég skil þig ef þú segir til dæmis: {0}.".format( random.choice( ( - "Kveiktu á ljósunum inni í eldhúsi", - "Slökktu á leslampanum", "Breyttu lit lýsingarinnar í stofunni í bláan", "Gerðu ljósið í borðstofunni bjartara", "Stilltu á bjartasta niðri í kjallara", + "Kveiktu á ljósunum inni í eldhúsi", + "Slökktu á leslampanum", ) ) ) @@ -92,7 +100,7 @@ def help_text(lemma: str) -> str: HANDLE_TREE = True # The grammar nonterminals this module wants to handle -QUERY_NONTERMINALS = {"QIoT"} +QUERY_NONTERMINALS = {"QHue"} _COLORS: Dict[str, List[float]] = { "appelsínugulur": [0.6195, 0.3624], @@ -108,167 +116,111 @@ def help_text(lemma: str) -> str: # The context-free grammar for the queries recognized by this plug-in module GRAMMAR = read_grammar_file( - "iot_hue", color_names=" | ".join(f"'{color}:lo'" for color in _COLORS.keys()) + "iot_hue", color_names=" | ".join(f"'{color}:lo'/fall" for color in _COLORS.keys()) ) -def QIoTQuery(node: Node, params: ParamList, result: Result) -> None: - result.qtype = _IoT_QTYPE - - -def QIoTColorWord(node: Node, params: ParamList, result: Result) -> None: - result.changing_color = True - - -def QIoTSceneWord(node: Node, params: ParamList, result: Result) -> None: - result.changing_scene = True +# Insert or update hue object kept in result +_upsert_hue_obj: Callable[[Result, Dict[str, Any]], None] = ( + lambda r, d: r.__setattr__("hue_obj", d) + if "hue_obj" not in r + else cast(Dict[str, Any], r["hue_obj"]).update(d) +) -def QIoTBrightnessWord(node: Node, params: ParamList, result: Result) -> None: - result.changing_brightness = True +def QHueQuery(node: Node, params: ParamList, result: Result) -> None: + result.qtype = _HUE_QTYPE -def QIoTTurnOnLightsRest(node: Node, params: ParamList, result: Result) -> None: +def QHueTurnOnLights(node: Node, params: ParamList, result: Result) -> None: result.action = "turn_on" - if "hue_obj" not in result: - result["hue_obj"] = {"on": True} - else: - result["hue_obj"]["on"] = True + _upsert_hue_obj(result, {"on": True}) -def QIoTTurnOffLightsRest(node: Node, params: ParamList, result: Result) -> None: +def QHueTurnOffLights(node: Node, params: ParamList, result: Result) -> None: result.action = "turn_off" - if "hue_obj" not in result: - result["hue_obj"] = {"on": False} - else: - result["hue_obj"]["on"] = False + _upsert_hue_obj(result, {"on": False}) -def QIoTNewColor(node: Node, params: ParamList, result: Result) -> None: +def QHueChangeColor(node: Node, params: ParamList, result: Result) -> None: result.action = "set_color" color_hue = _COLORS.get(result.color_name, None) if color_hue is not None: + _upsert_hue_obj(result, {"on": True, "xy": color_hue}) + + +def QHueChangeScene(node: Node, params: ParamList, result: Result) -> None: + result.action = "set_scene" + scene_name = result.get("scene_name", None) + if scene_name is not None: if "hue_obj" not in result: - result["hue_obj"] = {"on": True, "xy": color_hue} + result["hue_obj"] = {"on": True, "scene": scene_name} else: - result["hue_obj"]["xy"] = color_hue + result["hue_obj"]["scene"] = scene_name result["hue_obj"]["on"] = True -def QIoTMoreBrighterOrHigher(node: Node, params: ParamList, result: Result) -> None: +def QHueIncreaseBrightness(node: Node, params: ParamList, result: Result) -> None: result.action = "increase_brightness" - if "hue_obj" not in result: - result["hue_obj"] = {"on": True, "bri_inc": 64} - else: - result["hue_obj"]["bri_inc"] = 64 - result["hue_obj"]["on"] = True + _upsert_hue_obj(result, {"on": True, "bri_inc": 64}) -def QIoTLessDarkerOrLower(node: Node, params: ParamList, result: Result) -> None: +def QHueDecreaseBrightness(node: Node, params: ParamList, result: Result) -> None: result.action = "decrease_brightness" - if "hue_obj" not in result: - result["hue_obj"] = {"bri_inc": -64} - else: - result["hue_obj"]["bri_inc"] = -64 - - -def QIoTIncreaseVerb(node: Node, params: ParamList, result: Result) -> None: - result.action = "increase_brightness" - if "hue_obj" not in result: - result["hue_obj"] = {"on": True, "bri_inc": 64} - else: - result["hue_obj"]["bri_inc"] = 64 - result["hue_obj"]["on"] = True + _upsert_hue_obj(result, {"bri_inc": -64}) -def QIoTCooler(node: Node, params: ParamList, result: Result) -> None: +def QHueCooler(node: Node, params: ParamList, result: Result) -> None: result.action = "decrease_colortemp" result.changing_temp = True - if "hue_obj" not in result: - result["hue_obj"] = {"ct_inc": -30000} - else: - result["hue_obj"]["ct_inc"] = -30000 + _upsert_hue_obj(result, {"ct_inc": -30000}) -def QIoTWarmer(node: Node, params: ParamList, result: Result) -> None: +def QHueWarmer(node: Node, params: ParamList, result: Result) -> None: result.action = "increase_colortemp" result.changing_temp = True - if "hue_obj" not in result: - result["hue_obj"] = {"ct_inc": 30000} - else: - result["hue_obj"]["ct_inc"] = 30000 - + _upsert_hue_obj(result, {"ct_inc": 30000}) -def QIoTDecreaseVerb(node: Node, params: ParamList, result: Result) -> None: - result.action = "decrease_brightness" - if "hue_obj" not in result: - result["hue_obj"] = {"bri_inc": -64} - else: - result["hue_obj"]["bri_inc"] = -64 - -def QIoTBrightest(node: Node, params: ParamList, result: Result) -> None: +def QHueBrightest(node: Node, params: ParamList, result: Result) -> None: result.action = "increase_brightness" - if "hue_obj" not in result: - result["hue_obj"] = {"bri": 255} - else: - result["hue_obj"]["bri"] = 255 + _upsert_hue_obj(result, {"bri": 255}) -def QIoTDarkest(node: Node, params: ParamList, result: Result) -> None: +def QHueDarkest(node: Node, params: ParamList, result: Result) -> None: result.action = "decrease_brightness" - if "hue_obj" not in result: - result["hue_obj"] = {"bri": 0} - else: - result["hue_obj"]["bri"] = 0 - - -def QIoTNewScene(node: Node, params: ParamList, result: Result) -> None: - result.action = "set_scene" - scene_name = result.get("scene_name", None) - if scene_name is not None: - if "hue_obj" not in result: - result["hue_obj"] = {"on": True, "scene": scene_name} - else: - result["hue_obj"]["scene"] = scene_name - result["hue_obj"]["on"] = True + _upsert_hue_obj(result, {"bri": 0}) -def QIoTColorName(node: Node, params: ParamList, result: Result) -> None: +def QHueColorName(node: Node, params: ParamList, result: Result) -> None: fc = node.first_child(lambda x: True) if fc: result["color_name"] = fc.string_self().strip("'").split(":")[0] -def QIoTSceneName(node: Node, params: ParamList, result: Result) -> None: +def QHueSceneName(node: Node, params: ParamList, result: Result) -> None: result["scene_name"] = result._indefinite result["changing_scene"] = True - print("scene: " + result.get("scene_name", None)) -def QIoTGroupName(node: Node, params: ParamList, result: Result) -> None: +def QHueGroupName(node: Node, params: ParamList, result: Result) -> None: result["group_name"] = result._indefinite -def QIoTLightName(node: Node, params: ParamList, result: Result) -> None: +def QHueLightName(node: Node, params: ParamList, result: Result) -> None: result["light_name"] = result._indefinite -def QIoTSpeakerHotwords(node: Node, params: ParamList, result: Result) -> None: - print("lights banwords") - result.abort = True - - _SPEAKER_WORDS: FrozenSet[str] = frozenset( ( "tónlist", "lag", "hátalari", - "bylgja", "útvarp", "útvarpsstöð", "útvarp saga", + "bylgja", "gullbylgja", "x-ið", "léttbylgjan", @@ -299,7 +251,6 @@ def QIoTSpeakerHotwords(node: Node, params: ParamList, result: Result) -> None: "útvarp hundrað og einn", "útvarp hundrað einn", "útvarp hundrað 1", - "útvarp", ) ) @@ -307,53 +258,37 @@ def QIoTSpeakerHotwords(node: Node, params: ParamList, result: Result) -> None: def sentence(state: QueryStateDict, result: Result) -> None: """Called when sentence processing is complete""" q: Query = state["query"] - if result.get("abort"): - q.set_error("E_QUERY_NOT_UNDERSTOOD") - return # Extract matched terminals in grammar (used like lemmas in this case) lemmas = set( i[0].root(state, result.params) for i in result.enum_descendants(lambda x: isinstance(x, TerminalNode)) ) - if not lemmas.isdisjoint(_SPEAKER_WORDS): - q.set_error("E_QUERY_NOT_UNDERSTOOD") - return - changing_color = result.get("changing_color", False) - changing_scene = result.get("changing_scene", False) - changing_brightness = result.get("changing_brightness", False) - # changing_temp = result.get("changing_temp", False) - if ( - sum((changing_color, changing_scene, changing_brightness)) > 1 - or "qtype" not in result - ): - print("Multiple options error?") + if not lemmas.isdisjoint(_SPEAKER_WORDS) or result.qtype != _HUE_QTYPE: + # Uses a word that is associated with the sonos module + # (or incorrect qtype) q.set_error("E_QUERY_NOT_UNDERSTOOD") return q.set_qtype(result.qtype) - smartdevice_type = "iot" - cd = q.client_data(smartdevice_type) - device_data = None - if cd: - # Fetch relevant data from the device_data table to perform an action on the lights - device_data = cast(Optional[DeviceData], cd.get("iot_lights")) - - hue_credentials: Optional[Dict[str, str]] = None + # TODO: Caching? + cd = q.client_data("iot") + device_data = cast(Optional[_IoTDeviceData], cd.get("iot_lights")) if cd else None + bridge_ip: Optional[str] = None + username: Optional[str] = None if device_data is not None: - dev = device_data - assert dev is not None - # TODO: Better error checking - light = dev.get("philips_hue") - hue_credentials = light.get("credentials") - bridge_ip = hue_credentials.get("ip_address") - username = hue_credentials.get("username") - - if not device_data or not hue_credentials: - answer = "Það vantar að tengja Philips Hub-inn." - q.set_answer(*gen_answer(answer)) + # TODO: Error checking + bridge_ip = device_data["philips_hue"]["credentials"]["ip_address"] + username = device_data["philips_hue"]["credentials"]["username"] + + if not device_data or not (bridge_ip and username): + q.set_answer( + {"answer": "Það vantar að tengja Philips Hue miðstöðina."}, + "Það vantar að tengja Philips Hue miðstöðina.", + "Það vantar að tengja filips hjú miðstöðina.", + ) return try: @@ -370,10 +305,10 @@ def sentence(state: QueryStateDict, result: Result) -> None: + f"var BRIDGE_IP = '{bridge_ip}';var USERNAME = '{username}';" + read_jsfile(str(Path("Philips_Hue", "fuse_search.js"))) + read_jsfile(str(Path("Philips_Hue", "set_lights.js"))) + + f"return setLights('{light_or_group_name}', '{json.dumps(result.hue_obj)}');" ) - js += f"return setLights('{light_or_group_name}', '{json.dumps(result.hue_obj)}');" q.set_command(js) except Exception as e: - logging.warning("Exception while processing random query: {0}".format(e)) + logging.warning("Exception while processing iot_hue query: {0}".format(e)) q.set_error("E_EXCEPTION: {0}".format(e)) raise diff --git a/queries/iot_speakers.py b/queries/iot_speaker.py similarity index 99% rename from queries/iot_speakers.py rename to queries/iot_speaker.py index 2618e029..c9ea252d 100644 --- a/queries/iot_speakers.py +++ b/queries/iot_speaker.py @@ -70,7 +70,12 @@ _IoT_QTYPE = "IoTSpeakers" -TOPIC_LEMMAS = ["tónlist", "spila", "útvarp", "útvarpsstöð"] +TOPIC_LEMMAS = [ + "tónlist", + "spila", + "útvarp", + "útvarpsstöð", +] def help_text(lemma: str) -> str: @@ -93,7 +98,7 @@ def help_text(lemma: str) -> str: QUERY_NONTERMINALS = {"QIoTSpeaker", "QIoTSpeakerQuery"} # The context-free grammar for the queries recognized by this plug-in module -GRAMMAR = read_grammar_file("iot_speakers") +GRAMMAR = read_grammar_file("iot_speaker") def QIoTSpeaker(node: Node, params: ParamList, result: Result) -> None: diff --git a/query.py b/query.py index a20d0ad0..21f94b84 100755 --- a/query.py +++ b/query.py @@ -285,7 +285,9 @@ def calc_score(w: SPPF_Node) -> ResultDict: if ch is not None: rd = calc_score(ch) d = child_scores[fam_ix] - if "ban" in rd: # Carry ban status up the tree + if "ban" in rd: + # If this child/family is completely banned, + # carry its ban status on up into child_scores dict d["ban"] = rd["ban"] # type: ignore else: # Otherwise modify score d["sc"] += rd["sc"] @@ -368,7 +370,7 @@ def calc_score(w: SPPF_Node) -> ResultDict: return NULL_SC root_score = calc_score(root_node) - if "ban" in root_score: + if root_score.get("ban"): # Best family is banned, which means that # no non-banned families were found raise BannedForestException("Entire parse forest for this query is banned") @@ -489,7 +491,10 @@ def process_queries( self.process_sentence(state, query_tree) if query.has_answer(): # The processor successfully answered the query: We're done - query.dsm.update_dialogue_data() + if dialogue_name: + # Update dialogue data if appropriate + # TODO: This should be hidden within the dsm.get_answer + query.dsm.update_dialogue_data() return True return False @@ -1020,7 +1025,7 @@ def ban_nonterminal_set(self, nonterminals: Set[str]) -> None: """ diff = nonterminals.difference(self._parser._grammar.nonterminals) assert len(diff) == 0, ( - f"ban_nonterminals: nonterminals {diff} don't" + f"ban_nonterminals: nonterminals {diff} don't " "correspond to a nonterminal in the grammar" ) self._banned_nonterminals.update(nonterminals) diff --git a/tools/grammar_gen.py b/tools/grammar_gen.py index 2351bd66..1e734d82 100755 --- a/tools/grammar_gen.py +++ b/tools/grammar_gen.py @@ -27,7 +27,7 @@ Use --help to see more information on usage. """ -from typing import Callable, Iterable, Iterator, List, Optional, Union +from typing import Callable, Iterable, Iterator, List, Optional, Union, Match import re import sys @@ -186,8 +186,6 @@ def get_wordform(gi: BIN_LiteralTerminal) -> str: assert ( len(variants) > 0 ), f"Specify variant for single quoted terminal: {gi.name}" - else: - assert len(variants) == 0, f"Too many variants for atviksorð: {gi.name}" if not cat and len(ll[1]) > 0: # Guess category from lemma lookup @@ -310,6 +308,9 @@ def _generate_all( # (note: there are probably more recursive # nonterminals, they can be added here) _RECURSIVE_NT = re.compile(r"^Nl([/_][a-zA-Z0-9]+)*$") +_PLACEHOLDER_RE = re.compile(r"{([\w]+?)}") +_PLACEHOLDER_PREFIX = "GENERATORPLACEHOLDER_" +_PLACEHOLDER_PREFIX_LEN = len(_PLACEHOLDER_PREFIX) def _generate_one( @@ -320,6 +321,9 @@ def _generate_one( # Special handling of Nl nonterminal, # since it is recursive yield [pink(f"<{gi.name}>")] + elif gi.name.startswith(_PLACEHOLDER_PREFIX): + # Placeholder nonterminal (replaces) + yield [blue(f"{{{gi.name[_PLACEHOLDER_PREFIX_LEN:]}}}")] elif isinstance(gi, Nonterminal): if gi.is_optional and gi.name.endswith("*"): # Star nonterminal, signify using brackets and '...' @@ -416,16 +420,13 @@ def _generate_one( if args.output: p = args.output assert isinstance(p, Path) - try: - p.touch(exist_ok=False) # Raise error if we are overwriting a file - except FileExistsError: - if not args.force: - print("Output file already exists!") - exit(1) + if (p.is_file() or p.exists()) and not args.force: + print("Output file already exists!") + exit(1) if not args.color or p is not None: - # Undefine color functions useless: ColorF = lambda s: s + # Undefine color functions [ bold, black, @@ -446,10 +447,29 @@ def _generate_one( ] = [useless] * 16 grammar_fragments: str = PREAMBLE + + # We replace {...} format strings with a placeholder + placeholder_defs: str = "" + + def placeholder_func(m: Match[str]) -> str: + """ + Replaces {...} format strings in grammar with an empty nonterminal. + We then handle these nonterminals specifically in _generate_one(). + """ + global placeholder_defs + new_nt = f"{_PLACEHOLDER_PREFIX}{m.group(1)}" + # Create empty production for this nonterminal ('keep' tag just in case) + placeholder_defs += f"\n{new_nt} → ∅\n$tag(keep) {new_nt}\n" + # Replace format string with reference to new nonterminal + return new_nt + for file in [BIN_Parser._GRAMMAR_FILE] + args.files: # type: ignore with open(file, "r") as f: grammar_fragments += "\n" - grammar_fragments += f.read() + grammar_fragments += _PLACEHOLDER_RE.sub(placeholder_func, f.read()) + + # Add all the placeholder nonterminal definitions we added + grammar_fragments += placeholder_defs # Initialize QueryGrammar class from grammar files grammar = QueryGrammar() From 18e2acd3738f36d8b2f9a1ebc85a8df2b0c29079 Mon Sep 17 00:00:00 2001 From: Logi E Date: Thu, 29 Sep 2022 15:28:12 +0000 Subject: [PATCH 365/371] Preparing for visindavaka Large modifications to Hue javascript, simplified the Hue grammar for the time being. Working on Sonos module and the Sonos class. --- queries/extras/sonos.py | 72 +++-- queries/grammars/iot_hue.grammar | 389 +++++++++++++------------- queries/grammars/iot_speaker.grammar | 5 + queries/iot_hue.py | 91 ++++-- queries/iot_speaker.py | 138 ++++----- queries/js/Philips_Hue/fuse_search.js | 30 -- queries/js/Philips_Hue/set_lights.js | 335 ++++++++++++++-------- query.py | 15 +- resources/iot_supported.toml | 2 +- templates/hue-connection.html | 3 - 10 files changed, 590 insertions(+), 490 deletions(-) delete mode 100644 queries/js/Philips_Hue/fuse_search.js diff --git a/queries/extras/sonos.py b/queries/extras/sonos.py index 90c0bb79..4b31d234 100644 --- a/queries/extras/sonos.py +++ b/queries/extras/sonos.py @@ -24,6 +24,7 @@ import logging import json +from typing_extensions import TypedDict import flask import requests from datetime import datetime, timedelta @@ -62,6 +63,8 @@ def post_to_json_api( except Exception as e: logging.warning("Error parsing JSON API response: {0}".format(e)) return None + + _GROUPS_DICT = { "fjölskylduherbergi": "Family Room", "fjölskyldu herbergi": "Family Room", @@ -109,6 +112,21 @@ def post_to_json_api( } +class _Creds(TypedDict): + code: str + timestamp: str + access_token: str + refresh_token: str + + +class _SonosSpeakerData(TypedDict): + credentials: _Creds + + +class SonosDeviceData(TypedDict): + sonos: _SonosSpeakerData + + # TODO - Decide what should happen if user does not designate a speaker but owns multiple speakers # TODO - Remove debug print statements # TODO - Testing and proper error handling @@ -116,10 +134,10 @@ def post_to_json_api( class SonosClient: def __init__( self, - device_data: Dict[str, str], + device_data: SonosDeviceData, client_id: str, - group_name: str = None, - radio_name: str = None, + group_name: Optional[str] = None, + radio_name: Optional[str] = None, ): self._client_id = client_id self._device_data = device_data @@ -127,9 +145,8 @@ def __init__( self._radio_name = radio_name self._encoded_credentials = read_api_key("SonosEncodedCredentials") self._code = self._device_data["sonos"]["credentials"]["code"] - self._timestamp = ( - self._device_data.get("sonos").get("credentials").get("timestamp") - ) + self._timestamp = self._device_data["sonos"]["credentials"]["timestamp"] + try: self._access_token = self._device_data["sonos"]["credentials"][ "access_token" @@ -147,19 +164,11 @@ def __init__( self._group_id = self._get_group_id() self._store_data_and_credentials() - """ - ------------------------------------- PRIVATE METHODS -------------------------------------------------------------------------------- - """ - def _check_token_expiration(self) -> None: """ Checks if access token is expired, and calls a function to refresh it if necessary. """ - try: - timestamp = self._device_data["sonos"]["credentials"]["timestamp"] - except (KeyError, TypeError): - return - timestamp = datetime.strptime(timestamp, "%Y-%m-%d %H:%M:%S.%f") + timestamp = datetime.strptime(self._timestamp, "%Y-%m-%d %H:%M:%S.%f") if (datetime.now() - timestamp) > timedelta(hours=24): self._update_sonos_token() @@ -169,7 +178,8 @@ def _update_sonos_token(self) -> None: """ self._encoded_credentials = read_api_key("SonosEncodedCredentials") self._refresh_expired_token() - sonos_dict = { + + sonos_dict: SonosDeviceData = { "sonos": { "credentials": { "access_token": self._access_token, @@ -210,7 +220,7 @@ def _create_token(self) -> Union[None, List[Any], Dict[str, Any]]: self._timestamp = str(datetime.now()) return response - def _get_households(self) -> Dict[str, str]: + def _get_households(self) -> List[Dict[str, str]]: """ Returns the list of households of the user """ @@ -220,19 +230,6 @@ def _get_households(self) -> Dict[str, str]: response = query_json_api(url, headers=headers) return response["households"] - def _get_household_id(self) -> str: - """ - Returns the household id for the given query - """ - url = f"https://api.ws.sonos.com/control/api/v1/households" - headers = { - "Content-Type": "application/json", - "Authorization": f"Bearer {self._access_token}", - } - - response = query_json_api(url, headers) - return response["households"][0]["id"] - def _get_groups(self) -> Dict[str, str]: """ Returns the list of groups of the user @@ -335,7 +332,7 @@ def _store_data_and_credentials(self) -> None: sonos_dict["sonos"] = {"credentials": cred_dict} self._store_data(sonos_dict) - def _store_data(self, data: Dict) -> None: + def _store_data(self, data: SonosDeviceData) -> None: new_dict = {"iot_speakers": data} Query.store_query_data(self._client_id, "iot", new_dict, update_in_place=True) @@ -354,7 +351,9 @@ def _create_playerdict_for_db(self, players: list) -> Dict[str, str]: def _create_or_join_session(self, recursion=None) -> Optional[str]: url = f"https://api.ws.sonos.com/control/api/v1/groups/{self._group_id}/playbackSession/joinOrCreate" - payload = json.dumps({"appId": "com.mideind.embla", "appContext": "embla123"}) # FIXME: Use something else than embla123 + payload = json.dumps( + {"appId": "com.mideind.embla", "appContext": "embla123"} + ) # FIXME: Use something else than embla123 headers = { "Content-Type": "application/json", "Authorization": f"Bearer {self._access_token}", @@ -373,10 +372,6 @@ def _create_or_join_session(self, recursion=None) -> Optional[str]: session_id = response["sessionId"] return session_id - """ - ------------------------------------- PUBLIC METHODS -------------------------------------------------------------------------------- - """ - def play_radio_stream(self, radio_url: str) -> Optional[str]: session_id = self._create_or_join_session() if radio_url is None: @@ -385,10 +380,11 @@ def play_radio_stream(self, radio_url: str) -> Optional[str]: except KeyError: radio_url = "http://netradio.ruv.is/rondo.mp3" - url = f"https://api.ws.sonos.com/control/api/v1/groups/playbackSessions/{session_id}/playbackSession/loadStreamUrl?" + url = f"https://api.ws.sonos.com/control/api/v1/playbackSessions/{session_id}/playbackSession/loadStreamUrl" + payload = json.dumps( { - "streamUrl": f"{radio_url}", + "streamUrl": radio_url, "playOnCompletion": True, # "stationMetadata": {"name": f"{radio_name}"}, "itemId": "StreamItemId", diff --git a/queries/grammars/iot_hue.grammar b/queries/grammars/iot_hue.grammar index 53e4f92e..8c579119 100644 --- a/queries/grammars/iot_hue.grammar +++ b/queries/grammars/iot_hue.grammar @@ -22,6 +22,7 @@ QHueBreyttuSkiptu → "breyttu" | "skiptu" | "breyta" | "skipta" QHueLáttu → "láttu" | "láta" QHueHækkaðuAuktu → "hækkaðu" | "auktu" | "hækka" | "auka" QHueLækkaðuMinnkaðu → "lækkaðu" | "minnkaðu" | "lækka" | "minnka" +QHueVeraVerða → "vera" | "verða" # Commands for turning on lights QHueTurnOnLights → QHueKveiktu QHueKveiktuX @@ -62,122 +63,114 @@ QHueChangeColor → # Commands for changing the current scene QHueChangeScene → QHueKveiktu "á" QHueNewScene/þgf QHueHvar? - | QHueStilltuSettu "á" QHueNewScene/þf - | QHueBreyttuSkiptu "yfir"? "í" QHueNewScene/þf + | QHueStilltuSettu "á" QHueNewScene/þf QHueHvar? + | QHueBreyttuSkiptu "yfir"? "í" QHueNewScene/þf QHueHvar? QHueKveiktuX → QHueLight/þf? QHueHvar # ... ljósið í eldhúsinu | "á" QHueLight/þgf QHueHvar? # ... á lampanum í stofunni + | QHueAllLights # ... öll ljósin QHueSlökktuX → QHueLight/þf? QHueHvar # ... ljósið í eldhúsinu | "á" QHueLight/þgf QHueHvar? # ... á lampanum í stofunni + | QHueAllLights # ... öll ljósin QHueHækkaðuX → - QHueLjósTegund/þf QHueHvar? + QHueLight/þf QHueHvar? # TODO | QHueBrightnessSubject/þf QHueHvar? QHueLækkaðuX → QHueLight/þf QHueHvar? | QHueBrightnessSubject/þf QHueHvar? -QHueGerðuX → - QHueSubject/þf QHueHvar? QHueHvernigMake - | QHueSubject/þf QHueHvernigMake QHueHvar? - | QHueHvar? QHueSubject/þf QHueHvernigMake - | QHueHvar? QHueHvernigMake QHueSubject/þf - | QHueHvernigMake QHueSubject/þf QHueHvar? - | QHueHvernigMake QHueHvar? QHueSubject/þf - -# TODO: Add support for "stilltu rauðan lit á ljósið í eldhúsinu" -QHueSettuX → - QHueSubject/þf QHueHvar? QHueHvernigSet - | QHueSubject/þf QHueHvernigSet QHueHvar? - | QHueHvar? QHueSubject/þf QHueHvernigSet - | QHueHvar? QHueHvernigSet QHueSubject/þf - | QHueHvernigSet QHueSubject/þf QHueHvar? - | QHueHvernigSet QHueHvar? QHueSubject/þf - -QHueBreyttuX → - QHueSubjectOne/þgf QHueHvar? QHueHvernigChange - | QHueSubjectOne/þgf QHueHvernigChange QHueHvar? - | QHueHvar? QHueSubjectOne/þgf QHueHvernigChange - | QHueHvar? QHueHvernigChange QHueSubjectOne/þgf - | QHueHvernigChange QHueSubjectOne/þgf QHueHvar? - | QHueHvernigChange QHueHvar? QHueSubjectOne/þgf - -QHueLáttuX → - QHueSubject/þf QHueHvar? QHueHvernigLet - | QHueSubject/þf QHueHvernigLet QHueHvar? - | QHueHvar? QHueSubject/þf QHueHvernigLet - # | QHueHvar? QHueHvernigLet QHueSubject/þf - # | QHueHvernigLet QHueSubject/þf QHueHvar? - # | QHueHvernigLet QHueHvar? QHueSubject/þf - -QHueSubject/fall → - QHueSubjectOne/fall - | QHueSubjectTwo/fall - -# TODO: Decide whether LightSubject/þgf should be accepted -QHueSubjectOne/fall → - QHueLight/fall - | QHueColorSubject/fall - | QHueBrightnessSubject/fall - | QHueSceneSubject/fall - -QHueSubjectTwo/fall → - QHueGroupNameSubject/fall # á bara að styðja "gerðu eldhúsið rautt", "gerðu eldhúsið rómó" "gerðu eldhúsið bjartara", t.d. +# QHueGerðuX → +# QHueSubject/þf QHueHvar? QHueHvernigMake +# | QHueSubject/þf QHueHvernigMake QHueHvar? +# | QHueHvar? QHueSubject/þf QHueHvernigMake +# | QHueHvar? QHueHvernigMake QHueSubject/þf +# | QHueHvernigMake QHueSubject/þf QHueHvar? +# | QHueHvernigMake QHueHvar? QHueSubject/þf + +# QHueSettuX → +# QHueSubject/þf QHueHvar? QHueHvernigSet +# | QHueSubject/þf QHueHvernigSet QHueHvar? +# | QHueHvar? QHueSubject/þf QHueHvernigSet +# | QHueHvar? QHueHvernigSet QHueSubject/þf +# | QHueHvernigSet QHueSubject/þf QHueHvar? +# | QHueHvernigSet QHueHvar? QHueSubject/þf + +# QHueBreyttuX → +# QHueSubjectOne/þgf QHueHvar? QHueHvernigChange +# | QHueSubjectOne/þgf QHueHvernigChange QHueHvar? +# | QHueHvar? QHueSubjectOne/þgf QHueHvernigChange +# | QHueHvar? QHueHvernigChange QHueSubjectOne/þgf +# | QHueHvernigChange QHueSubjectOne/þgf QHueHvar? +# | QHueHvernigChange QHueHvar? QHueSubjectOne/þgf + +# QHueLáttuX → +# QHueSubject/þf QHueHvar? QHueHvernigLet +# | QHueSubject/þf QHueHvernigLet QHueHvar? +# | QHueHvar? QHueSubject/þf QHueHvernigLet +# | QHueHvar? QHueHvernigLet QHueSubject/þf +# | QHueHvernigLet QHueSubject/þf QHueHvar? +# | QHueHvernigLet QHueHvar? QHueSubject/þf + +# QHueÁHverju +# QHueSpyrjaHuldu_þf +# QHueSpyrjaHuldu_þgf +# QHueSpyrjaHuldu_ef +# QHueNewSetting_ef + +# QHueSubject/fall → +# QHueSubjectOne/fall +# | QHueSubjectTwo/fall + +# # TODO: Decide whether LightSubject/þgf should be accepted +# QHueSubjectOne/fall → +# QHueLight/fall +# | QHueColorSubject/fall +# | QHueBrightnessSubject/fall +# | QHueSceneWord/fall + +# QHueSubjectTwo/fall → +# QHueGroupName/fall # á bara að styðja "gerðu eldhúsið rautt", "gerðu eldhúsið rómó" "gerðu eldhúsið bjartara", t.d. QHueHvar → QHueLocationPreposition QHueGroupName/þgf + | QHueEverywhere -QHueHvernigMake → - QHueAnnaðAndlag # gerðu litinn rauðan í eldhúsinu EÐA gerðu birtuna meiri í eldhúsinu - | QHueAðHverju # gerðu litinn að rauðum í eldhúsinu - | QHueÞannigAð +# QHueHvernigMake → +# QHueAnnaðAndlag # gerðu litinn rauðan í eldhúsinu EÐA gerðu birtuna meiri í eldhúsinu +# | QHueAðHverju # gerðu litinn að rauðum í eldhúsinu +# | QHueÞannigAð -QHueHvernigSet → - QHueÁHvað - | QHueÞannigAð +# QHueHvernigSet → +# QHueÁHvað +# | QHueÞannigAð -QHueHvernigChange → - QHueÍHvað - | QHueÞannigAð +# QHueHvernigChange → +# QHueÍHvað +# | QHueÞannigAð -QHueHvernigLet → - QHueVerða QHueSomethingOrSomehow - | QHueVera QHueSomehow +# QHueHvernigLet → +# QHueVerða QHueSomethingOrSomehow +# | QHueVera QHueSomehow -QHueÞannigAð → - "þannig" "að"? pfn_nf QHueBeOrBecomeSubjunctive QHueAnnaðAndlag +# QHueÞannigAð → +# "þannig" "að"? pfn_nf QHueBeOrBecomeSubjunctive QHueAnnaðAndlag -# I think these verbs only appear in these forms. -# In which case these terminals should be deleted and a direct reference should be made in the relevant non-terminals. -QHueVeraVerða → - QHueVera | QHueVerða +# QHueBeOrBecomeSubjunctive → +# "verði" | "sé" -QHueVera → - "vera" - -QHueVerða → - "verða" - -QHueBeOrBecomeSubjunctive → - "verði" - | "sé" - -QHueColorSubject/fall → - QHueColorWord/fall QHueLight/ef? - | QHueColorWord/fall "á" QHueLight/þgf +# QHueColorSubject/fall → +# QHueColorWord/fall QHueLight/ef? +# | QHueColorWord/fall "á" QHueLight/þgf QHueBrightnessSubject/fall → QHueBrightnessWord/fall QHueLight/ef? | QHueBrightnessWord/fall "á" QHueLight/þgf -QHueSceneSubject/fall → QHueSceneWord/fall - -QHueGroupNameSubject/fall → QHueGroupName/fall - QHueLocationPreposition → QHueLocationPrepositionFirstPart? QHueLocationPrepositionSecondPart @@ -191,63 +184,73 @@ QHueLocationPrepositionFirstPart → | "upp:ao" | "út:ao" -QHueLocationPrepositionSecondPart → - "á" | "í" +QHueLocationPrepositionSecondPart → "á" | "í" -QHueGroupName/fall → - no/fall #| QHueBanwords/fall - # | sérnafn/fall +QHueLight/fall → + 'ljós:no'/fall + | QHueLightName/fall -QHueLightName/fall → - no/fall #| QHueBanwords/fall +# QHueLjósTegund/fall → +# | 'lampi:no'/fall +# | 'útiljós'/fall +# | 'leslampi'/fall +# | 'borðlampi'/fall +# | 'gólflampi'/fall +# | 'lýsing'/fall +# | 'birta'/fall +# | 'Birta'/fall + +QHueGroupName/fall → no/fall + # | sérnafn/fall + # | QHueBanwords/fall + +# Note: don't use this for 'öll ljósin í svefnherberginu' +# as this +QHueAllLights → + "öll" "ljósin" + +QHueEverywhere → + "alls_staðar" + | "alstaðar" + | "allstaðar" + | "allsstaðar" + | "alsstaðar" + | "öllu" "húsinu" + +QHueLightName/fall → no/fall # | sérnafn/fall + # | QHueBanwords/fall -QHueColorName/fall → - {color_names} +QHueColorName/fall → {color_names} -QHueSceneName → - no | lo #| QHueBanwords/fall +QHueSceneName → no | lo # | sérnafn/fall + # | QHueBanwords/fall -QHueAnnaðAndlag → - QHueNewSetting/nf - | QHueSpyrjaHuldu/nf +# QHueAnnaðAndlag → +# QHueNewSetting/nf +# | QHueSpyrjaHuldu/nf -QHueAðHverju → - "að" QHueNewSetting/þgf +# QHueAðHverju → +# "að" QHueNewSetting/þgf -QHueÁHvað → - "á" QHueNewSetting/þf +# QHueÁHvað → +# "á" QHueNewSetting/þf -QHueÍHvað → - "í" QHueNewSetting/þf +# QHueÍHvað → +# "í" QHueNewSetting/þf -QHueÁHverju → - "á" QHueLight/þgf +# QHueÁHverju → +# "á" QHueLight/þgf # | "á" QHueNewSetting/þgf -QHueSomethingOrSomehow → - QHueAnnaðAndlag - | QHueAðHverju - -QHueSomehow → - QHueAnnaðAndlag - | QHueÞannigAð - -QHueLight/fall → - QHueLjósTegund/fall - | QHueLightName/fall +# QHueSomethingOrSomehow → +# QHueAnnaðAndlag +# | QHueAðHverju -QHueLjósTegund/fall → - 'ljós'/fall - | 'lampi'/fall - # | 'útiljós'/fall - # | 'leslampi'/fall - # | 'borðlampi'/fall - # | 'gólflampi'/fall - # | 'lýsing'/fall - # | 'birta'/fall - # | 'Birta'/fall +# QHueSomehow → +# QHueAnnaðAndlag +# | QHueÞannigAð QHueColorWord/fall → 'litur'/fall @@ -255,7 +258,7 @@ QHueColorWord/fall → | 'blær'/fall QHueBrightnessWords/fall → - 'bjartur'/fall + 'bjartur:lo'/fall | QHueBrightnessWord/fall QHueBrightnessWord/fall → @@ -273,82 +276,82 @@ QHueSceneWord/fall → # | "Sena" # Need to ask Hulda how this works. -QHueSpyrjaHuldu/fall → - # QHueHuldaColor/fall - QHueHuldaBrightness/fall - # | QHueHuldaScene/fall - | QHueHuldaTemp/fall +# QHueSpyrjaHuldu/fall → +# # QHueHuldaColor/fall +# QHueHuldaBrightness/fall +# # | QHueHuldaScene/fall +# | QHueHuldaTemp/fall # Do I need a "new light state" non-terminal? -QHueNewSetting/fall → - QHueNewColor/fall - | QHueNewBrightness/fall - | QHueNewScene/fall +# QHueNewSetting/fall → +# QHueNewColor/fall +# | QHueNewBrightness/fall +# | QHueNewScene/fall -# TODO: Missing "meira dimmt" -QHueHuldaBrightness/fall → - QHueMoreBrighterOrHigher/fall QHueBrightnessWords/fall? - | QHueLessDarkerOrLower/fall QHueBrightnessWords/fall? +# # TODO: Missing "meira dimmt" +# QHueHuldaBrightness/fall → +# QHueMoreBrighterOrHigher/fall QHueBrightnessWords/fall? +# | QHueLessDarkerOrLower/fall QHueBrightnessWords/fall? -QHueHuldaTemp/fall → - QHueWarmer/fall - | QHueCooler/fall +# QHueHuldaTemp/fall → +# QHueWarmer/fall +# | QHueCooler/fall -# TODO: Unsure about whether to include /fall after QHueColorName +# # TODO: Unsure about whether to include /fall after QHueColorName QHueNewColor/fall → QHueColorWord/fall QHueColorName/fall | QHueColorName/fall QHueColorWord/fall? -QHueNewBrightness/fall → - 'sá'/fall? QHueBrightestOrDarkest/fall - | QHueBrightestOrDarkest/fall QHueBrightnessOrSettingWord/fall +# QHueNewBrightness/fall → +# 'sá'/fall? QHueBrightestOrDarkest/fall +# | QHueBrightestOrDarkest/fall QHueBrightnessOrSettingWord/fall QHueNewScene/fall → QHueSceneWord/fall QHueSceneName - | QHueSceneName QHueSceneWord/fall? - -QHueMoreBrighterOrHigher/fall → - 'mikill:lo'_mst/fall - | 'bjartur:lo'_mst/fall - | 'ljós:lo'_mst/fall - | 'hár:lo'_mst/fall - -QHueLessDarkerOrLower/fall → - 'lítill:lo'_mst/fall - | 'dökkur:lo'_mst/fall - | 'dimmur:lo'_mst/fall - | 'lágur:lo'_mst/fall - -QHueWarmer/fall → - 'hlýr:lo'_mst/fall - | 'heitur:lo'_mst/fall - | "hlýri" - -QHueCooler/fall → - 'kaldur:lo'_mst/fall - -QHueBrightestOrDarkest/fall → - QHueBrightest/fall - | QHueDarkest/fall - -QHueBrightest/fall → - 'bjartur:lo'_evb - | 'bjartur:lo'_esb - | 'ljós:lo'_evb - | 'ljós:lo'_esb - -QHueDarkest/fall → - 'dimmur:lo'_evb - | 'dimmur:lo'_esb - | 'dökkur:lo'_evb - | 'dökkur:lo'_esb - -QHueBrightnessOrSettingWord/fall → - QHueBrightnessWord/fall - | QHueStilling/fall - -QHueStilling/fall → - 'stilling'/fall + # | QHueSceneName QHueSceneWord/fall? + +# QHueMoreBrighterOrHigher/fall → +# 'mikill:lo'_mst/fall +# | 'bjartur:lo'_mst/fall +# | 'ljós:lo'_mst/fall +# | 'hár:lo'_mst/fall + +# QHueLessDarkerOrLower/fall → +# 'lítill:lo'_mst/fall +# | 'dökkur:lo'_mst/fall +# | 'dimmur:lo'_mst/fall +# | 'lágur:lo'_mst/fall + +# QHueWarmer/fall → +# 'hlýr:lo'_mst/fall +# | 'heitur:lo'_mst/fall +# | "hlýri" + +# QHueCooler/fall → +# 'kaldur:lo'_mst/fall + +# QHueBrightestOrDarkest/fall → +# QHueBrightest/fall +# | QHueDarkest/fall + +# QHueBrightest/fall → +# 'bjartur:lo'_evb +# | 'bjartur:lo'_esb +# | 'ljós:lo'_evb +# | 'ljós:lo'_esb + +# QHueDarkest/fall → +# 'dimmur:lo'_evb +# | 'dimmur:lo'_esb +# | 'dökkur:lo'_evb +# | 'dökkur:lo'_esb + +# QHueBrightnessOrSettingWord/fall → +# QHueBrightnessWord/fall +# | QHueStilling/fall + +# QHueStilling/fall → +# 'stilling'/fall # Catching hotwords from iot_speakers # QHueBanwords/fall → diff --git a/queries/grammars/iot_speaker.grammar b/queries/grammars/iot_speaker.grammar index 67b402c0..ff62a61b 100644 --- a/queries/grammars/iot_speaker.grammar +++ b/queries/grammars/iot_speaker.grammar @@ -47,8 +47,12 @@ QIoTSpeakerPlayVerb → 'spila:so'_bh # "pása", as a verb, is not recognized by Árnastofnun's database, but is relatively common in casual speech. QIoTSpeakerPauseVerb → "stöðvaðu" + | "stöðva" | "stoppaðu" + | "stoppa" | "pásaðu" + | "pása" + | "gerðu" "hlé" "á" QIoTSpeakerIncreaseOrDecreaseVerb → QIoTSpeakerIncreaseVerb @@ -350,6 +354,7 @@ QIoTSpeakerRas2 → QIoTSpeakerRondo → "rondo" "fm"? | "rondó" "fm"? + | "róndó" "fm"? QIoTSpeakerFm957 → "fm" "957" diff --git a/queries/iot_hue.py b/queries/iot_hue.py index 2fe40d44..c41912b4 100755 --- a/queries/iot_hue.py +++ b/queries/iot_hue.py @@ -116,7 +116,8 @@ def help_text(lemma: str) -> str: # The context-free grammar for the queries recognized by this plug-in module GRAMMAR = read_grammar_file( - "iot_hue", color_names=" | ".join(f"'{color}:lo'/fall" for color in _COLORS.keys()) + "iot_hue", + color_names=" | ".join(f"'{color}:lo'/fall" for color in _COLORS.keys()), ) @@ -153,12 +154,9 @@ def QHueChangeColor(node: Node, params: ParamList, result: Result) -> None: def QHueChangeScene(node: Node, params: ParamList, result: Result) -> None: result.action = "set_scene" scene_name = result.get("scene_name", None) + if scene_name is not None: - if "hue_obj" not in result: - result["hue_obj"] = {"on": True, "scene": scene_name} - else: - result["hue_obj"]["scene"] = scene_name - result["hue_obj"]["on"] = True + _upsert_hue_obj(result, {"on": True, "scene": scene_name}) def QHueIncreaseBrightness(node: Node, params: ParamList, result: Result) -> None: @@ -173,13 +171,11 @@ def QHueDecreaseBrightness(node: Node, params: ParamList, result: Result) -> Non def QHueCooler(node: Node, params: ParamList, result: Result) -> None: result.action = "decrease_colortemp" - result.changing_temp = True _upsert_hue_obj(result, {"ct_inc": -30000}) def QHueWarmer(node: Node, params: ParamList, result: Result) -> None: result.action = "increase_colortemp" - result.changing_temp = True _upsert_hue_obj(result, {"ct_inc": 30000}) @@ -208,6 +204,14 @@ def QHueGroupName(node: Node, params: ParamList, result: Result) -> None: result["group_name"] = result._indefinite +def QHueEverywhere(node: Node, params: ParamList, result: Result) -> None: + result["everywhere"] = True + + +def QHueAllLights(node: Node, params: ParamList, result: Result) -> None: + result["everywhere"] = True + + def QHueLightName(node: Node, params: ParamList, result: Result) -> None: result["light_name"] = result._indefinite @@ -254,6 +258,20 @@ def QHueLightName(node: Node, params: ParamList, result: Result) -> None: ) ) +_PROBABLY_LIGHT_NAME: FrozenSet[str] = frozenset( + ( + "ljós", + "loftljós", + "gólfljós", + "veggljós", + "lampi", + "lampar", + "borðlampi", + "gólflampi", + "vegglampi", + ) +) + def sentence(state: QueryStateDict, result: Result) -> None: """Called when sentence processing is complete""" @@ -271,29 +289,44 @@ def sentence(state: QueryStateDict, result: Result) -> None: return q.set_qtype(result.qtype) + if "action" in result: + q.set_key(result.action) - # TODO: Caching? - cd = q.client_data("iot") - device_data = cast(Optional[_IoTDeviceData], cd.get("iot_lights")) if cd else None + try: + # TODO: Caching? + cd = q.client_data("iot") + device_data = ( + cast(Optional[_IoTDeviceData], cd.get("iot_lights")) if cd else None + ) - bridge_ip: Optional[str] = None - username: Optional[str] = None - if device_data is not None: - # TODO: Error checking - bridge_ip = device_data["philips_hue"]["credentials"]["ip_address"] - username = device_data["philips_hue"]["credentials"]["username"] + bridge_ip: Optional[str] = None + username: Optional[str] = None + if device_data is not None: + # TODO: Error checking + bridge_ip = device_data["philips_hue"]["credentials"]["ip_address"] + username = device_data["philips_hue"]["credentials"]["username"] + + if not device_data or not (bridge_ip and username): + q.set_answer( + {"answer": "Það vantar að tengja Philips Hue miðstöðina."}, + "Það vantar að tengja Philips Hue miðstöðina.", + "Það vantar að tengja filips hjú miðstöðina.", + ) + return - if not device_data or not (bridge_ip and username): - q.set_answer( - {"answer": "Það vantar að tengja Philips Hue miðstöðina."}, - "Það vantar að tengja Philips Hue miðstöðina.", - "Það vantar að tengja filips hjú miðstöðina.", - ) - return + light = result.get("light_name", "*") + if light == "ljós": + # Non-specific word for light, so we match all + light = "*" - try: - # TODO: What if light and group is empty? - light_or_group_name = result.get("light_name", result.get("group_name", "")) + group = result.get("group_name", "") + if result.get("everywhere"): + # Specifically asked for everywhere, match every group + group = "*" + + # If group or scene name is more like the name of a light + if group in _PROBABLY_LIGHT_NAME: + light, group = group, light q.set_answer( {"answer": "Skal gert."}, @@ -302,10 +335,8 @@ def sentence(state: QueryStateDict, result: Result) -> None: ) js = ( read_jsfile(str(Path("Libraries", "fuse.js"))) - + f"var BRIDGE_IP = '{bridge_ip}';var USERNAME = '{username}';" - + read_jsfile(str(Path("Philips_Hue", "fuse_search.js"))) + read_jsfile(str(Path("Philips_Hue", "set_lights.js"))) - + f"return setLights('{light_or_group_name}', '{json.dumps(result.hue_obj)}');" + + f"return await setLights('{bridge_ip}','{username}','{light}','{group}','{json.dumps(result.hue_obj)}');" ) q.set_command(js) except Exception as e: diff --git a/queries/iot_speaker.py b/queries/iot_speaker.py index c9ea252d..e222987e 100644 --- a/queries/iot_speaker.py +++ b/queries/iot_speaker.py @@ -31,14 +31,14 @@ # TODO: Two specified groups or lights. # TODO: No specified location # TODO: Fix scene issues -from typing import Dict +from typing import Dict, cast import logging import random from query import Query, QueryStateDict from queries import read_grammar_file -from queries.extras.sonos import SonosClient +from queries.extras.sonos import SonosClient, SonosDeviceData from tree import ParamList, Result, Node # Dictionary of radio stations and their stream urls @@ -68,7 +68,7 @@ } -_IoT_QTYPE = "IoTSpeakers" +_SPEAKER_QTYPE = "IoTSpeakers" TOPIC_LEMMAS = [ "tónlist", @@ -102,7 +102,7 @@ def help_text(lemma: str) -> str: def QIoTSpeaker(node: Node, params: ParamList, result: Result) -> None: - result.qtype = _IoT_QTYPE + result.qtype = _SPEAKER_QTYPE def QIoTSpeakerTurnOnVerb(node: Node, params: ParamList, result: Result) -> None: @@ -286,83 +286,69 @@ def QIoTSpeakerUtvarpSudurland(node: Node, params: ParamList, result: Result) -> def sentence(state: QueryStateDict, result: Result) -> None: """Called when sentence processing is complete""" q: Query = state["query"] + + if result.get("qtype") != _SPEAKER_QTYPE or not q.client_id: + q.set_error("E_QUERY_NOT_UNDERSTOOD") + return + if "qkey" not in result: result.qkey = "turn_on" - if result.qkey == "turn_on" and result.get("target") == "radio": - result.qkey = "radio" - if "qtype" in result: - try: - q.set_qtype(result.qtype) - cd = q.client_data("iot") - device_data = None - if cd: - device_data = cd.get("iot_speakers") - if device_data is not None: - sonos_client = SonosClient( - device_data, q.client_id, group_name=result.get("group_name") - ) - - # Map of query keys to handler functions and the corresponding answer string for Embla - radio_url = _RADIO_STREAMS.get(result.get("station")) - handler_map = { - "turn_on": [ - sonos_client.toggle_play, - [], - "Ég kveikti á tónlistinni", - ], - "turn_off": [ - sonos_client.toggle_pause, - [], - "Ég slökkti á tónlistinni", - ], - "increase_volume": [ - sonos_client.increase_volume, - [], - "Ég hækkaði í tónlistinni", - ], - "decrease_volume": [ - sonos_client.decrease_volume, - [], - "Ég lækkaði í tónlistinni", - ], - "radio": [ - sonos_client.play_radio_stream, - [radio_url], - "Ég setti á útvarpstöðina", - ], - "next_song": [ - sonos_client.next_song, - [], - "Ég skipti á næsta lag", - ], - "prev_song": [ - sonos_client.prev_song, - [], - "Ég hötta á fyrri tón", # TODO: wtf - ], - } - handler, args, answer = handler_map.get(result.qkey) - response = handler(*args) - if response == "Group not found": - text_ans = f"Herbergið '{result.group_name}' fannst ekki. Vinsamlegast athugaðu í Sonos appinu hvort nafnið sé rétt." - else: - text_ans = answer - q.set_answer( - dict(answer=text_ans), - text_ans, - text_ans.replace("Sonos", "Sónos"), - ) - return + + qk: str = result.qkey + if qk == "turn_on" and result.get("target") == "radio": + qk = "radio" + + try: + q.set_qtype(result.qtype) + cd = q.client_data("iot") + device_data = None + if cd: + device_data = cd.get("iot_speakers") + if device_data is not None: + sonos_client = SonosClient( + cast(SonosDeviceData, device_data), + q.client_id, + group_name=result.get("group_name"), + ) + + answer: str + if qk == "turn_on": + sonos_client.toggle_play() + answer = "Ég kveikti á tónlistinni" + elif qk == "turn_off": + sonos_client.toggle_pause() + answer = "Ég slökkti á tónlistinni" + elif qk == "increase_volume": + sonos_client.increase_volume() + answer = "Ég hækkaði í tónlistinni" + elif qk == "decrease_volume": + sonos_client.decrease_volume() + answer = "Ég lækkaði í tónlistinni" + elif qk == "radio": + # TODO: Error checking + station = result.get("station") + radio_url = _RADIO_STREAMS[station] + sonos_client.play_radio_stream(radio_url) + answer = "Ég setti á útvarpstöðina" + elif qk == "next_song": + sonos_client.next_song() + answer = "Ég skipti í næsta lag" + elif qk == "prev_song": + sonos_client.prev_song() + answer = "Ég skipti í fyrra lag" else: - print("No device data found for this account") + logging.warning("Incorrect qkey in speaker module") return - except Exception as e: - logging.warning("Exception answering iot_speakers query: {0}".format(e)) - q.set_error("E_EXCEPTION: {0}".format(e)) + + q.set_answer( + dict(answer=answer), + answer, + answer.replace("Sonos", "Sónos"), + ) return - else: - print("ELSE") - q.set_error("E_QUERY_NOT_UNDERSTOOD") + except Exception as e: + logging.warning("Exception answering iot_speakers query: {0}".format(e)) + q.set_error("E_EXCEPTION: {0}".format(e)) return # TODO: Need to add check for if there are no registered devices to an account, probably when initilazing the querydata diff --git a/queries/js/Philips_Hue/fuse_search.js b/queries/js/Philips_Hue/fuse_search.js deleted file mode 100644 index 5bf95600..00000000 --- a/queries/js/Philips_Hue/fuse_search.js +++ /dev/null @@ -1,30 +0,0 @@ -"use strict"; - -/** Fuzzy search function that returns an object in the form of {result: (Object), score: (Number)} - * @param {String} query - The search term - * @param {Object} data - The data to search - */ -function philipsFuzzySearch(query, data) { - // Restructure data to be searchable by name - let newData = Object.keys(data).map(function (key) { - return { ID: key, info: data[key] }; - }); - // Fuzzy search for the query term (returns an array of objects) - var fuse = new Fuse(newData, { - keys: ["info", "info.name"], - includeScore: true, - shouldSort: true, - threshold: 0.5, - }); - let searchResult = fuse.search(query); - - let resultObject = {}; - if (searchResult[0] === undefined) { - return null; - } - // Structure the return object to be in the form of {result: (Object), score: (Number)} - resultObject.result = searchResult[0].item; - resultObject.score = searchResult[0].score; - - return resultObject; -} diff --git a/queries/js/Philips_Hue/set_lights.js b/queries/js/Philips_Hue/set_lights.js index 92177072..204a7c62 100644 --- a/queries/js/Philips_Hue/set_lights.js +++ b/queries/js/Philips_Hue/set_lights.js @@ -1,163 +1,265 @@ -"use strict"; +/** Fuzzy search function that returns an object in the form of {result: (Object), score: (Number)} + * @param {string} query - the search term + * @param {Object} data - the data to search + * @param {string[]} searchKeys - the key/s for searching the data + * @return {Object[]} List of results from search + */ +function fuzzySearch(query, data, searchKeys) { + if (searchKeys === undefined) { + searchKeys = ["info.name"]; + } + // Set default argument for searchKeys + + // Fuzzy search for the query term (returns an array of objects) + let fuse = new Fuse(data, { + includeScore: true, + keys: searchKeys, + shouldSort: true, + threshold: 0.5, + }); + + // Array of results + let searchResult = fuse.search(query); + return searchResult.map((obj) => { + // Copy score inside item + obj.item.score = obj.score; + // Return item itself + return obj.item; + }); +} -function call_api(url, state) { - fetch(`http://${BRIDGE_IP}/api/${USERNAME}/${url}`, { +/** Send a PUT request to the given URL endpoint of the Philips Hue hub. + * @param {string} hub_ip - the IP address of the Philips Hue hub on the local network + * @param {string} username - the username we have registered with the hub + * @param {string} url_endpoint - the relevant URL endpoint of the Hue API + * @param {string} state - JSON encoded body + * @returns {Promise} Promise which resolves to the hub's response + */ +async function call_api_v1(hub_ip, username, url_endpoint, state) { + return fetch(`http://${hub_ip}/api/${username}/${url_endpoint}`, { method: "PUT", body: state, }) .then((resp) => resp.json()) - .then((obj) => {}) - .catch((err) => {}); - return; + .catch((err) => [{ error: "Invalid response from hub." }]); } -/** Finds a matching light or group and returns an object with the ID, name and url for the target - * @param {String} target - the target to find the target e.g. "eldhús" - * @param {Object} allLights - an object of all lights from the API - * @param {Object} allGroups - an object of all groups from the API +/** + * Fetch all connected lights for this hub. + * @param {string} hub_ip - the IP address of the Philips Hue hub on the local network + * @param {string} username - the username we have registered with the hub + * @returns {Promise} Promise which resolves to list of all connected lights */ -function getTargetObject(target, allLights, allGroups) { - let targetObject; - let lightsResult = philipsFuzzySearch(target, allLights); - let groupsResult = philipsFuzzySearch(target, allGroups); - - if (lightsResult != null && groupsResult != null) { - // Found a match for a light group and a light - targetObject = - lightsResult.score < groupsResult.score // Select the light with the highest score - ? { - id: lightsResult.result.ID, - url: `lights/${lightsResult.result.ID}/state`, - } - : { - id: groupsResult.result.ID, - lights: groupsResult.result.info.lights, - url: `groups/${groupsResult.result.ID}/action`, - }; - } else if (lightsResult != null && groupsResult == null) { - // Found a match for a single light - targetObject = { - id: lightsResult.result.ID, - url: `lights/${lightsResult.result.ID}/state`, - }; - } else if (groupsResult != null && lightsResult == null) { - // Found a match for a light group - targetObject = { - id: groupsResult.result.ID, - lights: groupsResult.result.info.lights, - url: `groups/${groupsResult.result.ID}/action`, - }; - } else { - return; - } - return targetObject; +async function getAllLights(hub_ip, username) { + return fetch(`http://${hub_ip}/api/${username}/lights`) + .then((resp) => resp.json()) + .then( + // Restructure data to be easier to work with + (data) => Object.keys(data).map((key) => ({ ID: key, info: data[key] })) + ); } -/** Returns the ID for a given scene name using fuzzy search - * @param {String} sceneName - the name of the scene to find - * @param {Object} allScenes - an array of all scenes from the API +/** + * Fetch all light groups for this hub. + * @param {string} hub_ip - the IP address of the Philips Hue hub on the local network + * @param {string} username - the username we have registered with the hub + * @returns {Promise} Promise which resolves to list of all light groups */ -function getSceneID(scene_name, allScenes) { - let scenesResult = philipsFuzzySearch(scene_name, allScenes); - if (scenesResult != null) { - return scenesResult.result.ID; - } else { - return; - } -} - -async function getAllLights(hub_ip = BRIDGE_IP, username = USERNAME) { - return fetch(`http://${hub_ip}/api/${username}/lights`).then((resp) => resp.json()); -} - -async function getAllGroups(hub_ip = BRIDGE_IP, username = USERNAME) { - return fetch(`http://${hub_ip}/api/${username}/groups`).then((resp) => resp.json()); -} - -async function getAllScenes(hub_ip = BRIDGE_IP, username = USERNAME) { - return fetch(`http://${hub_ip}/api/${username}/scenes`).then((resp) => resp.json()); +async function getAllGroups(hub_ip, username) { + return fetch(`http://${hub_ip}/api/${username}/groups`) + .then((resp) => resp.json()) + .then( + // Restructure data to be easier to work with + (data) => Object.keys(data).map((key) => ({ ID: key, info: data[key] })) + ); } -function getCurrentState(id) { - return fetch(`http://${BRIDGE_IP}/api/${USERNAME}/lights/${id}`).then((resp) => resp.json()); +/** + * Fetch all scenes for this hub. + * @param {string} hub_ip - the IP address of the Philips Hue hub on the local network + * @param {string} username - the username we have registered with the hub + * @returns {Promise} Promise which resolves to list of registered scenes + */ +async function getAllScenes(hub_ip, username) { + return fetch(`http://${hub_ip}/api/${username}/scenes`) + .then((resp) => resp.json()) + .then( + // Restructure data to be easier to work with + (data) => Object.keys(data).map((key) => ({ ID: key, info: data[key] })) + ); } /** * Check whether any of the targeted lights are Ikea TRADFRI lights. * Done in order to deal with a bug where the lights only accept * one parameter at a time. - * @param {Object} targetObject Object containing all lights in target/query - * @param {Object} all_lights Object containing info for all connected lights - * @returns True if any of the query lights are Ikea TRADFRI lights, false otherwise + * @param {Object[]} matchedLights - array containing all lights in target/query + * @returns {boolean} True if any of the query lights are Ikea TRADFRI lights, false otherwise */ -function check_if_ikea_bulb_in_target(targetObject, all_lights) { - for (let key in targetObject.lights) { - let lightID = targetObject.lights[key]; - let light = all_lights[lightID]; +function containsIKEABulb(matchedLights) { + // Why does for..in give indexes instead of objects? :/ + for (const i in matchedLights) { + const light = matchedLights[i]; if ( - light.manufacturername.includes("IKEA") || - light.modelid.includes("TRADFRI") || - light.manufacturername.includes("ikea") || - light.manufacturername.includes("tradfri") + light.info.manufacturername.includes("IKEA") || + light.info.modelid.includes("TRADFRI") || + light.info.manufacturername.includes("ikea") || + light.info.manufacturername.includes("tradfri") ) { return true; } } + return false; +} + +/** + * For a given light-group query pair, + * find the most appropriate API endpoint. + * @param {string} light - query for a light + * @param {string} group - query for a group + * @param {Object[]} allLights - list of all lights + * @param {Object[]} allGroups - list of all groups + * @returns {Object} object containing API endpoint and affected lights/groups + */ +function findLights(light, group, allLights, allGroups) { + let matchedLights = []; + let matchedGroups = []; + + // Find matching lights + if (light !== "*") { + matchedLights = fuzzySearch(light, allLights); + console.log("matchedLights:", matchedLights.toString()); + } + // Find matching groups + if (group !== "*" && group !== "") { + matchedGroups = fuzzySearch(group, allGroups); + console.log("matchedGroups:", matchedGroups.toString()); + } + + // API URL endpoint (different for groups vs lights) + let api_endpoint = null; + if (light === "*" && group === "*") { + api_endpoint = `groups/0/action`; // Special all-lights group + } else { + if (matchedGroups.length === 0 && matchedLights.length === 0) { + // Fallback if no group and light matched + let x = light; + light = group; + group = x; + console.log("SWITCHED"); + // Try searching again, but swap light and group queries + if (light !== "*") { + matchedLights = fuzzySearch(light, allLights); + } + if (group !== "*" && group !== "") { + matchedGroups = fuzzySearch(group, allGroups); + } + if (matchedGroups.length === 0 && matchedLights.length === 0) { + return "Ekki tókst að finna ljós"; + } + } + if (matchedLights.length === 0) { + console.log("found a group"); + // Found a group + if (light === "*") { + console.log("target entire group"); + // Target entire group + api_endpoint = `groups/${matchedGroups[0].ID}/action`; + // Update matched lights + matchedLights = allLights.filter((li) => + matchedGroups[0].info.lights.includes(li.ID) + ); + } else { + console.log("villa í ljósinu"); + return `Ekkert ljós fannst í herberginu ${group} með nafnið ${light}.`; + } + } else if (matchedGroups.length === 0) { + console.log("fann ljós"); + // Found a light + api_endpoint = `lights/${matchedLights[0].ID}/state`; + matchedLights = [matchedLights[0]]; + } else { + console.log("fundum bæði"); + // Found both, try to intelligently find a light within a group + for (let i1 in matchedGroups) { + let currGroup = matchedGroups[i1]; + for (let i2 in matchedLights) { + let currLight = matchedLights[i2]; + if (currGroup.info.lights.includes(currLight.ID)) { + // Found the matched light inside the current group; perfect + api_endpoint = `lights/${currLight.ID}/state`; + matchedLights = [currLight]; + break; + } + } + if (api_endpoint !== null) { + // Found a light, end loop + break; + } + } + } + } + return { + endpoint: api_endpoint, + affectedLights: matchedLights, + affectedGroups: matchedGroups, + }; } /** Gets a target for the given query and sets the state of the target to the given state using a fetch request. - * @param {String} target - the target to find the target, e.g. "eldhús" or "lampi" - * @param {String} state - the state to set the target to e.g. {"on": true} or {"scene": "energize"} - * @return Basic string explaining what happened (in Icelandic). + * @param {string} hub_ip - the IP address of the Philips Hue hub on the local network + * @param {string} username - the username we have registered with the hub + * @param {string} light - the name of a light, "*" matches anything + * @param {string} group - the name of a group, "*" matches anything + * @param {string} json_data - the JSON encoded state to set the target to e.g. {"on": true} or {"scene": "energize"} + * @return {string} Basic string explaining what happened (in Icelandic). */ -async function setLights(target, state) { - let parsedState = JSON.parse(state); - let promiseList = [getAllGroups(), getAllLights()]; - let sceneName; +async function setLights(hub_ip, username, light, group, json_data) { + let parsedState = JSON.parse(json_data); + console.log("parsedState:", parsedState); + let promiseList = [getAllGroups(hub_ip, username), getAllLights(hub_ip, username)]; if (parsedState.scene) { - sceneName = parsedState.scene; - promiseList.push(getAllScenes()); + promiseList.push(getAllScenes(hub_ip, username)); } - // Get all lights and all groups from the API (and all scenes if "scene" was a paramater) + console.log("created promises..."); + // Get all lights and all groups from the API + // (and all scenes if "scene" was a paramater) return await Promise.allSettled(promiseList).then((resolvedPromises) => { + console.log("promises resolved!"); let allGroups = resolvedPromises[0].value; + console.log("allGroups:", allGroups); let allLights = resolvedPromises[1].value; + console.log("allLights:", allLights); let allScenes; if (resolvedPromises.length > 2) { allScenes = resolvedPromises[2].value; - } - - // Get the target object for the given target - let targetObject = getTargetObject(target, allLights, allGroups); - if (targetObject === undefined) { - return "Ekki tókst að finna ljós"; - } - - // Check if state includes a scene or a brightness change - if (sceneName) { - let sceneID = getSceneID(parsedState.scene, allScenes); - if (sceneID === undefined) { - return "Ekki tókst að finna senu"; + let scenesResults = fuzzySearch(parsedState.scene, allScenes); + console.log("scenesResults:", scenesResults); + if (scenesResults.length == 0) { + return `Ekki tókst að finna senuna ${parsedState.scene}.`; } - parsedState.scene = sceneID; // Change the scene parameter to the scene ID - state = JSON.stringify(parsedState); - } else if (parsedState.bri_inc) { - state = JSON.stringify(parsedState); + parsedState.scene = scenesResults[0].ID; // Change the scene parameter to the scene ID } - + let targetObj = findLights(light, group, allLights, allGroups); + let payload = JSON.stringify(parsedState); // Send data to API - let url = targetObject.url; - call_api(url, state); + console.log("sendum payload:", payload); + call_api_v1(hub_ip, username, targetObj.endpoint, payload); + console.log("buid :)"); - // Deal with Ikea TRADFRI bug - let isTradfriBulb = check_if_ikea_bulb_in_target(targetObject, allLights); - if (sceneName && isTradfriBulb) { + // Deal with IKEA TRADFRI bug + // (sometimes can't handle more than one change at a time) + if ( + containsIKEABulb(targetObj.affectedLights) && + (parsedState.scene || Object.keys(parsedState).length > 2) + ) { + console.log("ja ikea pera"); let sleep = (ms) => new Promise((r) => setTimeout(r, ms)); sleep(450).then(() => { - call_api(url, state); + call_api_v1(hub_ip, username, targetObj.endpoint, payload); }); } - + console.log("buid 2 :)"); // Basic formatting of answers if (parsedState.scene) { return "Ég breytti um senu."; @@ -166,6 +268,7 @@ async function setLights(target, state) { return "Ég slökkti ljósin."; } if (parsedState.on == true && Object.keys(parsedState).length == 1) { + console.log("alveg buid faddfafdafda"); return "Ég kveikti ljósin."; } if (parsedState.bri_inc && parsedState.bri_inc > 0) { @@ -177,6 +280,6 @@ async function setLights(target, state) { if (parsedState.xy || parsedState.hue) { return "Ég breytti lit ljóssins."; } - return "Stillingu hefur verið breytt."; + return "Stillingu ljósa var breytt."; }); } diff --git a/query.py b/query.py index 21f94b84..bd8f3bf3 100755 --- a/query.py +++ b/query.py @@ -1368,15 +1368,24 @@ def execute(self) -> ResponseDict: result["valid"] = True if Settings.DEBUG: # Dump query results to the console - def converter(o): + def converter(o: object): """Ensure that datetime is output in ISO format to JSON""" if isinstance(o, datetime): return o.isoformat()[0:16] return None print( - "{0}".format( - json.dumps(result, indent=3, ensure_ascii=False, default=converter) + json.dumps( + { + k: (f"{v[:100]} ... {v[-100:]}") + if isinstance(v, str) and len(v) > 1000 + else v + for k, v in result.items() + # This ^ is just to shorten very long lines + }, + indent=3, + ensure_ascii=False, + default=converter, ) ) return result diff --git a/resources/iot_supported.toml b/resources/iot_supported.toml index fc90a9c6..6f434b6e 100644 --- a/resources/iot_supported.toml +++ b/resources/iot_supported.toml @@ -35,7 +35,7 @@ name = "Spotify" brand = "Spotify" group = "iot_streaming" logo = "https://upload.wikimedia.org/wikipedia/commons/thumb/1/19/Spotify_logo_without_text.svg/168px-Spotify_logo_without_text.svg.png" -#music_note_outlined +# music_note_outlined icon = 0xf1fb webview_home = '{host}/iot/spotify-instructions?client_id={client_id}&iot_group=iot_streaming&iot_name=spotify' webview_connect = '{host}/iot/spotify-connection?client_id={client_id}&request_url={host}' diff --git a/templates/hue-connection.html b/templates/hue-connection.html index ef21a27e..148e8b84 100644 --- a/templates/hue-connection.html +++ b/templates/hue-connection.html @@ -43,9 +43,6 @@

    Leiðbeiningar