IAMTFRMZA commited on
Commit
e6c3180
·
verified ·
1 Parent(s): 16745ff

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +84 -55
app.py CHANGED
@@ -3,7 +3,6 @@ import asyncio
3
  import edge_tts
4
  import time
5
  import os
6
- import re
7
  import uuid
8
  import firebase_admin
9
  from firebase_admin import credentials, firestore
@@ -20,26 +19,36 @@ openai_key = os.getenv("openai_key")
20
  assistant_id = os.getenv("assistant_id")
21
  client = OpenAI(api_key=openai_key)
22
 
23
- # ---- Voice Settings ----
24
- FIXED_VOICE_NAME = "Jenny (US, Female)"
25
- FIXED_VOICE = "en-US-JennyNeural"
 
 
 
 
 
 
 
 
 
26
 
27
  # --- State setup
28
  if "user_id" not in st.session_state:
29
  st.session_state["user_id"] = str(uuid.uuid4())
30
  user_id = st.session_state["user_id"]
31
 
 
 
32
  if "last_tts_text" not in st.session_state:
33
  st.session_state["last_tts_text"] = ""
34
  if "last_audio_path" not in st.session_state:
35
  st.session_state["last_audio_path"] = ""
 
 
36
  if "is_thinking" not in st.session_state:
37
  st.session_state["is_thinking"] = False
38
 
