IAMTFRMZA commited on
Commit
0b3f9f3
Β·
verified Β·
1 Parent(s): d001693

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +89 -46
app.py CHANGED
@@ -1,32 +1,60 @@
1
  import streamlit as st
2
- from openai import OpenAI
 
3
  import time
4
  import os
5
  import uuid
6
  import firebase_admin
7
  from firebase_admin import credentials, firestore
 
8
 
9
- # πŸ” Firebase setup
10
  if not firebase_admin._apps:
11
  cred = credentials.Certificate("firebase-service-account.json")
12
  firebase_admin.initialize_app(cred)
13
-
14
  db = firestore.client()
15
 
16
- # πŸ” OpenAI setup
17
  openai_key = os.getenv("openai_key")
18
  assistant_id = os.getenv("assistant_id")
19
  client = OpenAI(api_key=openai_key)
20
 
21
- # 🌐 Streamlit Config
 
 
 
 
 
 
 
 
 
 
 
 
22
  st.set_page_config(page_title="LOR Technologies AI Assistant", layout="wide")
23
 
24
- # 🎯 Session + User ID
25
  if "user_id" not in st.session_state:
26
  st.session_state["user_id"] = str(uuid.uuid4())
27
  user_id = st.session_state["user_id"]
28
 
29
- # πŸ–ΌοΈ LORTech Branding + Styling
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  st.markdown("""
31
  <style>
32
  .block-container {padding-top: 1rem; padding-bottom: 0rem;}
@@ -37,7 +65,6 @@ st.markdown("""
37
  .lt-logo { vertical-align: middle; }
38
  </style>
39
  """, unsafe_allow_html=True)
40
-
41
  st.markdown("""
42
  <div style='text-align: center; margin-top: 20px; margin-bottom: -10px;'>
43
  <span style='display: inline-flex; align-items: center; gap: 8px;'>
@@ -47,7 +74,7 @@ st.markdown("""
47
  </div>
48
  """, unsafe_allow_html=True)
49
 
50
- # πŸ” Get or create a thread ID
51
  def get_or_create_thread_id():
52
  doc_ref = db.collection("users").document(user_id)
53
  doc = doc_ref.get()
@@ -58,7 +85,6 @@ def get_or_create_thread_id():
58
  doc_ref.set({"thread_id": thread.id, "created_at": firestore.SERVER_TIMESTAMP})
59
  return thread.id
60
 
61
- # πŸ’Ύ Save a message
62
  def save_message(role, content):
63
  db.collection("users").document(user_id).collection("messages").add({
64
  "role": role,
@@ -66,7 +92,6 @@ def save_message(role, content):
66
  "timestamp": firestore.SERVER_TIMESTAMP
67
  })
68
 
69
- # πŸ’¬ Display chat history
70
  def display_chat_history():
71
  messages = db.collection("users").document(user_id).collection("messages").order_by("timestamp").stream()
72
  assistant_icon_html = "<img src='/static/lorain.jpg' width='20' style='vertical-align:middle;'/>"
@@ -77,6 +102,25 @@ def display_chat_history():
77
  else:
78
  st.markdown(f"<div class='stChatMessage' data-testid='stChatMessage-assistant'>{assistant_icon_html} <strong>LORAIN:</strong> {data['content']}</div>", unsafe_allow_html=True)
79
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  # --- Main Chat UI ---
81
  input_col, clear_col = st.columns([9, 1])
82
  with input_col:
@@ -97,33 +141,10 @@ with clear_col:
97
  thread_id = get_or_create_thread_id()
98
  display_chat_history()
99
 
100
- if "mute_voice" not in st.session_state:
101
- st.session_state["mute_voice"] = False
102
-
103
- if "last_tts_text" not in st.session_state:
104
- st.session_state["last_tts_text"] = ""
105
-
106
- def synthesize_voice(text, user_id):
107
- audio_path = f"output_{user_id}.mp3"
108
- # Only synthesize if text changed or file doesn't exist
109
- if st.session_state["last_tts_text"] != text or not os.path.exists(audio_path):
110
- with st.spinner("Synthesizing voice with GPT-4o..."):
111
- speech_response = client.audio.speech.create(
112
- model="tts-1",
113
- voice="nova",
114
- input=text,
115
- response_format="mp3"
116
- )
117
- with open(audio_path, "wb") as f:
118
- f.write(speech_response.content)
119
- st.session_state["last_tts_text"] = text
120
- return audio_path
121
-
122
  if user_input:
123
- # Send user message to OpenAI thread
124
  client.beta.threads.messages.create(thread_id=thread_id, role="user", content=user_input)
125
  save_message("user", user_input)
126
-
127
  with st.spinner("Thinking and typing... πŸ’­"):
128
  run = client.beta.threads.runs.create(thread_id=thread_id, assistant_id=assistant_id)
129
  while True:
@@ -131,23 +152,48 @@ if user_input:
131
  if run_status.status == "completed":
132
  break
133
  time.sleep(1)
134
-
135
  messages_response = client.beta.threads.messages.list(thread_id=thread_id)
136
  latest_response = sorted(messages_response.data, key=lambda x: x.created_at)[-1]
137
  assistant_message = latest_response.content[0].text.value
138
  save_message("assistant", assistant_message)
139
 
140
- # Voice autoplay unless muted
141
  mute_voice = st.session_state.get("mute_voice", False)
142
- audio_path = synthesize_voice(assistant_message, user_id) if not mute_voice else None
143
-
144
- if not mute_voice and audio_path:
 
145
  st.audio(audio_path, format="audio/mp3", autoplay=True)
146
  elif mute_voice:
147
- st.info("πŸ”‡ Voice is muted. To enable assistant speech, click 'Unmute Voice' below and ask another question.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
148
 
149
- # Single mute/unmute button
150
- if not mute_voice:
 
 
 
 
 
 
 
 
 
 
151
  if st.button("πŸ”‡ Mute Voice"):
152
  st.session_state["mute_voice"] = True
153
  st.rerun()
@@ -155,6 +201,3 @@ if user_input:
155
  if st.button("πŸ”Š Unmute Voice"):
156
  st.session_state["mute_voice"] = False
157
  st.rerun()
158
-
159
- time.sleep(0.5)
160
- st.rerun()
 
1
  import streamlit as st
2
+ import asyncio
3
+ import edge_tts
4
  import time
5
  import os
6
  import uuid
7
  import firebase_admin
8
  from firebase_admin import credentials, firestore
9
+ from openai import OpenAI
10
 
11
+ # ---- Firebase setup ----
12
  if not firebase_admin._apps:
13
  cred = credentials.Certificate("firebase-service-account.json")
14
  firebase_admin.initialize_app(cred)
 
15
  db = firestore.client()
16
 
17
+ # ---- OpenAI setup ----
18
  openai_key = os.getenv("openai_key")
19
  assistant_id = os.getenv("assistant_id")
20
  client = OpenAI(api_key=openai_key)
21
 
22
+ # ---- Edge TTS voices ----
23
+ VOICE_OPTIONS = {
24
+ "Jenny (US, Female)": "en-US-JennyNeural",
25
+ "Aria (US, Female)": "en-US-AriaNeural",
26
+ "Ryan (UK, Male)": "en-GB-RyanNeural",
27
+ "Natasha (AU, Female)": "en-AU-NatashaNeural",
28
+ "William (AU, Male)": "en-AU-WilliamNeural",
29
+ "Libby (UK, Female)": "en-GB-LibbyNeural",
30
+ "Leah (SA, Female)": "en-ZA-LeahNeural",
31
+ "Luke (SA, Male)": "en-ZA-LukeNeural"
32
+ }
33
+
34
+ # --- Streamlit Config ---
35
  st.set_page_config(page_title="LOR Technologies AI Assistant", layout="wide")
36
 
37
+ # --- User/session state ---
38
  if "user_id" not in st.session_state:
39
  st.session_state["user_id"] = str(uuid.uuid4())
40
  user_id = st.session_state["user_id"]
41
 
42
+ if "mute_voice" not in st.session_state:
43
+ st.session_state["mute_voice"] = False
44
+ if "last_tts_text" not in st.session_state:
45
+ st.session_state["last_tts_text"] = ""
46
+ if "last_audio_path" not in st.session_state:
47
+ st.session_state["last_audio_path"] = ""
48
+ if "selected_voice" not in st.session_state:
49
+ st.session_state["selected_voice"] = "Jenny (US, Female)"
50
+
51
+ # --- Sidebar for Voice Selection ---
52
+ with st.sidebar:
53
+ st.markdown("### Voice Settings")
54
+ selected_voice = st.selectbox("Select assistant voice", list(VOICE_OPTIONS.keys()), index=list(VOICE_OPTIONS.keys()).index(st.session_state["selected_voice"]))
55
+ st.session_state["selected_voice"] = selected_voice
56
+
57
+ # --- Branding & Styling ---
58
  st.markdown("""
59
  <style>
60
  .block-container {padding-top: 1rem; padding-bottom: 0rem;}
 
65
  .lt-logo { vertical-align: middle; }
66
  </style>
67
  """, unsafe_allow_html=True)
 
68
  st.markdown("""
69
  <div style='text-align: center; margin-top: 20px; margin-bottom: -10px;'>
70
  <span style='display: inline-flex; align-items: center; gap: 8px;'>
 
74
  </div>
75
  """, unsafe_allow_html=True)
76
 
77
+ # --- Firestore helpers ---
78
  def get_or_create_thread_id():
79
  doc_ref = db.collection("users").document(user_id)
80
  doc = doc_ref.get()
 
85
  doc_ref.set({"thread_id": thread.id, "created_at": firestore.SERVER_TIMESTAMP})
86
  return thread.id
87
 
 
88
  def save_message(role, content):
89
  db.collection("users").document(user_id).collection("messages").add({
90
  "role": role,
 
92
  "timestamp": firestore.SERVER_TIMESTAMP
93
  })
94
 
 
95
  def display_chat_history():
96
  messages = db.collection("users").document(user_id).collection("messages").order_by("timestamp").stream()
97
  assistant_icon_html = "<img src='/static/lorain.jpg' width='20' style='vertical-align:middle;'/>"
 
102
  else:
103
  st.markdown(f"<div class='stChatMessage' data-testid='stChatMessage-assistant'>{assistant_icon_html} <strong>LORAIN:</strong> {data['content']}</div>", unsafe_allow_html=True)
104
 
105
+ # --- Edge TTS synth ---
106
+ async def edge_tts_synthesize(text, voice, user_id):
107
+ out_path = f"output_{user_id}.mp3"
108
+ communicate = edge_tts.Communicate(text, voice)
109
+ await communicate.save(out_path)
110
+ return out_path
111
+
112
+ def synthesize_voice(text, voice_key, user_id):
113
+ voice = VOICE_OPTIONS[voice_key]
114
+ out_path = f"output_{user_id}.mp3"
115
+ # Only synthesize if text changed or file missing or voice changed
116
+ if st.session_state["last_tts_text"] != text or not os.path.exists(out_path) or st.session_state.get("last_voice") != voice:
117
+ with st.spinner(f"Generating voice ({voice_key})..."):
118
+ asyncio.run(edge_tts_synthesize(text, voice, user_id))
119
+ st.session_state["last_tts_text"] = text
120
+ st.session_state["last_audio_path"] = out_path
121
+ st.session_state["last_voice"] = voice
122
+ return out_path
123
+
124
  # --- Main Chat UI ---
125
  input_col, clear_col = st.columns([9, 1])
126
  with input_col:
 
141
  thread_id = get_or_create_thread_id()
142
  display_chat_history()
143
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
  if user_input:
145
+ # --- OpenAI Assistant Response ---
146
  client.beta.threads.messages.create(thread_id=thread_id, role="user", content=user_input)
147
  save_message("user", user_input)
 
148
  with st.spinner("Thinking and typing... πŸ’­"):
149
  run = client.beta.threads.runs.create(thread_id=thread_id, assistant_id=assistant_id)
150
  while True:
 
152
  if run_status.status == "completed":
153
  break
154
  time.sleep(1)
 
155
  messages_response = client.beta.threads.messages.list(thread_id=thread_id)
156
  latest_response = sorted(messages_response.data, key=lambda x: x.created_at)[-1]
157
  assistant_message = latest_response.content[0].text.value
158
  save_message("assistant", assistant_message)
159
 
160
+ # --- TTS: Speak unless muted ---
161
  mute_voice = st.session_state.get("mute_voice", False)
162
+ audio_path = None
163
+ if not mute_voice and assistant_message.strip():
164
+ audio_path = synthesize_voice(assistant_message, st.session_state["selected_voice"], user_id)
165
+ st.session_state["last_audio_path"] = audio_path
166
  st.audio(audio_path, format="audio/mp3", autoplay=True)
167
  elif mute_voice:
168
+ st.info("πŸ”‡ Voice is muted. Click Unmute below to enable assistant speech.")
169
+
170
+ # --- Controls (Mute/Unmute/Replay) ---
171
+ col1, col2 = st.columns([1, 1])
172
+ with col1:
173
+ if not mute_voice and st.button("πŸ”‡ Mute Voice"):
174
+ st.session_state["mute_voice"] = True
175
+ st.rerun()
176
+ elif mute_voice and st.button("πŸ”Š Unmute Voice"):
177
+ st.session_state["mute_voice"] = False
178
+ st.rerun()
179
+ with col2:
180
+ # Replay button: Always available if last_audio_path exists
181
+ if st.session_state.get("last_audio_path") and os.path.exists(st.session_state["last_audio_path"]):
182
+ if st.button("πŸ” Replay Voice"):
183
+ st.audio(st.session_state["last_audio_path"], format="audio/mp3", autoplay=True)
184
 
185
+ time.sleep(0.2)
186
+ st.rerun()
187
+ else:
188
+ # Always show last audio with replay if available
189
+ if st.session_state.get("last_audio_path") and os.path.exists(st.session_state["last_audio_path"]) and not st.session_state["mute_voice"]:
190
+ st.audio(st.session_state["last_audio_path"], format="audio/mp3", autoplay=False)
191
+ # Controls: Only show Replay when idle
192
+ if st.button("πŸ” Replay Last Voice"):
193
+ st.audio(st.session_state["last_audio_path"], format="audio/mp3", autoplay=True)
194
+
195
+ # Show mute/unmute in idle state too
196
+ if not st.session_state["mute_voice"]:
197
  if st.button("πŸ”‡ Mute Voice"):
198
  st.session_state["mute_voice"] = True
199
  st.rerun()
 
201
  if st.button("πŸ”Š Unmute Voice"):
202
  st.session_state["mute_voice"] = False
203
  st.rerun()