diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index f37eeb9..7d83ee5 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -27,7 +27,6 @@ repos:
- id: check-added-large-files
- id: detect-private-key
- id: requirements-txt-fixer
- - id: debug-statements
- repo: https://github.com/pycqa/isort
rev: 5.12.0
@@ -52,10 +51,3 @@ repos:
- id: python-check-blanket-noqa
- id: python-use-type-annotations
- id: text-unicode-replacement-char
-
- - repo: https://github.com/pre-commit/mirrors-mypy
- rev: v0.991
- hooks:
- - id: mypy
- additional_dependencies: [ types-PyYAML==6.0.10, types_requests==2.28.8, types-ujson==5.5.0 ]
- args: [ --ignore-missing-imports, --warn-no-return, --warn-redundant-casts, --disallow-incomplete-defs ]
diff --git a/.streamlit/config.toml b/.streamlit/config.toml
index d10ebc7..e8425ee 100644
--- a/.streamlit/config.toml
+++ b/.streamlit/config.toml
@@ -1,3 +1,6 @@
+[global]
+showWarningOnDirectExecution = false
+
[theme]
primaryColor = "#0E8388"
backgroundColor = "#2C3333"
diff --git a/assets/icons/rec_on.gif b/assets/icons/rec_on.gif
new file mode 100644
index 0000000..bf5c58a
Binary files /dev/null and b/assets/icons/rec_on.gif differ
diff --git a/chat.py b/chat.py
index c9390fe..a39062c 100644
--- a/chat.py
+++ b/chat.py
@@ -10,17 +10,19 @@ from src.utils.conversation import get_user_input, show_chat_buttons, show_conve
import streamlit as st
# --- PATH SETTINGS ---
-current_dir = Path(__file__).parent if "__file__" in locals() else Path.cwd()
-css_file = current_dir / "src/styles/.css"
-assets_dir = current_dir / "assets"
-icons_dir = assets_dir / "icons"
-img_dir = assets_dir / "img"
-tg_svg = icons_dir / "tg.svg"
+current_dir: Path = Path(__file__).parent if "__file__" in locals() else Path.cwd()
+css_file: Path = current_dir / "src/styles/.css"
+assets_dir: Path = current_dir / "assets"
+icons_dir: Path = assets_dir / "icons"
+img_dir: Path = assets_dir / "img"
+tg_svg: Path = icons_dir / "tg.svg"
# --- GENERAL SETTINGS ---
-PAGE_TITLE = "AI Talks"
-PAGE_ICON = "🤖"
-AI_MODEL_OPTIONS = [
+PAGE_TITLE: str = "AI Talks"
+PAGE_ICON: str = "🤖"
+LANG_EN: str = "En"
+LANG_RU: str = "Ru"
+AI_MODEL_OPTIONS: list[str] = [
"gpt-3.5-turbo",
"gpt-4",
"gpt-4-32k",
@@ -35,7 +37,7 @@ with open(css_file) as f:
selected_lang = option_menu(
menu_title=None,
- options=["En", "Ru", ],
+ options=[LANG_EN, LANG_RU, ],
icons=["globe2", "globe"],
menu_icon="cast",
default_index=0,
@@ -44,36 +46,44 @@ selected_lang = option_menu(
)
# Storing The Context
+if "locale" not in st.session_state:
+ st.session_state.locale = en
if "generated" not in st.session_state:
- st.session_state["generated"] = []
+ st.session_state.generated = []
if "past" not in st.session_state:
- st.session_state["past"] = []
+ st.session_state.past = []
if "messages" not in st.session_state:
- st.session_state["messages"] = []
+ st.session_state.messages = []
if "user_text" not in st.session_state:
- st.session_state["user_text"] = ""
+ st.session_state.user_text = ""
+if "input_kind" not in st.session_state:
+ st.session_state.input_kind = st.session_state.locale.input_kind_1
def main() -> None:
- if st.session_state.user_text:
- show_conversation()
- st.session_state.user_text = ""
-
- c1, c2, c3 = st.columns(3)
+ c1, c2 = st.columns(2)
with c1, c2:
c1.selectbox(label=st.session_state.locale.select_placeholder1, key="model", options=AI_MODEL_OPTIONS)
- role_kind = c2.radio(
+ st.session_state.input_kind = c2.radio(
+ label=st.session_state.locale.input_kind,
+ options=(st.session_state.locale.input_kind_1, st.session_state.locale.input_kind_2),
+ horizontal=True,
+ )
+ role_kind = c1.radio(
label=st.session_state.locale.radio_placeholder,
options=(st.session_state.locale.radio_text1, st.session_state.locale.radio_text2),
horizontal=True,
)
match role_kind:
case st.session_state.locale.radio_text1:
- c3.selectbox(label=st.session_state.locale.select_placeholder2, key="role",
+ c2.selectbox(label=st.session_state.locale.select_placeholder2, key="role",
options=st.session_state.locale.ai_role_options)
case st.session_state.locale.radio_text2:
- c3.text_input(label=st.session_state.locale.select_placeholder3, key="role")
+ c2.text_input(label=st.session_state.locale.select_placeholder3, key="role")
+ if st.session_state.user_text:
+ show_conversation()
+ st.session_state.user_text = ""
get_user_input()
show_chat_buttons()
@@ -85,7 +95,7 @@ if __name__ == "__main__":
case "Ru":
st.session_state.locale = ru
case _:
- locale = en
+ st.session_state.locale = en
st.markdown(f"
{st.session_state.locale.title}
", unsafe_allow_html=True)
st.markdown("---")
main()
diff --git a/requirements.txt b/requirements.txt
index b9ec276..baf82d8 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,8 +1,10 @@
-streamlit==1.20.0
+streamlit==1.21.0
streamlit-chat==0.0.2.2
streamlit_option_menu==0.3.2
-openai==0.27.2
+openai==0.27.4
gtts==2.3.1
pip==23.0.1
+bokeh==2.4.2
+streamlit-bokeh-events==0.1.2
watchdog==3.0.0
diff --git a/src/utils/agi/bard.py b/src/utils/agi/bard.py
index 7dba87a..be961c3 100644
--- a/src/utils/agi/bard.py
+++ b/src/utils/agi/bard.py
@@ -12,7 +12,7 @@ import streamlit as st
US_AG = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36"
-class Chatbot:
+class BardChat:
"""
A class to interact with Google Bard.
Parameters
diff --git a/src/utils/conversation.py b/src/utils/conversation.py
index 0fac216..211255b 100644
--- a/src/utils/conversation.py
+++ b/src/utils/conversation.py
@@ -3,20 +3,34 @@ from openai.error import InvalidRequestError, OpenAIError
from requests.exceptions import TooManyRedirects
from streamlit_chat import message
-from src.utils.agi.bard import Chatbot
+from src.utils.agi.bard import BardChat
from src.utils.agi.chat_gpt import chat_gpt_request
+from src.utils.stt import show_voice_input
from src.utils.tts import show_player
def clear_chat() -> None:
- st.session_state["generated"] = []
- st.session_state["past"] = []
- st.session_state["messages"] = []
- st.session_state["user_text"] = ""
+ st.session_state.generated = []
+ st.session_state.past = []
+ st.session_state.messages = []
+ st.session_state.user_text = ""
+
+
+def show_text_input() -> None:
+ st.text_area(label=st.session_state.locale.chat_placeholder, value=st.session_state.user_text, key="user_text")
def get_user_input():
- st.text_area(label=st.session_state.locale.chat_placeholder, value=st.session_state.user_text, key="user_text")
+ match st.session_state.input_kind:
+ case st.session_state.locale.input_kind_1:
+ clear_chat()
+ show_text_input()
+ case st.session_state.locale.input_kind_2:
+ clear_chat()
+ show_voice_input()
+ show_text_input()
+ case _:
+ show_text_input()
def show_chat_buttons() -> None:
@@ -39,7 +53,7 @@ def show_chat(ai_content: str, user_text: str) -> None:
st.session_state.generated.append(ai_content)
if st.session_state.generated:
for i in range(len(st.session_state.generated)):
- message(st.session_state["past"][i], is_user=True, key=str(i) + "_user", avatar_style="micah")
+ message(st.session_state.past[i], is_user=True, key=str(i) + "_user", avatar_style="micah")
message("", key=str(i))
st.markdown(st.session_state.generated[i])
@@ -67,7 +81,7 @@ def chat_gpt_conversation() -> None:
def bard_conversation() -> None:
try:
- bard = Chatbot(st.secrets.api_credentials.bard_session)
+ bard = BardChat(st.secrets.api_credentials.bard_session)
ai_content = bard.ask(st.session_state.user_text)
st.warning(ai_content.get("content"))
except (TooManyRedirects, AttributeError) as err:
diff --git a/src/utils/lang.py b/src/utils/lang.py
index d4ca708..f2534e1 100644
--- a/src/utils/lang.py
+++ b/src/utils/lang.py
@@ -17,6 +17,10 @@ class Locale:
chat_run_btn: str
chat_clear_btn: str
chat_save_btn: str
+ speak_btn: str
+ input_kind: str
+ input_kind_1: str
+ input_kind_2: str
select_placeholder1: str
select_placeholder2: str
select_placeholder3: str
@@ -44,9 +48,13 @@ class EnLocale(Locale):
donates1: str = "Russia"
donates2: str = "World"
chat_placeholder: str = "Start Your Conversation With AI:"
- chat_run_btn: str = "Run"
+ chat_run_btn: str = "Ask"
chat_clear_btn: str = "Clear"
chat_save_btn: str = "Save"
+ speak_btn: str = "Push to Speak"
+ input_kind: str = "Input Kind"
+ input_kind_1: str = "Text"
+ input_kind_2: str = "Voice [test mode]"
select_placeholder1: str = "Select Model"
select_placeholder2: str = "Select Role"
select_placeholder3: str = "Create Role"
@@ -83,9 +91,13 @@ class RuLocale(Locale):
donates1: str = "Россия"
donates2: str = "Остальной Мир"
chat_placeholder: str = "Начните Вашу Беседу с ИИ:"
- chat_run_btn: str = "Запустить"
+ chat_run_btn: str = "Спросить"
chat_clear_btn: str = "Очистить"
chat_save_btn: str = "Сохранить"
+ speak_btn: str = "Нажмите и Говорите"
+ input_kind: str = "Вид ввода"
+ input_kind_1: str = "Текст"
+ input_kind_2: str = "Голос [тестовый режим]"
select_placeholder1: str = "Выберите Модель"
select_placeholder2: str = "Выберите Роль"
select_placeholder3: str = "Создайте Роль"
diff --git a/src/utils/stt.py b/src/utils/stt.py
index e69de29..5cff52c 100644
--- a/src/utils/stt.py
+++ b/src/utils/stt.py
@@ -0,0 +1,83 @@
+import streamlit as st
+from bokeh.models import CustomJS
+from bokeh.models.widgets import Button
+from streamlit_bokeh_events import streamlit_bokeh_events
+
+REC_GIF = "assets/icons/rec_on.gif"
+
+
+def get_js_code(lang: str) -> str:
+ return """
+ var value = "";
+ var rand = 0;
+ var recognition = new webkitSpeechRecognition();
+ recognition.continuous = false;
+ recognition.interimResults = true;
+ """ + f"recognition.lang = '{lang}';" + """
+ document.dispatchEvent(new CustomEvent("GET_ONREC", {detail: 'start'}));
+
+ recognition.onspeechstart = function () {
+ document.dispatchEvent(new CustomEvent("GET_ONREC", {detail: 'running'}));
+ }
+ recognition.onsoundend = function () {
+ document.dispatchEvent(new CustomEvent("GET_ONREC", {detail: 'stop'}));
+ }
+ recognition.onresult = function (e) {
+ var value2 = "";
+ for (var i = e.resultIndex; i < e.results.length; ++i) {
+ if (e.results[i].isFinal) {
+ value += e.results[i][0].transcript;
+ rand = Math.random();
+
+ } else {
+ value2 += e.results[i][0].transcript;
+ }
+ }
+ document.dispatchEvent(new CustomEvent("GET_TEXT", {detail: {t:value, s:rand}}));
+ document.dispatchEvent(new CustomEvent("GET_INTRM", {detail: value2}));
+
+ }
+ recognition.onerror = function(e) {
+ document.dispatchEvent(new CustomEvent("GET_ONREC", {detail: 'stop'}));
+ }
+ recognition.start();
+ """
+
+
+def show_speak_btn() -> Button:
+ stt_button = Button(label=st.session_state.locale.speak_btn, button_type="success", width=100)
+ stt_button.js_on_event("button_click", CustomJS(code=get_js_code(st.session_state.locale.lang_code)))
+ return stt_button
+
+
+def get_bokeh_result() -> dict:
+ stt_button = show_speak_btn()
+ return streamlit_bokeh_events(
+ bokeh_plot=stt_button,
+ events="GET_TEXT,GET_ONREC,GET_INTRM",
+ key="listen",
+ refresh_on_update=False,
+ override_height=75,
+ debounce_time=0,
+ )
+
+
+def show_voice_input() -> None:
+ if "input" not in st.session_state:
+ st.session_state.input = {"text": "", "session": 0}
+ result = get_bokeh_result()
+ if result:
+ if "GET_TEXT" in result:
+ if result.get("GET_TEXT")["t"] != "" and result.get("GET_TEXT")["s"] != st.session_state.input["session"]:
+ st.session_state.input["text"] = result.get("GET_TEXT")["t"]
+ st.session_state.input["session"] = result.get("GET_TEXT")["s"]
+ if "GET_ONREC" in result:
+ placeholder = st.container()
+ if result.get("GET_ONREC") == "start":
+ placeholder.image(REC_GIF)
+ st.session_state.input["text"] = ""
+ elif result.get("GET_ONREC") == "running":
+ placeholder.image(REC_GIF)
+ elif result.get("GET_ONREC") == "stop":
+ if st.session_state.input["text"] != "":
+ st.session_state.user_text = st.session_state.input["text"]