39
- # --- Page config ---
40
- st.set_page_config(page_title="LOR Technologies AI Assistant", layout="wide")
41
-
42
- # --- CSS Styling ---
43
  st.markdown("""
44
  <style>
45
  .block-container {padding-top: 1rem;}
@@ -57,19 +66,24 @@ st.markdown("""
57
  .stChatMessage[data-testid="stChatMessage-user"] { background: #f0f0f0; color: #000000; }
58
  .stChatMessage[data-testid="stChatMessage-assistant"] { background: #e3f2fd; color: #000000; }
59
  .chat-history-wrapper {
60
- margin-top: 0.5em; margin-bottom: 5em; height: 65vh; overflow-y: auto; padding: 0 0.5em;
 
 
61
  }
62
  .chat-input-bar {
63
  position: fixed; bottom: 0; width: 100%; z-index: 100;
64
  background: #191b22; padding: 0.6em 1em; border-top: 1px solid #22232c;
65
- display: flex; align-items: center; gap: 0.5em;
66
  }
67
  .chat-input-bar input { width: 100%; font-size: 1.1em; }
68
- .clear-chat-btn { background: none; border: none; font-size: 1.4em; color: #999; cursor: pointer; }
 
 
 
69
  </style>
70
  """, unsafe_allow_html=True)
71
 
72
- # --- Top Branding ---
73
  st.markdown("""
74
  <div class="lor-brand-bar">
75
  <img src="https://lortechnologies.com/wp-content/uploads/2023/03/LOR-Online-Logo.svg" class="logo-mini" />
@@ -77,6 +91,30 @@ st.markdown("""
77
  </div>
78
  """, unsafe_allow_html=True)
79
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  # --- Firestore helpers ---
81
  def get_or_create_thread_id():
82
  doc_ref = db.collection("users").document(user_id)
@@ -119,34 +157,25 @@ def display_chat_history():
119
  )
120
  st.markdown('<div class="chat-history-wrapper">' + "".join(chat_msgs) + '</div>', unsafe_allow_html=True)
121
 
122
- # --- Edge TTS synth ---
123
- def sanitize_tts_text(text):
124
- text = re.sub(r'[^\w\s\.\,\!\?\:\;\'\"]', '', text) # keep basic punctuation
125
- text = text.replace('.co.za', 'dot coza')
126
- return text
127
-
128
  async def edge_tts_synthesize(text, voice, user_id):
129
  out_path = f"output_{user_id}.mp3"
130
  communicate = edge_tts.Communicate(text, voice)
131
  await communicate.save(out_path)
132
  return out_path
133
 
134
- def synthesize_voice(text, user_id):
135
- voice = FIXED_VOICE
136
- sanitized = sanitize_tts_text(text)
137
  out_path = f"output_{user_id}.mp3"
138
- if st.session_state["last_tts_text"] != sanitized or not os.path.exists(out_path):
139
- try:
140
- with st.spinner(f"Generating voice ({FIXED_VOICE_NAME})..."):
141
- asyncio.run(edge_tts_synthesize(sanitized, voice, user_id))
142
- st.session_state["last_tts_text"] = sanitized
143
- st.session_state["last_audio_path"] = out_path
144
- except Exception as e:
145
- st.warning(f"TTS Error: {e}")
146
- return None
147
  return out_path
148
 
149
- # --- CHAT: display history ---
150
  display_chat_history()
151
 
152
  # --- LORAIN is thinking indicator ---
@@ -154,12 +183,12 @@ if st.session_state.get("is_thinking", False):
154
  st.markdown("""
155
  <div style="
156
  text-align:center; color:#ddd; font-size: 14px;
157
- margin-top: -1em; margin-bottom: 0.5em;">
158
  🤖 <em>LORAIN is thinking...</em>
159
  </div>
160
  """, unsafe_allow_html=True)
161
 
162
- # --- INPUT BAR (floating at bottom) ---
163
  st.markdown('<div class="chat-input-bar">', unsafe_allow_html=True)
164
  col1, col2 = st.columns([10, 1])
165
  user_input = col1.chat_input("Type your message here...")
@@ -167,10 +196,23 @@ if col2.button("🗑️", help="Clear Chat", key="clear-chat-bottom"):
167
  clear_chat_history()
168
  st.markdown('</div>', unsafe_allow_html=True)
169
 
170
- # --- PROCESS USER INPUT ---
171
- if user_input:
172
- st.session_state["is_thinking"] = True # start thinking
 
 
 
 
 
 
 
 
 
 
173
 
 
 
 
174
  thread_id = get_or_create_thread_id()
175
  client.beta.threads.messages.create(thread_id=thread_id, role="user", content=user_input)
176
  save_message("user", user_input)
@@ -188,25 +230,12 @@ if user_input:
188
  assistant_message = latest_response.content[0].text.value
189
  save_message("assistant", assistant_message)
190
 
191
- audio_path = synthesize_voice(assistant_message, user_id)
192
- if audio_path and os.path.exists(audio_path):
193
- st.audio(audio_path, format="audio/mp3", autoplay=True)
194
-
195
- st.session_state["is_thinking"] = False # stop thinking
196
 
 
197
  time.sleep(0.2)
198
  st.rerun()
199
-
200
- # --- Auto-scroll JS ---
201
- st.markdown("""
202
- <script>
203
- window.onload = function() {
204
- var chatWrapper = document.querySelector('.chat-history-wrapper');
205
- if(chatWrapper){ chatWrapper.scrollTop = chatWrapper.scrollHeight; }
206
- };
207
- setTimeout(function(){
208
- var chatWrapper = document.querySelector('.chat-history-wrapper');
209
- if(chatWrapper){ chatWrapper.scrollTop = chatWrapper.scrollHeight; }
210
- }, 300);
211
- </script>
212
- """, unsafe_allow_html=True)
 
3
  import edge_tts
4
  import time
5
  import os
 
6
  import uuid
7
  import firebase_admin
8
  from firebase_admin import credentials, firestore
 
19
  assistant_id = os.getenv("assistant_id")
20
  client = OpenAI(api_key=openai_key)
21
 
22
+ VOICE_OPTIONS = {
23
+ "Jenny (US, Female)": "en-US-JennyNeural",
24
+ "Aria (US, Female)": "en-US-AriaNeural",
25
+ "Ryan (UK, Male)": "en-GB-RyanNeural",
26
+ "Natasha (AU, Female)": "en-AU-NatashaNeural",
27
+ "William (AU, Male)": "en-AU-WilliamNeural",
28
+ "Libby (UK, Female)": "en-GB-LibbyNeural",
29
+ "Leah (SA, Female)": "en-ZA-LeahNeural",
30
+ "Luke (SA, Male)": "en-ZA-LukeNeural"
31
+ }
32
+
33
+ st.set_page_config(page_title="LOR Technologies AI Assistant", layout="wide")
34
 
35
  # --- State setup
36
  if "user_id" not in st.session_state:
37
  st.session_state["user_id"] = str(uuid.uuid4())
38
  user_id = st.session_state["user_id"]
39
 
40
+ if "mute_voice" not in st.session_state:
41
+ st.session_state["mute_voice"] = False
42
  if "last_tts_text" not in st.session_state:
43
  st.session_state["last_tts_text"] = ""
44
  if "last_audio_path" not in st.session_state:
45
  st.session_state["last_audio_path"] = ""
46
+ if "selected_voice" not in st.session_state:
47
+ st.session_state["selected_voice"] = "Jenny (US, Female)"
48
  if "is_thinking" not in st.session_state:
49
  st.session_state["is_thinking"] = False
50
 
51
+ # --- CSS ---
 
 
 
52
  st.markdown("""
53
  <style>
54
  .block-container {padding-top: 1rem;}
 
66
  .stChatMessage[data-testid="stChatMessage-user"] { background: #f0f0f0; color: #000000; }
67
  .stChatMessage[data-testid="stChatMessage-assistant"] { background: #e3f2fd; color: #000000; }
68
  .chat-history-wrapper {
69
+ display: flex; flex-direction: column; justify-content: flex-end;
70
+ height: 65vh; overflow-y: auto;
71
+ margin-bottom: 6em; padding: 0 0.5em;
72
  }
73
  .chat-input-bar {
74
  position: fixed; bottom: 0; width: 100%; z-index: 100;
75
  background: #191b22; padding: 0.6em 1em; border-top: 1px solid #22232c;
76
+ display: flex; align-items: center; gap: 0.5em; flex-wrap: nowrap;
77
  }
78
  .chat-input-bar input { width: 100%; font-size: 1.1em; }
79
+ .clear-chat-btn {
80
+ flex: 0 0 auto;
81
+ background: none; border: none; font-size: 1.4em; color: #999; cursor: pointer;
82
+ }
83
  </style>
84
  """, unsafe_allow_html=True)
85
 
86
+ # --- Branding ---
87
  st.markdown("""
88
  <div class="lor-brand-bar">
89
  <img src="https://lortechnologies.com/wp-content/uploads/2023/03/LOR-Online-Logo.svg" class="logo-mini" />
 
91
  </div>
92
  """, unsafe_allow_html=True)
93
 
94
+ # --- Sidebar: audio/voice controls
95
+ with st.sidebar:
96
+ st.markdown("### Voice Settings & Controls")
97
+ selected_voice = st.selectbox(
98
+ "Select assistant voice", list(VOICE_OPTIONS.keys()),
99
+ index=list(VOICE_OPTIONS.keys()).index(st.session_state["selected_voice"])
100
+ )
101
+ st.session_state["selected_voice"] = selected_voice
102
+
103
+ last_audio = st.session_state.get("last_audio_path")
104
+ mute_voice = st.session_state.get("mute_voice", False)
105
+ if last_audio and os.path.exists(last_audio):
106
+ st.audio(last_audio, format="audio/mp3", autoplay=not mute_voice)
107
+ if st.button("🔁 Replay Voice"):
108
+ st.audio(last_audio, format="audio/mp3", autoplay=True)
109
+ if not mute_voice:
110
+ if st.button("🔇 Mute Voice"):
111
+ st.session_state["mute_voice"] = True
112
+ st.rerun()
113
+ else:
114
+ if st.button("🔊 Unmute Voice"):
115
+ st.session_state["mute_voice"] = False
116
+ st.rerun()
117
+
118
  # --- Firestore helpers ---
119
  def get_or_create_thread_id():
120
  doc_ref = db.collection("users").document(user_id)
 
157
  )
158
  st.markdown('<div class="chat-history-wrapper">' + "".join(chat_msgs) + '</div>', unsafe_allow_html=True)
159
 
160
+ # --- Edge TTS ---
 
 
 
 
 
161
  async def edge_tts_synthesize(text, voice, user_id):
162
  out_path = f"output_{user_id}.mp3"
163
  communicate = edge_tts.Communicate(text, voice)
164
  await communicate.save(out_path)
165
  return out_path
166
 
167
+ def synthesize_voice(text, voice_key, user_id):
168
+ voice = VOICE_OPTIONS[voice_key]
 
169
  out_path = f"output_{user_id}.mp3"
170
+ if st.session_state["last_tts_text"] != text or not os.path.exists(out_path) or st.session_state.get("last_voice") != voice:
171
+ with st.spinner(f"Generating voice ({voice_key})..."):
172
+ asyncio.run(edge_tts_synthesize(text, voice, user_id))
173
+ st.session_state["last_tts_text"] = text
174
+ st.session_state["last_audio_path"] = out_path
175
+ st.session_state["last_voice"] = voice
 
 
 
176
  return out_path
177
 
178
+ # --- Show chat history ---
179
  display_chat_history()
180
 
181
  # --- LORAIN is thinking indicator ---
 
183
  st.markdown("""
184
  <div style="
185
  text-align:center; color:#ddd; font-size: 14px;
186
+ margin-top: 0.5em; margin-bottom: 0.5em;">
187
  🤖 <em>LORAIN is thinking...</em>
188
  </div>
189
  """, unsafe_allow_html=True)
190
 
191
+ # --- Input bar (floating at bottom) ---
192
  st.markdown('<div class="chat-input-bar">', unsafe_allow_html=True)
193
  col1, col2 = st.columns([10, 1])
194
  user_input = col1.chat_input("Type your message here...")
 
196
  clear_chat_history()
197
  st.markdown('</div>', unsafe_allow_html=True)
198
 
199
+ # --- Auto-scroll JS ---
200
+ st.markdown("""
201
+ <script>
202
+ window.onload = function() {
203
+ var chatWrapper = document.querySelector('.chat-history-wrapper');
204
+ if(chatWrapper){ chatWrapper.scrollTop = chatWrapper.scrollHeight; }
205
+ };
206
+ setTimeout(function(){
207
+ var chatWrapper = document.querySelector('.chat-history-wrapper');
208
+ if(chatWrapper){ chatWrapper.scrollTop = chatWrapper.scrollHeight; }
209
+ }, 300);
210
+ </script>
211
+ """, unsafe_allow_html=True)
212
 
213
+ # --- Handle user input ---
214
+ if user_input:
215
+ st.session_state["is_thinking"] = True
216
  thread_id = get_or_create_thread_id()
217
  client.beta.threads.messages.create(thread_id=thread_id, role="user", content=user_input)
218
  save_message("user", user_input)
 
230
  assistant_message = latest_response.content[0].text.value
231
  save_message("assistant", assistant_message)
232
 
233
+ mute_voice = st.session_state.get("mute_voice", False)
234
+ audio_path = None
235
+ if not mute_voice and assistant_message.strip():
236
+ audio_path = synthesize_voice(assistant_message, st.session_state["selected_voice"], user_id)
237
+ st.session_state["last_audio_path"] = audio_path
238
 
239
+ st.session_state["is_thinking"] = False
240
  time.sleep(0.2)
241
  st.rerun()