(.*?\))$", answer_text, re.DOTALL|re.MULTILINE)
+ memories = []
+ for match in matches:
+ to_execute = match.group(1)
+ answer_text = answer_text.replace(match.group(0), "")
+ memories.append(to_execute)
+
return answer_text, memories
async def _run_code(self, to_execute):
@@ -206,13 +223,13 @@ async def _run_code(self, to_execute):
except Exception as e:
result = (
- f'Error while executing\n\n"""python\n{to_execute}\n"""\n\n{str(e)}'
+ f"Error while executing\n\n```python\n{to_execute}\n```\n\n{str(e)}"
)
traceback.print_exc()
break
if not result:
- result = f'\n"""python\n{to_execute}\n"""'
+ result = f"\n```python\n{to_execute}\n```"
return result
diff --git a/wafl/answerer/rule_creator.py b/wafl/answerer/rule_creator.py
deleted file mode 100644
index a1a49ba0..00000000
--- a/wafl/answerer/rule_creator.py
+++ /dev/null
@@ -1,49 +0,0 @@
-class RuleCreator:
- def __init__(
- self,
- knowledge,
- config,
- interface,
- max_num_rules,
- delete_current_rule,
- max_recursion=1,
- ):
- self._knowledge = knowledge
- self._config = config
- self._interface = interface
- self._max_num_rules = max_num_rules
- self._delete_current_rule = delete_current_rule
- self._max_indentation = max_recursion
- self._indent_str = " "
-
- async def create_from_query(self, query):
- rules = await self._knowledge.ask_for_rule_backward(query)
- rules = rules[: self._max_num_rules]
- rules_texts = []
- for rule in rules:
- rules_text = f"- If {rule.effect.text} go through the following points:\n"
- for cause_index, cause in enumerate(rule.causes):
- rules_text += f"{self._indent_str}{cause_index + 1}) {cause.text}\n"
- rules_text += await self.recursively_add_rules(cause)
-
- rules_text += f'{self._indent_str}{len(rule.causes) + 1}) After you completed all the steps output "{self._delete_current_rule}" and continue the conversation.\n'
-
- rules_texts.append(rules_text)
- await self._interface.add_fact(f"The bot remembers the rule:\n{rules_text}")
-
- return "\n".join(rules_texts)
-
- async def recursively_add_rules(self, query, depth=2):
- rules = await self._knowledge.ask_for_rule_backward(query, threshold=0.95)
- rules = rules[: self._max_num_rules]
- rules_texts = []
- for rule in rules:
- rules_text = f"- If {rule.effect.text} go through the following points:\n"
- for cause_index, causes in enumerate(rule.causes):
- indentation = self._indent_str * depth
- rules_text += f"{indentation}{cause_index + 1}) {causes.text}\n"
- rules_text += await self.recursively_add_rules(causes.text, depth + 1)
-
- rules_texts.append(rules_text)
-
- return "\n".join(rules_texts)
diff --git a/wafl/answerer/rule_maker.py b/wafl/answerer/rule_maker.py
new file mode 100644
index 00000000..f9775d21
--- /dev/null
+++ b/wafl/answerer/rule_maker.py
@@ -0,0 +1,34 @@
+class RuleMaker:
+ def __init__(
+ self,
+ knowledge: "Knowledge",
+ config: "BaseConfig",
+ interface: "BaseInterface",
+ max_num_rules: int,
+ delete_current_rule: str,
+ max_recursion: int = 3,
+ ):
+ self._knowledge = knowledge
+ self._config = config
+ self._interface = interface
+ self._max_num_rules = max_num_rules
+ self._delete_current_rule = delete_current_rule
+ if not config.get_value("max_recursion"):
+ self._max_indentation = max_recursion
+ else:
+ self._max_indentation = config.get_value("max_recursion")
+
+ async def create_from_query(self, query):
+ rules = await self._knowledge.ask_for_rule_backward(query, threshold=0.92)
+ rules = rules[: self._max_num_rules]
+ rules_texts = []
+ for rule in rules:
+ rules_text = rule.get_string_using_template(
+ "- If {effect} go through the following points:"
+ )
+ rules_text += f'{rule.indent_str}- After you completed all the steps output "{self._delete_current_rule}" and continue the conversation.\n'
+
+ rules_texts.append(rules_text)
+ await self._interface.add_fact(f"The bot remembers the rule:\n{rules_text}")
+
+ return "\n".join(rules_texts)
diff --git a/wafl/command_line.py b/wafl/command_line.py
index 14fbb6cd..fc68db06 100644
--- a/wafl/command_line.py
+++ b/wafl/command_line.py
@@ -7,7 +7,7 @@
run_from_command_line,
run_testcases,
print_incipit,
- download_models
+ download_models,
)
from wafl.runners.run_from_actions import run_action
from wafl.runners.run_from_audio import run_from_audio
diff --git a/wafl/connectors/base_llm_connector.py b/wafl/connectors/base_llm_connector.py
index 806164e4..1bd57a63 100644
--- a/wafl/connectors/base_llm_connector.py
+++ b/wafl/connectors/base_llm_connector.py
@@ -1,7 +1,6 @@
import logging
import re
-
from wafl.connectors.utils import select_best_answer
_system_logger = logging.getLogger(__file__)
@@ -39,11 +38,7 @@ async def generate(self, prompt: str) -> str:
text = prompt
start = len(text)
- while (
- all(item not in text[start:] for item in self._last_strings)
- and len(text) < start + self._max_reply_length
- ):
- text += select_best_answer(await self.predict(text), self._last_strings)
+ text += select_best_answer(await self.predict(text), self._last_strings)
end_set = set()
for item in self._last_strings:
@@ -59,7 +54,7 @@ async def generate(self, prompt: str) -> str:
if end_set:
end = min(end_set)
- candidate_answer = text[start:end].split("bot: ")[-1].strip()
+ candidate_answer = text[start:end].strip()
candidate_answer = re.sub(r"(.*)<\|.*\|>", r"\1", candidate_answer).strip()
if prompt not in self._cache:
diff --git a/wafl/connectors/remote/remote_llm_connector.py b/wafl/connectors/remote/remote_llm_connector.py
index d232df7d..e9e3c36e 100644
--- a/wafl/connectors/remote/remote_llm_connector.py
+++ b/wafl/connectors/remote/remote_llm_connector.py
@@ -1,7 +1,10 @@
+import json
+
import aiohttp
import asyncio
from wafl.connectors.base_llm_connector import BaseLLMConnector
+from wafl.variables import is_supported
class RemoteLLMConnector(BaseLLMConnector):
@@ -9,13 +12,14 @@ class RemoteLLMConnector(BaseLLMConnector):
_max_reply_length = 1024
_num_prediction_tokens = 200
_cache = {}
- _num_replicas = 10
- def __init__(self, config, last_strings=None):
+ def __init__(self, config, last_strings=None, num_replicas=3):
super().__init__(last_strings)
host = config["model_host"]
port = config["model_port"]
+ self._default_temperature = config["temperature"]
self._server_url = f"https://{host}:{port}/predictions/bot"
+ self._num_replicas = num_replicas
try:
loop = asyncio.get_running_loop()
@@ -28,28 +32,37 @@ def __init__(self, config, last_strings=None):
):
raise RuntimeError("Cannot connect a running LLM.")
- async def predict(self, prompt: str, temperature=None, num_tokens=None) -> [str]:
+ async def predict(
+ self, prompt: str, temperature=None, num_tokens=None, num_replicas=None
+ ) -> [str]:
if not temperature:
- temperature = 0.5
+ temperature = self._default_temperature
if not num_tokens:
num_tokens = self._num_prediction_tokens
+ if not num_replicas:
+ num_replicas = self._num_replicas
+
payload = {
"data": prompt,
"temperature": temperature,
"num_tokens": num_tokens,
"last_strings": self._last_strings,
- "num_replicas": self._num_replicas,
+ "num_replicas": num_replicas,
}
for _ in range(self._max_tries):
async with aiohttp.ClientSession(
- connector=aiohttp.TCPConnector(ssl=False)
+ conn_timeout=6000,
+ connector=aiohttp.TCPConnector(ssl=False),
) as session:
async with session.post(self._server_url, json=payload) as response:
- answer = await response.text()
- return answer.split("<||>")
+ answer = json.loads(await response.text())
+ status = answer["status"]
+ if status != "success":
+ raise RuntimeError(f"Error in prediction: {answer}")
+ return answer["prediction"].split("<||>")
return [""]
@@ -66,7 +79,14 @@ async def check_connection(self):
conn_timeout=3, connector=aiohttp.TCPConnector(ssl=False)
) as session:
async with session.post(self._server_url, json=payload) as response:
- await response.text()
+ answer = json.loads(await response.text())
+ wafl_llm_version = answer["version"]
+ print(f"Connected to wafl-llm v{wafl_llm_version}.")
+ if not is_supported(wafl_llm_version):
+ print("This version of wafl-llm is not supported.")
+ print("Please update wafl-llm.")
+ raise aiohttp.client.InvalidURL
+
return True
except aiohttp.client.InvalidURL:
diff --git a/wafl/connectors/remote/remote_whisper_connector.py b/wafl/connectors/remote/remote_whisper_connector.py
index 3b7a8cba..d9498a83 100644
--- a/wafl/connectors/remote/remote_whisper_connector.py
+++ b/wafl/connectors/remote/remote_whisper_connector.py
@@ -38,6 +38,11 @@ async def predict(self, waveform, hotword=None) -> Dict[str, float]:
async with session.post(self._server_url, json=payload) as response:
data = await response.text()
prediction = json.loads(data)
+ if "transcription" not in prediction:
+ raise RuntimeError(
+ "No transcription found in prediction. Is your microphone working?"
+ )
+
transcription = prediction["transcription"]
score = prediction["score"]
logp = prediction["logp"]
diff --git a/wafl/events/conversation_events.py b/wafl/events/conversation_events.py
index cf38856b..e00572f1 100644
--- a/wafl/events/conversation_events.py
+++ b/wafl/events/conversation_events.py
@@ -1,5 +1,6 @@
import os
import re
+import traceback
from wafl.events.answerer_creator import create_answerer
from wafl.simple_text_processing.normalize import normalized
@@ -59,6 +60,7 @@ async def _process_query(self, text: str):
if (
not text_is_question
+ and self._interface.get_utterances_list()
and self._interface.get_utterances_list()[-1].find("user:") == 0
):
await self._interface.output("I don't know what to reply")
@@ -108,7 +110,7 @@ def reload_knowledge(self):
def reset_discourse_memory(self):
self._answerer = create_answerer(
- self._config, self._knowledge, self._interface, logger
+ self._config, self._knowledge, self._interface, self._logger
)
def _activation_word_in_text(self, activation_word, text):
diff --git a/wafl/facts.py b/wafl/facts.py
index 23db2067..c2a3eec2 100644
--- a/wafl/facts.py
+++ b/wafl/facts.py
@@ -1,9 +1,10 @@
from dataclasses import dataclass
+from typing import Union
@dataclass
class Fact:
- text: str
+ text: Union[str, dict]
is_question: bool = False
variable: str = None
is_interruption: bool = False
diff --git a/wafl/frontend/index.html b/wafl/frontend/index.html
index bc7283f1..8137ca99 100644
--- a/wafl/frontend/index.html
+++ b/wafl/frontend/index.html
@@ -39,6 +39,37 @@
+
+
+
+
+
+
diff --git a/wafl/frontend/selector.html b/wafl/frontend/selector.html
index 15ad1ce6..0752552e 100644
--- a/wafl/frontend/selector.html
+++ b/wafl/frontend/selector.html
@@ -2,7 +2,6 @@
WAFL frontend
-
diff --git a/wafl/interface/base_interface.py b/wafl/interface/base_interface.py
index 18089c28..cb54f27c 100644
--- a/wafl/interface/base_interface.py
+++ b/wafl/interface/base_interface.py
@@ -20,6 +20,9 @@ async def input(self) -> str:
def bot_has_spoken(self, to_set: bool = None):
raise NotImplementedError
+ async def insert_input(self, text: str):
+ pass
+
def is_listening(self):
return self._is_listening
@@ -32,9 +35,6 @@ def deactivate(self):
self._facts = []
self._utterances = []
- def add_hotwords(self, hotwords: List[str]):
- raise NotImplementedError
-
async def add_choice(self, text):
self._choices.append((time.time(), text))
await self.output(f"Making the choice: {text}", silent=True)
@@ -60,8 +60,17 @@ def reset_history(self):
self._choices = []
self._facts = []
+ def add_hotwords(self, hotwords):
+ pass
+
def _decorate_reply(self, text: str) -> str:
if not self._decorator:
return text
return self._decorator.extract(text, self._utterances)
+
+ def _insert_utterance(self, speaker, text: str):
+ if self._utterances == [] or text != self._utterances[-1][1].replace(
+ f"{speaker}: ", ""
+ ):
+ self._utterances.append((time.time(), f"{speaker}: {text}"))
diff --git a/wafl/interface/list_interface.py b/wafl/interface/list_interface.py
new file mode 100644
index 00000000..c7416d2e
--- /dev/null
+++ b/wafl/interface/list_interface.py
@@ -0,0 +1,53 @@
+import asyncio
+from typing import List
+
+from wafl.interface.base_interface import BaseInterface
+
+
+class ListInterface(BaseInterface):
+ def __init__(self, interfaces_list: List[BaseInterface]):
+ super().__init__()
+ self._interfaces_list = interfaces_list
+ self._synchronize_interfaces()
+
+ async def output(self, text: str, silent: bool = False):
+ await asyncio.wait(
+ [interface.output(text, silent) for interface in self._interfaces_list],
+ return_when=asyncio.ALL_COMPLETED,
+ )
+
+ async def input(self) -> str:
+ done, pending = await asyncio.wait(
+ [interface.input() for interface in self._interfaces_list],
+ return_when=asyncio.FIRST_COMPLETED,
+ )
+ return done.pop().result()
+
+ async def insert_input(self, text: str):
+ await asyncio.wait(
+ [interface.insert_input(text) for interface in self._interfaces_list],
+ return_when=asyncio.ALL_COMPLETED,
+ )
+
+ def bot_has_spoken(self, to_set: bool = None):
+ for interface in self._interfaces_list:
+ interface.bot_has_spoken(to_set)
+
+ def activate(self):
+ for interface in self._interfaces_list:
+ interface.activate()
+ super().activate()
+
+ def deactivate(self):
+ for interface in self._interfaces_list:
+ interface.deactivate()
+ super().deactivate()
+ self._synchronize_interfaces()
+
+ def add_hotwords(self, hotwords):
+ for interface in self._interfaces_list:
+ interface.add_hotwords(hotwords)
+
+ def _synchronize_interfaces(self):
+ for interface in self._interfaces_list:
+ interface._utterances = self._utterances
diff --git a/wafl/interface/queue_interface.py b/wafl/interface/queue_interface.py
index 08fdc247..cf14c9ca 100644
--- a/wafl/interface/queue_interface.py
+++ b/wafl/interface/queue_interface.py
@@ -1,5 +1,4 @@
import asyncio
-import time
from wafl.interface.base_interface import BaseInterface
@@ -16,9 +15,8 @@ async def output(self, text: str, silent: bool = False):
self.output_queue.append({"text": text, "silent": True})
return
- utterance = text
- self.output_queue.append({"text": utterance, "silent": False})
- self._utterances.append((time.time(), f"bot: {text}"))
+ self.output_queue.append({"text": text, "silent": False})
+ self._insert_utterance("bot", text)
self.bot_has_spoken(True)
async def input(self) -> str:
@@ -26,9 +24,12 @@ async def input(self) -> str:
await asyncio.sleep(0.1)
text = self.input_queue.pop(0)
- self._utterances.append((time.time(), f"user: {text}"))
+ self._insert_utterance("user", text)
return text
+ async def insert_input(self, text: str):
+ self.input_queue.append(text)
+
def bot_has_spoken(self, to_set: bool = None):
if to_set != None:
self._bot_has_spoken = to_set
diff --git a/wafl/interface/voice_interface.py b/wafl/interface/voice_interface.py
index 46e53999..b3a1c2f1 100644
--- a/wafl/interface/voice_interface.py
+++ b/wafl/interface/voice_interface.py
@@ -1,12 +1,10 @@
import os
import random
import re
-import time
from wafl.events.utils import remove_text_between_brackets
-from wafl.simple_text_processing.deixis import from_bot_to_user
from wafl.interface.base_interface import BaseInterface
-from wafl.interface.utils import get_most_common_words, not_good_enough
+from wafl.interface.utils import not_good_enough
from wafl.listener.whisper_listener import WhisperListener
from wafl.speaker.fairseq_speaker import FairSeqSpeaker
from wafl.speaker.soundfile_speaker import SoundFileSpeaker
@@ -42,17 +40,6 @@ def __init__(self, config):
self._bot_has_spoken = False
self._utterances = []
- async def add_hotwords_from_knowledge(
- self, knowledge: "Knowledge", max_num_words: int = 100, count_threshold: int = 5
- ):
- hotwords = get_most_common_words(
- knowledge.get_facts_and_rule_as_text(),
- max_num_words=max_num_words,
- count_threshold=count_threshold,
- )
- hotwords = [word.lower() for word in hotwords]
- self._listener.add_hotwords(hotwords)
-
def add_hotwords(self, hotwords):
self._listener.add_hotwords(hotwords)
@@ -65,8 +52,8 @@ async def output(self, text: str, silent: bool = False):
return
self._listener.activate()
- text = from_bot_to_user(text)
- self._utterances.append((time.time(), f"bot: {text}"))
+ text = text
+ self._insert_utterance("bot", text)
print(COLOR_START + "bot> " + text + COLOR_END)
await self._speaker.speak(text)
self.bot_has_spoken(True)
@@ -89,7 +76,7 @@ async def input(self) -> str:
print(COLOR_START + "user> " + text + COLOR_END)
utterance = remove_text_between_brackets(text)
if utterance.strip():
- self._utterances.append((time.time(), f"user: {text}"))
+ self._insert_utterance("user", text)
return text
diff --git a/wafl/listener/whisper_listener.py b/wafl/listener/whisper_listener.py
index 4fbe75ff..f2b7770f 100644
--- a/wafl/listener/whisper_listener.py
+++ b/wafl/listener/whisper_listener.py
@@ -91,7 +91,12 @@ async def input(self):
while True:
await asyncio.sleep(0)
- inp = self.stream.read(self._chunk)
+ try:
+ inp = self.stream.read(self._chunk)
+ except IOError:
+ self.activate()
+ inp = self.stream.read(self._chunk)
+
rms_val = _rms(inp)
if rms_val > self._volume_threshold:
waveform = self.record(start_with=inp)
diff --git a/wafl/rules.py b/wafl/rules.py
index 3e3d32f6..fc4837cf 100644
--- a/wafl/rules.py
+++ b/wafl/rules.py
@@ -6,15 +6,24 @@
class Rule:
effect: "Fact"
causes: List["Fact"]
+ _max_indentation = 3
+ indent_str = " "
def toJSON(self):
return str(self)
+ def get_string_using_template(self, effect_template: str) -> str:
+ rule_str = effect_template.replace("{effect}", self.effect.text) + "\n"
+ return self._add_clauses(rule_str)
+
def __str__(self):
- rule_str = self.effect.text
+ rule_str = self.effect.text + "\n"
+ return self._add_clauses(rule_str)
+
+ def _add_clauses(self, rule_str: str) -> str:
for cause in self.causes:
try:
- rule_str += "\n " + cause.text
+ rule_str += self._recursively_add_clauses(cause)
except TypeError as e:
print(f"Error in rule:'''\n{rule_str}'''")
@@ -23,3 +32,22 @@ def __str__(self):
raise e
return rule_str
+
+ def _recursively_add_clauses(self, query: str, depth: int = 1) -> str:
+ indentation = self.indent_str * depth
+ if type(query) == str:
+ return f"{indentation}- {query}\n"
+
+ if type(query.text) == str:
+ return f"{indentation}- {query.text}\n"
+
+ if depth > self._max_indentation:
+ return ""
+
+ clause = list(query.text.keys())[0]
+ rules_text = f"{indentation}- {clause}\n"
+ for clauses in query.text.values():
+ for cause_index, clause in enumerate(clauses):
+ rules_text += self._recursively_add_clauses(clause, depth + 1)
+
+ return rules_text
diff --git a/wafl/run.py b/wafl/run.py
index 4f664ecb..b0397e84 100644
--- a/wafl/run.py
+++ b/wafl/run.py
@@ -52,4 +52,3 @@ def download_models():
import nltk
nltk.download("averaged_perceptron_tagger")
-
diff --git a/wafl/runners/routes.py b/wafl/runners/routes.py
index 169bfabc..78b182c0 100644
--- a/wafl/runners/routes.py
+++ b/wafl/runners/routes.py
@@ -1,22 +1,9 @@
-import asyncio
import os
-import random
-import sys
-import threading
-from flask import Flask, render_template, redirect, url_for
+from flask import Flask
from flask_cors import CORS
-from wafl.config import Configuration
-from wafl.events.conversation_events import ConversationEvents
-from wafl.interface.queue_interface import QueueInterface
-from wafl.knowledge.single_file_knowledge import SingleFileKnowledge
-from wafl.logger.local_file_logger import LocalFileLogger
-from wafl.scheduler.conversation_loop import ConversationLoop
-from wafl.scheduler.scheduler import Scheduler
-from wafl.scheduler.web_loop import WebLoop
_path = os.path.dirname(__file__)
-_logger = LocalFileLogger()
app = Flask(
__name__,
static_url_path="",
@@ -26,51 +13,11 @@
CORS(app)
-@app.route("/create_new_instance", methods=["POST"])
-def create_new_instance():
- conversation_id = random.randint(0, sys.maxsize)
- result = create_scheduler_and_webserver_loop(conversation_id)
- add_new_rules(app, conversation_id, result["web_server_loop"])
- thread = threading.Thread(target=result["scheduler"].run)
- thread.start()
- return redirect(url_for(f"index_{conversation_id}"))
-
-
-@app.route("/")
-async def index():
- return render_template("selector.html")
-
-
def get_app():
return app
-def create_scheduler_and_webserver_loop(conversation_id):
- config = Configuration.load_local_config()
- interface = QueueInterface()
- interface.activate()
- conversation_events = ConversationEvents(
- config=config,
- interface=interface,
- logger=_logger,
- )
- conversation_loop = ConversationLoop(
- interface,
- conversation_events,
- _logger,
- activation_word="",
- max_misses=-1,
- deactivate_on_closed_conversation=False,
- )
- asyncio.run(interface.output("Hello. How may I help you?"))
- web_loop = WebLoop(interface, conversation_id, conversation_events)
- return {
- "scheduler": Scheduler([conversation_loop, web_loop]),
- "web_server_loop": web_loop,
- }
-
-
-def add_new_rules(app, conversation_id, web_server_loop):
+def add_new_rules(app: Flask, conversation_id: int, web_server_loop: "WebLoop"):
app.add_url_rule(
f"/{conversation_id}/",
f"index_{conversation_id}",
@@ -125,3 +72,9 @@ def add_new_rules(app, conversation_id, web_server_loop):
web_server_loop.thumbs_down,
methods=["POST"],
)
+ app.add_url_rule(
+ f"/{conversation_id}/toggle_logs",
+ f"toggle_logs_{conversation_id}",
+ web_server_loop.toggle_logs,
+ methods=["POST"],
+ )
diff --git a/wafl/runners/run_from_actions.py b/wafl/runners/run_from_actions.py
index c4868874..4e005690 100644
--- a/wafl/runners/run_from_actions.py
+++ b/wafl/runners/run_from_actions.py
@@ -41,11 +41,11 @@ def predict_action(config, actions_list, expected_list):
raise ValueError("The agent did not say anything.")
if expected and not asyncio.run(
- entailer.left_entails_right(
- last_utterance,
- expected,
- "\n".join(interface.get_utterances_list()[:-1]),
- )
+ entailer.left_entails_right(
+ last_utterance,
+ expected,
+ "\n".join(interface.get_utterances_list()[:-1]),
+ )
):
del entailer, conversation_events, interface
raise ValueError(
diff --git a/wafl/runners/run_web_and_audio_interface.py b/wafl/runners/run_web_and_audio_interface.py
new file mode 100644
index 00000000..7f705db9
--- /dev/null
+++ b/wafl/runners/run_web_and_audio_interface.py
@@ -0,0 +1,62 @@
+import random
+import sys
+import threading
+
+from flask import render_template, redirect, url_for
+
+from wafl.interface.list_interface import ListInterface
+from wafl.interface.voice_interface import VoiceInterface
+from wafl.scheduler.scheduler import Scheduler
+from wafl.scheduler.web_loop import WebLoop
+from wafl.scheduler.conversation_loop import ConversationLoop
+from wafl.logger.local_file_logger import LocalFileLogger
+from wafl.events.conversation_events import ConversationEvents
+from wafl.interface.queue_interface import QueueInterface
+from wafl.config import Configuration
+from wafl.runners.routes import get_app, add_new_rules
+
+
+app = get_app()
+_logger = LocalFileLogger()
+
+
+def run_app():
+ @app.route("/create_new_instance", methods=["POST"])
+ def create_new_instance():
+ conversation_id = random.randint(0, sys.maxsize)
+ result = create_scheduler_and_webserver_loop(conversation_id)
+ add_new_rules(app, conversation_id, result["web_server_loop"])
+ thread = threading.Thread(target=result["scheduler"].run)
+ thread.start()
+ return redirect(url_for(f"index_{conversation_id}"))
+
+ @app.route("/")
+ async def index():
+ return render_template("selector.html")
+
+ def create_scheduler_and_webserver_loop(conversation_id):
+ config = Configuration.load_local_config()
+ interface = ListInterface([VoiceInterface(config), QueueInterface()])
+ interface.activate()
+ conversation_events = ConversationEvents(
+ config=config,
+ interface=interface,
+ logger=_logger,
+ )
+ conversation_loop = ConversationLoop(
+ interface,
+ conversation_events,
+ _logger,
+ activation_word=config.get_value("waking_up_word"),
+ )
+ web_loop = WebLoop(interface, conversation_id, conversation_events)
+ return {
+ "scheduler": Scheduler([conversation_loop, web_loop]),
+ "web_server_loop": web_loop,
+ }
+
+ app.run(host="0.0.0.0", port=Configuration.load_local_config().get_value("frontend_port"))
+
+
+if __name__ == "__main__":
+ run_app()
diff --git a/wafl/runners/run_web_interface.py b/wafl/runners/run_web_interface.py
index 35814571..ac285396 100644
--- a/wafl/runners/run_web_interface.py
+++ b/wafl/runners/run_web_interface.py
@@ -1,10 +1,63 @@
-from wafl.runners.routes import get_app
+import asyncio
+import random
+import sys
+import threading
+
+from flask import render_template, redirect, url_for
+
+from wafl.scheduler.scheduler import Scheduler
+from wafl.scheduler.web_loop import WebLoop
+from wafl.scheduler.conversation_loop import ConversationLoop
+from wafl.logger.local_file_logger import LocalFileLogger
+from wafl.events.conversation_events import ConversationEvents
+from wafl.interface.queue_interface import QueueInterface
+from wafl.config import Configuration
+from wafl.runners.routes import get_app, add_new_rules
+
app = get_app()
+_logger = LocalFileLogger()
def run_app():
- app.run(host="0.0.0.0", port=8889)
+ @app.route("/create_new_instance", methods=["POST"])
+ def create_new_instance():
+ conversation_id = random.randint(0, sys.maxsize)
+ result = create_scheduler_and_webserver_loop(conversation_id)
+ add_new_rules(app, conversation_id, result["web_server_loop"])
+ thread = threading.Thread(target=result["scheduler"].run)
+ thread.start()
+ return redirect(url_for(f"index_{conversation_id}"))
+
+ @app.route("/")
+ async def index():
+ return render_template("selector.html")
+
+ def create_scheduler_and_webserver_loop(conversation_id):
+ config = Configuration.load_local_config()
+ interface = QueueInterface()
+ interface.activate()
+ conversation_events = ConversationEvents(
+ config=config,
+ interface=interface,
+ logger=_logger,
+ )
+ conversation_loop = ConversationLoop(
+ interface,
+ conversation_events,
+ _logger,
+ activation_word="",
+ max_misses=-1,
+ deactivate_on_closed_conversation=False,
+ )
+ asyncio.run(interface.output("Hello. How may I help you?"))
+ web_loop = WebLoop(interface, conversation_id, conversation_events)
+ return {
+ "scheduler": Scheduler([conversation_loop, web_loop]),
+ "web_server_loop": web_loop,
+ }
+
+ app.run(host="0.0.0.0", port=Configuration.load_local_config().get_value("frontend_port"))
if __name__ == "__main__":
diff --git a/wafl/scheduler/messages_creator.py b/wafl/scheduler/messages_creator.py
new file mode 100644
index 00000000..ec0d85cb
--- /dev/null
+++ b/wafl/scheduler/messages_creator.py
@@ -0,0 +1,70 @@
+from wafl.scheduler.web_interface_implementation import get_html_from_dialogue_item
+
+
+class MessagesCreator:
+ def __init__(self, interface):
+ self._interface = interface
+ self._toggled_windows = []
+
+ def toggle_logs(self):
+ if "logs" in self._toggled_windows:
+ self._toggled_windows.remove("logs")
+ else:
+ self._toggled_windows.append("logs")
+
+ async def get_messages_window(self):
+ conversation = ""
+ conversation += await self._get_dialogue()
+ if "logs" in self._toggled_windows:
+ conversation += await self._get_logs()
+
+ return conversation
+
+ async def _get_dialogue(self):
+ dialogue_items = self._interface.get_utterances_list_with_timestamp()
+ dialogue = []
+ for index, item in enumerate(dialogue_items):
+ dialogue.append(
+ (
+ item[0],
+ get_html_from_dialogue_item(
+ item[1],
+ ),
+ )
+ )
+
+ dialogue_items = dialogue
+ dialogue_items = sorted(dialogue_items, key=lambda x: x[0])[::-1]
+ dialogue_items = [item[1] for item in dialogue_items]
+ conversation = (
+ ""
+ conversation += ""
+ conversation += "".join(choices_and_facts)
+ conversation += "
"
+ return conversation
diff --git a/wafl/scheduler/web_loop.py b/wafl/scheduler/web_loop.py
index f43a5a9e..2c16a416 100644
--- a/wafl/scheduler/web_loop.py
+++ b/wafl/scheduler/web_loop.py
@@ -2,11 +2,9 @@
import os
from flask import render_template, request, jsonify
-from wafl.interface.queue_interface import QueueInterface
+from wafl.interface.base_interface import BaseInterface
from wafl.logger.history_logger import HistoryLogger
-from wafl.scheduler.web_interface_implementation import (
- get_html_from_dialogue_item,
-)
+from wafl.scheduler.messages_creator import MessagesCreator
_path = os.path.dirname(__file__)
@@ -14,7 +12,7 @@
class WebLoop:
def __init__(
self,
- interface: QueueInterface,
+ interface: BaseInterface,
conversation_id: int,
conversation_events: "ConversationEvents",
):
@@ -23,13 +21,14 @@ def __init__(
self._conversation_id = conversation_id
self._conversation_events = conversation_events
self._prior_dialogue_items = ""
+ self._messages_creator = MessagesCreator(self._interface)
async def index(self):
return render_template("index.html", conversation_id=self._conversation_id)
async def handle_input(self):
query = request.form["query"]
- self._interface.input_queue.append(query)
+ await self._interface.insert_input(query)
return f"""