Spaces:
Running
Running
First
Browse files- .gitignore +1 -0
- app.py +205 -0
- requirements.txt +2 -0
- src/styles/.css +52 -0
- src/styles/menu_styles.py +31 -0
- src/utils/constants.py +7 -0
- src/utils/footer.py +12 -0
- src/utils/helpers.py +17 -0
- src/utils/lang.py +104 -0
- src/utils/ui.py +70 -0
.gitignore
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
*.pyc
|
app.py
ADDED
@@ -0,0 +1,205 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from pathlib import Path
|
2 |
+
import requests
|
3 |
+
import time
|
4 |
+
from random import randrange
|
5 |
+
from PIL import Image
|
6 |
+
import cv2
|
7 |
+
import numpy as np
|
8 |
+
from io import BytesIO
|
9 |
+
|
10 |
+
import streamlit as st
|
11 |
+
from streamlit_option_menu import option_menu
|
12 |
+
|
13 |
+
from src.styles.menu_styles import FOOTER_STYLES, HEADER_STYLES
|
14 |
+
from src.utils.lang import en, vi
|
15 |
+
from src.utils.ui import message_func
|
16 |
+
from src.utils.footer import show_info
|
17 |
+
from src.utils.helpers import get_random_img, get_files_in_dir
|
18 |
+
|
19 |
+
|
20 |
+
# --- PATH SETTINGS ---
|
21 |
+
current_dir: Path = Path(__file__).parent if "__file__" in locals() else Path.cwd()
|
22 |
+
css_file: Path = current_dir / "src/styles/.css"
|
23 |
+
assets_dir: Path = current_dir / "src/assets"
|
24 |
+
img_dir: Path = assets_dir / "img"
|
25 |
+
|
26 |
+
# --- GENERAL SETTINGS ---
|
27 |
+
PAGE_TITLE: str = "VNPT Chatbot"
|
28 |
+
PAGE_ICON: str = "🤖"
|
29 |
+
LANG_EN: str = "En"
|
30 |
+
LANG_VI: str = "Vi"
|
31 |
+
AI_MODEL_OPTIONS: list[str] = [
|
32 |
+
"v.0.0.1",
|
33 |
+
]
|
34 |
+
API_ADDRESS: str = ""
|
35 |
+
|
36 |
+
st.set_page_config(page_title=PAGE_TITLE, page_icon=PAGE_ICON, layout="wide")
|
37 |
+
|
38 |
+
# --- LOAD CSS ---
|
39 |
+
with open(css_file) as f:
|
40 |
+
st.markdown(f"<style>{f.read()}</style>", unsafe_allow_html=True)
|
41 |
+
|
42 |
+
selected_lang = option_menu(
|
43 |
+
menu_title=None,
|
44 |
+
options=[LANG_EN, LANG_VI, ],
|
45 |
+
icons=["globe2", "translate"],
|
46 |
+
menu_icon="cast",
|
47 |
+
default_index=0,
|
48 |
+
orientation="horizontal",
|
49 |
+
styles=HEADER_STYLES
|
50 |
+
)
|
51 |
+
|
52 |
+
# Storing The Context
|
53 |
+
if "locale" not in st.session_state:
|
54 |
+
st.session_state.locale = en
|
55 |
+
if "seed" not in st.session_state:
|
56 |
+
st.session_state.seed = randrange(10**3)
|
57 |
+
if "user_text" not in st.session_state:
|
58 |
+
st.session_state.user_text = ""
|
59 |
+
|
60 |
+
def main(api_address,
|
61 |
+
conversation_path_param,
|
62 |
+
new_chat_path_param,
|
63 |
+
chat_append_path_param,
|
64 |
+
process_path_param,
|
65 |
+
guide_path_param):
|
66 |
+
st.sidebar.selectbox(label=st.session_state.locale.select_placeholder1, key="model", options=AI_MODEL_OPTIONS)
|
67 |
+
|
68 |
+
if st.sidebar.button(st.session_state.locale.reset_conversation):
|
69 |
+
response = requests.post(api_address + new_chat_path_param)
|
70 |
+
# Check if request was successful (status code 200)
|
71 |
+
assert response.status_code == 200, response.status_code
|
72 |
+
|
73 |
+
# Add guide
|
74 |
+
st.sidebar.write("------------------------------------")
|
75 |
+
response = requests.post(api_address + guide_path_param,
|
76 |
+
json={"language": st.session_state.locale.lang_code})
|
77 |
+
assert response.status_code == 200, response.status_code
|
78 |
+
guide_dict = response.json()["options_dict"]
|
79 |
+
guide_options = {}
|
80 |
+
option_is_selected = False
|
81 |
+
option_prompt = ""
|
82 |
+
for k, v in guide_dict.items():
|
83 |
+
guide_options[k] = st.sidebar.button(k, use_container_width=True)
|
84 |
+
if guide_options[k]:
|
85 |
+
option_is_selected = True
|
86 |
+
option_prompt = v
|
87 |
+
|
88 |
+
prompt = st.chat_input()
|
89 |
+
print(prompt)
|
90 |
+
if option_is_selected:
|
91 |
+
print("Appending ...")
|
92 |
+
response = requests.post(api_address + chat_append_path_param,
|
93 |
+
json={"role": "user", "content": option_prompt})
|
94 |
+
assert response.status_code == 200, response.status_code
|
95 |
+
if prompt:
|
96 |
+
print("Appending ...")
|
97 |
+
response = requests.post(api_address + chat_append_path_param,
|
98 |
+
json={"role": "user", "content": prompt})
|
99 |
+
assert response.status_code == 200, response.status_code
|
100 |
+
|
101 |
+
# Visual conversation
|
102 |
+
response = requests.get(api_address + conversation_path_param,
|
103 |
+
json={"language": st.session_state.locale.lang_code})
|
104 |
+
assert response.status_code == 200, response.status_code
|
105 |
+
messages = response.json()["conversation"]
|
106 |
+
for message in messages:
|
107 |
+
message_func(
|
108 |
+
message["content"],
|
109 |
+
role = message["role"],
|
110 |
+
)
|
111 |
+
urls = message.get("urls", [])
|
112 |
+
titles = message.get("titles", [])
|
113 |
+
|
114 |
+
if len(urls) and not isinstance(urls[0], list):
|
115 |
+
urls = [[url] for url in urls]
|
116 |
+
if len(titles) and not isinstance(titles[0], list):
|
117 |
+
titles = [[title] for title in titles]
|
118 |
+
|
119 |
+
for sub_urls, sub_titles in zip(urls, titles):
|
120 |
+
display_images(sub_urls, sub_titles)
|
121 |
+
|
122 |
+
# for url, title in zip(urls, titles):
|
123 |
+
# display_image_with_title_and_url(title, url)
|
124 |
+
|
125 |
+
# Process the conversation if prompt
|
126 |
+
if prompt or option_is_selected:
|
127 |
+
print("Processing ...")
|
128 |
+
start = time.time()
|
129 |
+
response = requests.post(api_address + process_path_param,
|
130 |
+
json={"language": st.session_state.locale.lang_code})
|
131 |
+
assert response.status_code == 200, response.status_code
|
132 |
+
logs = response.json()["logs"]
|
133 |
+
for data in logs:
|
134 |
+
urls = data["urls"]
|
135 |
+
titles = data["titles"]
|
136 |
+
end = time.time()
|
137 |
+
print("done")
|
138 |
+
print(data)
|
139 |
+
print(end - start)
|
140 |
+
message_func(
|
141 |
+
data["content"],
|
142 |
+
role=data["role"]
|
143 |
+
)
|
144 |
+
for url, title in zip(urls, titles):
|
145 |
+
display_image_with_title_and_url(title, url)
|
146 |
+
message_func(
|
147 |
+
"",
|
148 |
+
role="assistant"
|
149 |
+
)
|
150 |
+
|
151 |
+
# if st.session_state.user_text:
|
152 |
+
# show_conversation()
|
153 |
+
# st.session_state.user_text = ""
|
154 |
+
# get_user_input()
|
155 |
+
# show_chat_buttons()
|
156 |
+
|
157 |
+
def run_agi():
|
158 |
+
global API_ADDRESS
|
159 |
+
print(API_ADDRESS)
|
160 |
+
st.sidebar.text_input("API address", key="api_address")
|
161 |
+
api_address = st.session_state.api_address
|
162 |
+
##TODO: Dynamic this
|
163 |
+
conversation_path_param = "/chatbot/"
|
164 |
+
new_chat_path_param = "/chatbot/new_chat"
|
165 |
+
chat_append_path_param = "/chatbot/append_text_from_user"
|
166 |
+
process_path_param = "/chatbot/process"
|
167 |
+
guide_path_param = "/chatbot/guide_options"
|
168 |
+
|
169 |
+
match selected_lang:
|
170 |
+
case "En":
|
171 |
+
st.session_state.locale = en
|
172 |
+
case "Vi":
|
173 |
+
st.session_state.locale = vi
|
174 |
+
case _:
|
175 |
+
st.session_state.locale = en
|
176 |
+
st.markdown(f"<h1 style='text-align: center;'>{st.session_state.locale.title}</h1>", unsafe_allow_html=True)
|
177 |
+
selected_footer = option_menu(
|
178 |
+
menu_title=None,
|
179 |
+
options=[
|
180 |
+
st.session_state.locale.footer_option1,
|
181 |
+
st.session_state.locale.footer_option0,
|
182 |
+
],
|
183 |
+
icons=["info-circle", "chat-square-text"], # https://icons.getbootstrap.com/
|
184 |
+
menu_icon="cast",
|
185 |
+
default_index=0,
|
186 |
+
orientation="horizontal",
|
187 |
+
styles=FOOTER_STYLES
|
188 |
+
)
|
189 |
+
|
190 |
+
match selected_footer:
|
191 |
+
case st.session_state.locale.footer_option0:
|
192 |
+
main(api_address,
|
193 |
+
conversation_path_param,
|
194 |
+
new_chat_path_param,
|
195 |
+
chat_append_path_param,
|
196 |
+
process_path_param,
|
197 |
+
guide_path_param)
|
198 |
+
case st.session_state.locale.footer_option1:
|
199 |
+
show_info()
|
200 |
+
case _:
|
201 |
+
show_info()
|
202 |
+
|
203 |
+
|
204 |
+
if __name__ == "__main__":
|
205 |
+
run_agi()
|
requirements.txt
ADDED
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
1 |
+
numpy
|
2 |
+
opencv-python
|
src/styles/.css
ADDED
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
a {
|
2 |
+
text-decoration: none;
|
3 |
+
font-weight: 500;
|
4 |
+
}
|
5 |
+
|
6 |
+
.block-container {
|
7 |
+
padding: 0 16px 96px 16px;
|
8 |
+
}
|
9 |
+
|
10 |
+
a:hover {
|
11 |
+
text-decoration: none;
|
12 |
+
color: #d33682 !important;
|
13 |
+
}
|
14 |
+
|
15 |
+
ul {
|
16 |
+
list-style-type: none;
|
17 |
+
}
|
18 |
+
|
19 |
+
hr {
|
20 |
+
margin-top: 0;
|
21 |
+
margin-bottom: 5%;
|
22 |
+
}
|
23 |
+
|
24 |
+
#MainMenu {
|
25 |
+
visibility: hidden;
|
26 |
+
}
|
27 |
+
|
28 |
+
footer {
|
29 |
+
visibility: hidden;
|
30 |
+
}
|
31 |
+
|
32 |
+
header {
|
33 |
+
visibility: hidden;
|
34 |
+
}
|
35 |
+
|
36 |
+
audio::-webkit-media-controls-panel,
|
37 |
+
audio::-webkit-media-controls-enclosure {
|
38 |
+
background-color: #326464;
|
39 |
+
max-height: 30px;
|
40 |
+
border-radius: 4px;
|
41 |
+
}
|
42 |
+
|
43 |
+
audio::-webkit-media-controls-time-remaining-display,
|
44 |
+
audio::-webkit-media-controls-current-time-display {
|
45 |
+
color: #CBE4DE;
|
46 |
+
text-shadow: none;
|
47 |
+
}
|
48 |
+
|
49 |
+
audio::-webkit-media-controls-timeline {
|
50 |
+
border-radius: 8px;
|
51 |
+
margin: 10px;
|
52 |
+
}
|
src/styles/menu_styles.py
ADDED
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from typing import Dict
|
2 |
+
|
3 |
+
HEADER_STYLES: Dict[str, Dict] = {
|
4 |
+
"container": {
|
5 |
+
"padding": "0px",
|
6 |
+
"display": "grid",
|
7 |
+
"margin": "0!important",
|
8 |
+
"background-color": "#2C3333"
|
9 |
+
},
|
10 |
+
"icon": {"color": "#CBE4DE", "font-size": "14px"},
|
11 |
+
"nav-link": {
|
12 |
+
"font-size": "14px",
|
13 |
+
"text-align": "center",
|
14 |
+
"margin": "auto",
|
15 |
+
"background-color": "#2C3333",
|
16 |
+
"height": "30px",
|
17 |
+
"width": "7rem",
|
18 |
+
"color": "#CBE4DE",
|
19 |
+
"border-radius": "5px"
|
20 |
+
},
|
21 |
+
"nav-link-selected": {
|
22 |
+
"background-color": "#2E4F4F",
|
23 |
+
"font-weight": "300",
|
24 |
+
"color": "#f5f5f5",
|
25 |
+
"border": "1px solid #0E8388"
|
26 |
+
}
|
27 |
+
}
|
28 |
+
|
29 |
+
FOOTER_STYLES: Dict[str, Dict] = {
|
30 |
+
|
31 |
+
}
|
src/utils/constants.py
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
AI_ROLE_OPTIONS_EN: list[str] = [
|
2 |
+
"fashion assistant",
|
3 |
+
]
|
4 |
+
|
5 |
+
AI_ROLE_OPTIONS_VI: list[str] = [
|
6 |
+
"trợ lý thời trang ảo",
|
7 |
+
]
|
src/utils/footer.py
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from pathlib import Path
|
2 |
+
|
3 |
+
import streamlit as st
|
4 |
+
|
5 |
+
|
6 |
+
def show_info():
|
7 |
+
st.divider()
|
8 |
+
st.markdown(f"<div style='text-align: justify;'>{st.session_state.locale.product_information}</div>",
|
9 |
+
unsafe_allow_html=True)
|
10 |
+
st.divider()
|
11 |
+
st.markdown("""[**Contact us**](https://www.linkedin.com/in/manh-do-quang-50823a255/)""")
|
12 |
+
st.markdown("""[**Buy Me A Coffee**](https://www.buymeacoffee.com/doquangmanp)""")
|
src/utils/helpers.py
ADDED
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import base64
|
2 |
+
import os
|
3 |
+
import random
|
4 |
+
from pathlib import Path
|
5 |
+
from typing import List
|
6 |
+
|
7 |
+
|
8 |
+
def get_files_in_dir(path: Path) -> List[str]:
|
9 |
+
files = []
|
10 |
+
for file in os.listdir(path):
|
11 |
+
if os.path.isfile(os.path.join(path, file)):
|
12 |
+
files.append(file)
|
13 |
+
return files
|
14 |
+
|
15 |
+
|
16 |
+
def get_random_img(img_names: List[str]) -> str:
|
17 |
+
return random.choice(img_names)
|
src/utils/lang.py
ADDED
@@ -0,0 +1,104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from dataclasses import dataclass
|
2 |
+
from typing import List # NOQA: UP035
|
3 |
+
|
4 |
+
from .constants import AI_ROLE_OPTIONS_EN, AI_ROLE_OPTIONS_VI
|
5 |
+
|
6 |
+
|
7 |
+
@dataclass
|
8 |
+
class Locale:
|
9 |
+
ai_role_options: List[str]
|
10 |
+
ai_role_prefix: str
|
11 |
+
ai_role_postfix: str
|
12 |
+
title: str
|
13 |
+
language: str
|
14 |
+
lang_code: str
|
15 |
+
chat_placeholder: str
|
16 |
+
chat_run_btn: str
|
17 |
+
chat_clear_btn: str
|
18 |
+
chat_save_btn: str
|
19 |
+
fill_api_address: str
|
20 |
+
select_placeholder1: str
|
21 |
+
select_placeholder2: str
|
22 |
+
select_placeholder3: str
|
23 |
+
radio_placeholder: str
|
24 |
+
radio_text1: str
|
25 |
+
radio_text2: str
|
26 |
+
reset_conversation: str
|
27 |
+
stt_placeholder: str
|
28 |
+
footer_title: str
|
29 |
+
footer_option0: str
|
30 |
+
footer_option1: str
|
31 |
+
footer_chat: str
|
32 |
+
footer_channel: str
|
33 |
+
product_information: str
|
34 |
+
tokens_count: str
|
35 |
+
message_cost: str
|
36 |
+
total_cost: str
|
37 |
+
|
38 |
+
|
39 |
+
# --- LOCALE SETTINGS ---
|
40 |
+
en = Locale(
|
41 |
+
ai_role_options=AI_ROLE_OPTIONS_EN,
|
42 |
+
ai_role_prefix="You are a female",
|
43 |
+
ai_role_postfix="Answer as concisely as possible.",
|
44 |
+
title="HangerAI Chatbot Assistant",
|
45 |
+
language="English",
|
46 |
+
lang_code="en",
|
47 |
+
chat_placeholder="Start Your Conversation With AI:",
|
48 |
+
chat_run_btn="Ask",
|
49 |
+
chat_clear_btn="Clear",
|
50 |
+
chat_save_btn="Save",
|
51 |
+
fill_api_address="Enter api address",
|
52 |
+
select_placeholder1="Select Model",
|
53 |
+
select_placeholder2="Select Role",
|
54 |
+
select_placeholder3="Create Role",
|
55 |
+
radio_placeholder="Role Interaction",
|
56 |
+
radio_text1="Select",
|
57 |
+
radio_text2="Create",
|
58 |
+
reset_conversation="New chat",
|
59 |
+
stt_placeholder="To Hear The Voice Of AI Press Play",
|
60 |
+
footer_title="Support & Feedback",
|
61 |
+
footer_option0="Chat",
|
62 |
+
footer_option1="Info",
|
63 |
+
footer_chat="AI Talks Chat",
|
64 |
+
footer_channel="AI Talks Channel",
|
65 |
+
product_information="""HangerChatbot tailored for the retail industry, specifically focusing on the fashion sector. Our chatbot is designed to provide exceptional assistance to customers in their product selection, exploration, and search. But that's not all – our product goes a step further by offering personalized clothing recommendations based on user interactions, preferences, history, and inquiries.\n
|
66 |
+
Powered by cutting-edge and state-of-the-art artificial intelligence technologies, our chatbot is here to revolutionize the way customers engage with your fashion brand. Whether it's helping customers find the perfect outfit, suggesting the latest trends, or answering their fashion-related questions, our chatbot is the ultimate solution to enhance the shopping experience.\n
|
67 |
+
Don't miss out on this opportunity to take your retail business to the next level with our advanced and customer-centric chatbot solution. Experience the future of retail with our innovative product today!""",
|
68 |
+
tokens_count="Tokens count: ",
|
69 |
+
message_cost="Message cost: ",
|
70 |
+
total_cost="Total cost of conversation: ",
|
71 |
+
)
|
72 |
+
|
73 |
+
vi = Locale(
|
74 |
+
ai_role_options=AI_ROLE_OPTIONS_VI,
|
75 |
+
ai_role_prefix="You are a female",
|
76 |
+
ai_role_postfix="Answer as concisely as possible.",
|
77 |
+
title="Trợ lý ảo Hanger",
|
78 |
+
language="Tiếng Việt",
|
79 |
+
lang_code="vi",
|
80 |
+
chat_placeholder="Start Your Conversation With AI:",
|
81 |
+
chat_run_btn="Ask",
|
82 |
+
chat_clear_btn="Clear",
|
83 |
+
chat_save_btn="Save",
|
84 |
+
fill_api_address="Nhập địa chỉ API",
|
85 |
+
select_placeholder1="Chọn mô hình",
|
86 |
+
select_placeholder2="Lựa chọn vai trò",
|
87 |
+
select_placeholder3="Tạo vai trò",
|
88 |
+
radio_placeholder="Lựa chọn phiên bản của trợ lý ảo",
|
89 |
+
radio_text1="Lựa chọn",
|
90 |
+
radio_text2="Tạo mới",
|
91 |
+
reset_conversation="Hội thoại mới",
|
92 |
+
stt_placeholder="To Hear The Voice Of AI Press Play",
|
93 |
+
footer_title="Support & Feedback",
|
94 |
+
footer_option0="Nói chuyện",
|
95 |
+
footer_option1="Thông tin",
|
96 |
+
footer_chat="AI Talks Chat",
|
97 |
+
footer_channel="AI Talks Channel",
|
98 |
+
product_information="""HangerChatbot được tùy chỉnh dành cho ngành bán lẻ, đặc biệt là tập trung vào lĩnh vực thời trang. Chatbot của chúng tôi được thiết kế để cung cấp sự hỗ trợ xuất sắc cho khách hàng trong việc lựa chọn sản phẩm, khám phá và tìm kiếm. Nhưng điều đó chưa đủ - sản phẩm của chúng tôi còn đi xa hơn bằng cách cung cấp các gợi ý về trang phục cá nhân dựa trên tương tác của người dùng, sở thích, lịch sử và câu hỏi.\n
|
99 |
+
Được trang bị bởi các công nghệ trí tuệ nhân tạo tiên tiến và hiện đại, chatbot của chúng tôi sẽ cách mạng hóa cách khách hàng tương tác với thương hiệu thời trang của bạn. Cho dù đó là giúp khách hàng tìm trang phục hoàn hảo, đề xuất các xu hướng mới nhất hoặc trả lời các câu hỏi liên quan đến thời trang, chatbot của chúng tôi là giải pháp tối ưu để nâng cao trải nghiệm mua sắm.\n
|
100 |
+
Đừng bỏ lỡ cơ hội này để đưa doanh nghiệp bán lẻ của bạn lên tầm cao mới với giải pháp chatbot tiên tiến và tập trung vào khách hàng của chúng tôi. Trải nghiệm tương lai của ngành bán lẻ với sản phẩm độc đáo của chúng tôi ngay hôm nay!""",
|
101 |
+
tokens_count="Tokens count: ",
|
102 |
+
message_cost="Message cost: ",
|
103 |
+
total_cost="Total cost of conversation: ",
|
104 |
+
)
|
src/utils/ui.py
ADDED
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import html
|
2 |
+
import re
|
3 |
+
import streamlit as st
|
4 |
+
|
5 |
+
|
6 |
+
def format_message(text):
|
7 |
+
"""
|
8 |
+
This function is used to format the messages in the chatbot UI.
|
9 |
+
|
10 |
+
Parameters:
|
11 |
+
text (str): The text to be formatted.
|
12 |
+
"""
|
13 |
+
text_blocks = re.split(r"```[\s\S]*?```", text)
|
14 |
+
code_blocks = re.findall(r"```([\s\S]*?)```", text)
|
15 |
+
|
16 |
+
text_blocks = [html.escape(block) for block in text_blocks]
|
17 |
+
|
18 |
+
formatted_text = ""
|
19 |
+
for i in range(len(text_blocks)):
|
20 |
+
formatted_text += text_blocks[i].replace("\n", "<br>")
|
21 |
+
if i < len(code_blocks):
|
22 |
+
formatted_text += f'<pre style="white-space: pre-wrap; word-wrap: break-word;"><code>{html.escape(code_blocks[i])}</code></pre>'
|
23 |
+
|
24 |
+
return formatted_text
|
25 |
+
|
26 |
+
|
27 |
+
def message_func(text, role):
|
28 |
+
"""
|
29 |
+
This function is used to display the messages in the chatbot UI.
|
30 |
+
|
31 |
+
Parameters:
|
32 |
+
text (str): The text to be displayed.
|
33 |
+
role (str): Role for the text.
|
34 |
+
"""
|
35 |
+
if role == "user":
|
36 |
+
avatar_url = "https://avataaars.io/?avatarStyle=Transparent&topType=ShortHairShortFlat&accessoriesType=Prescription01&hairColor=Auburn&facialHairType=BeardLight&facialHairColor=Black&clotheType=Hoodie&clotheColor=PastelBlue&eyeType=Squint&eyebrowType=DefaultNatural&mouthType=Smile&skinColor=Tanned"
|
37 |
+
message_alignment = "flex-end"
|
38 |
+
message_bg_color = "linear-gradient(135deg, #00B2FF 0%, #006AFF 100%)"
|
39 |
+
avatar_class = "user-avatar"
|
40 |
+
st.write(
|
41 |
+
f"""
|
42 |
+
<div style="display: flex; align-items: center; margin-bottom: 10px; justify-content: {message_alignment};">
|
43 |
+
<div style="background: {message_bg_color}; color: white; border-radius: 20px; padding: 10px; margin-right: 5px; max-width: 75%; font-size: 14px;">
|
44 |
+
{text} \n </div>
|
45 |
+
<img src="{avatar_url}" class="{avatar_class}" alt="avatar" style="width: 50px; height: 50px;" />
|
46 |
+
</div>
|
47 |
+
""",
|
48 |
+
unsafe_allow_html=True,
|
49 |
+
)
|
50 |
+
elif role == "assistant":
|
51 |
+
avatar_url = "https://avataaars.io/?avatarStyle=Transparent&topType=WinterHat2&accessoriesType=Kurt&hatColor=Blue01&facialHairType=MoustacheMagnum&facialHairColor=Blonde&clotheType=Overall&clotheColor=Gray01&eyeType=WinkWacky&eyebrowType=SadConcernedNatural&mouthType=Sad&skinColor=Light"
|
52 |
+
message_alignment = "flex-start"
|
53 |
+
message_bg_color = "#71797E"
|
54 |
+
avatar_class = "bot-avatar"
|
55 |
+
|
56 |
+
text = format_message(text)
|
57 |
+
|
58 |
+
st.write(
|
59 |
+
f"""
|
60 |
+
<div style="display: flex; align-items: center; margin-bottom: 10px; justify-content: {message_alignment};">
|
61 |
+
<img src="{avatar_url}" class="{avatar_class}" alt="avatar" style="width: 50px; height: 50px;" />
|
62 |
+
<div style="background: {message_bg_color}; color: white; border-radius: 20px; padding: 10px; margin-right: 5px; max-width: 75%; font-size: 14px;">
|
63 |
+
{text} \n </div>
|
64 |
+
</div>
|
65 |
+
""",
|
66 |
+
unsafe_allow_html=True,
|
67 |
+
)
|
68 |
+
|
69 |
+
else:
|
70 |
+
raise
|