Implemented comprehensive enhancements: removed TTS, fixed session state issues, improved UI/UX, added analytics, input validation, and feedback collection
Browse files- app.py +181 -98
- requirements.txt +0 -4
- services/tts.py +0 -88
app.py
CHANGED
|
@@ -43,10 +43,6 @@ if "ngrok_url_temp" not in st.session_state:
|
|
| 43 |
st.session_state.ngrok_url_temp = st.session_state.get("ngrok_url", "https://7bcc180dffd1.ngrok-free.app")
|
| 44 |
if "hf_expert_requested" not in st.session_state:
|
| 45 |
st.session_state.hf_expert_requested = False
|
| 46 |
-
if "tts_enabled" not in st.session_state:
|
| 47 |
-
st.session_state.tts_enabled = False
|
| 48 |
-
if "voice_input" not in st.session_state:
|
| 49 |
-
st.session_state.voice_input = ""
|
| 50 |
|
| 51 |
# Sidebar layout redesign
|
| 52 |
with st.sidebar:
|
|
@@ -153,22 +149,47 @@ with st.sidebar:
|
|
| 153 |
use_container_width=True,
|
| 154 |
disabled=st.session_state.is_processing):
|
| 155 |
st.session_state.hf_expert_requested = True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 156 |
|
| 157 |
# Main interface
|
| 158 |
st.title("🧠 AI Life Coach")
|
| 159 |
st.markdown("Ask me anything about personal development, goal setting, or life advice!")
|
| 160 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 161 |
# Display messages
|
| 162 |
for message in st.session_state.messages:
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
else:
|
| 169 |
-
st.markdown(message["content"])
|
| 170 |
-
if "timestamp" in message:
|
| 171 |
-
st.caption(f"🕒 {message['timestamp']}")
|
| 172 |
|
| 173 |
# Manual HF Analysis Section
|
| 174 |
if st.session_state.messages and len(st.session_state.messages) > 0:
|
|
@@ -256,105 +277,137 @@ if st.session_state.get("hf_expert_requested", False):
|
|
| 256 |
st.error(f"❌ HF Expert analysis failed: {user_msg}")
|
| 257 |
st.session_state.hf_expert_requested = False
|
| 258 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 259 |
# Chat input - FIXED VERSION (moved outside of tabs)
|
| 260 |
user_input = st.chat_input("Type your message here...", disabled=st.session_state.is_processing)
|
| 261 |
|
| 262 |
# Process message when received
|
| 263 |
if user_input and not st.session_state.is_processing:
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
st.
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
st.session_state.messages.append({
|
| 272 |
-
"role": "user",
|
| 273 |
-
"content": user_input,
|
| 274 |
-
"timestamp": datetime.now().strftime("%H:%M:%S")
|
| 275 |
-
})
|
| 276 |
-
|
| 277 |
-
# Process AI response
|
| 278 |
-
with st.chat_message("assistant"):
|
| 279 |
-
response_placeholder = st.empty()
|
| 280 |
-
status_placeholder = st.empty()
|
| 281 |
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 292 |
|
| 293 |
try:
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
|
|
|
|
|
|
|
|
|
| 300 |
|
| 301 |
-
if ai_response:
|
| 302 |
-
response_placeholder.markdown(ai_response)
|
| 303 |
-
status_placeholder.success("✅ Response received!")
|
| 304 |
-
else:
|
| 305 |
-
status_placeholder.warning("⚠️ Empty response from Ollama")
|
| 306 |
-
|
| 307 |
-
except Exception as ollama_error:
|
| 308 |
-
user_msg = translate_error(ollama_error)
|
| 309 |
-
status_placeholder.error(f"⚠️ {user_msg}")
|
| 310 |
-
|
| 311 |
-
# Fallback to HF if available
|
| 312 |
-
if config.hf_token and not ai_response:
|
| 313 |
-
status_placeholder.info(PROCESSING_STAGES["hf_init"])
|
| 314 |
try:
|
| 315 |
-
ai_response =
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 316 |
if ai_response:
|
| 317 |
response_placeholder.markdown(ai_response)
|
| 318 |
-
status_placeholder.success("✅
|
| 319 |
else:
|
| 320 |
-
status_placeholder.
|
| 321 |
-
|
| 322 |
-
|
|
|
|
| 323 |
status_placeholder.error(f"⚠️ {user_msg}")
|
| 324 |
-
|
| 325 |
-
# Save response if successful
|
| 326 |
-
if ai_response:
|
| 327 |
-
# Update conversation history
|
| 328 |
-
conversation.append({"role": "user", "content": user_input})
|
| 329 |
-
conversation.append({"role": "assistant", "content": ai_response})
|
| 330 |
-
user_session["conversation"] = conversation
|
| 331 |
-
session_manager.update_session("default_user", user_session)
|
| 332 |
|
| 333 |
-
#
|
| 334 |
-
|
| 335 |
-
"
|
| 336 |
-
|
| 337 |
-
|
| 338 |
-
|
| 339 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 340 |
st.session_state.messages.append({
|
| 341 |
"role": "assistant",
|
| 342 |
-
"content": "
|
| 343 |
"timestamp": datetime.now().strftime("%H:%M:%S")
|
| 344 |
})
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
|
| 348 |
-
|
| 349 |
-
st.session_state.messages.append({
|
| 350 |
-
"role": "assistant",
|
| 351 |
-
"content": f"⚠️ {user_msg}",
|
| 352 |
-
"timestamp": datetime.now().strftime("%H:%M:%S")
|
| 353 |
-
})
|
| 354 |
-
finally:
|
| 355 |
-
st.session_state.is_processing = False
|
| 356 |
-
time.sleep(0.5) # Brief pause
|
| 357 |
-
st.experimental_rerun()
|
| 358 |
|
| 359 |
# Add evaluation dashboard tab (separate from chat interface)
|
| 360 |
st.divider()
|
|
@@ -408,20 +461,20 @@ with tab1:
|
|
| 408 |
research_keywords = ["study", "research", "paper", "effectiveness", "clinical trial", "vitamin", "drug", "metformin", "CRISPR"]
|
| 409 |
if any(kw in final_prompt.lower() for kw in research_keywords):
|
| 410 |
st.markdown("**Related Research Papers:**")
|
| 411 |
-
with st.spinner("Searching
|
| 412 |
try:
|
| 413 |
papers = find_papers(final_prompt, limit=3)
|
| 414 |
if papers:
|
| 415 |
for i, paper in enumerate(papers):
|
| 416 |
-
with st.expander(f"📄 {paper['title'][:
|
| 417 |
st.markdown(f"**Authors:** {', '.join(paper['authors'][:3])}")
|
| 418 |
st.markdown(f"**Year:** {paper['year']}")
|
| 419 |
st.markdown(f"**Citations:** {paper['citation_count']}")
|
| 420 |
st.markdown(f"**Venue:** {paper['venue']}")
|
| 421 |
-
st.markdown(f"**Abstract:** {paper['abstract'][:
|
| 422 |
-
st.markdown(f"[View Paper]({paper['url']})")
|
| 423 |
else:
|
| 424 |
-
st.info("No relevant papers found for this topic.")
|
| 425 |
except Exception as e:
|
| 426 |
st.warning(f"Could not fetch research papers: {translate_error(e)}")
|
| 427 |
|
|
@@ -512,6 +565,36 @@ with tab2:
|
|
| 512 |
features.append("Weather Data")
|
| 513 |
|
| 514 |
st.markdown(f"**Active Features:** {', '.join(features) if features else 'None'}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 515 |
|
| 516 |
with tab3:
|
| 517 |
st.header("ℹ️ About AI Life Coach")
|
|
|
|
| 43 |
st.session_state.ngrok_url_temp = st.session_state.get("ngrok_url", "https://7bcc180dffd1.ngrok-free.app")
|
| 44 |
if "hf_expert_requested" not in st.session_state:
|
| 45 |
st.session_state.hf_expert_requested = False
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
|
| 47 |
# Sidebar layout redesign
|
| 48 |
with st.sidebar:
|
|
|
|
| 149 |
use_container_width=True,
|
| 150 |
disabled=st.session_state.is_processing):
|
| 151 |
st.session_state.hf_expert_requested = True
|
| 152 |
+
|
| 153 |
+
st.divider()
|
| 154 |
+
st.subheader("🐛 Debug Info")
|
| 155 |
+
# Show current configuration
|
| 156 |
+
st.markdown(f"**Environment:** {'HF Space' if config.is_hf_space else 'Local'}")
|
| 157 |
+
st.markdown(f"**Model:** {st.session_state.selected_model}")
|
| 158 |
+
st.markdown(f"**Ollama URL:** {st.session_state.ngrok_url_temp}")
|
| 159 |
+
|
| 160 |
+
# Show active features
|
| 161 |
+
features = []
|
| 162 |
+
if config.hf_token:
|
| 163 |
+
features.append("HF Expert")
|
| 164 |
+
if os.getenv("TAVILY_API_KEY"):
|
| 165 |
+
features.append("Web Search")
|
| 166 |
+
if config.openweather_api_key:
|
| 167 |
+
features.append("Weather")
|
| 168 |
+
|
| 169 |
+
st.markdown(f"**Active Features:** {', '.join(features) if features else 'None'}")
|
| 170 |
|
| 171 |
# Main interface
|
| 172 |
st.title("🧠 AI Life Coach")
|
| 173 |
st.markdown("Ask me anything about personal development, goal setting, or life advice!")
|
| 174 |
|
| 175 |
+
# Consistent message rendering function
|
| 176 |
+
def render_message(role, content, timestamp=None):
|
| 177 |
+
"""Render chat messages with consistent styling"""
|
| 178 |
+
with st.chat_message(role):
|
| 179 |
+
if role == "assistant" and content.startswith("### 🤖 HF Expert Analysis"):
|
| 180 |
+
st.markdown(content)
|
| 181 |
+
else:
|
| 182 |
+
st.markdown(content)
|
| 183 |
+
if timestamp:
|
| 184 |
+
st.caption(f"🕒 {timestamp}")
|
| 185 |
+
|
| 186 |
# Display messages
|
| 187 |
for message in st.session_state.messages:
|
| 188 |
+
render_message(
|
| 189 |
+
message["role"],
|
| 190 |
+
message["content"],
|
| 191 |
+
message.get("timestamp")
|
| 192 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 193 |
|
| 194 |
# Manual HF Analysis Section
|
| 195 |
if st.session_state.messages and len(st.session_state.messages) > 0:
|
|
|
|
| 277 |
st.error(f"❌ HF Expert analysis failed: {user_msg}")
|
| 278 |
st.session_state.hf_expert_requested = False
|
| 279 |
|
| 280 |
+
# Input validation function
|
| 281 |
+
def validate_user_input(text):
|
| 282 |
+
"""Validate and sanitize user input"""
|
| 283 |
+
if not text or not text.strip():
|
| 284 |
+
return False, "Input cannot be empty"
|
| 285 |
+
|
| 286 |
+
if len(text) > 1000:
|
| 287 |
+
return False, "Input too long (max 1000 characters)"
|
| 288 |
+
|
| 289 |
+
# Check for potentially harmful patterns
|
| 290 |
+
harmful_patterns = ["<script", "javascript:", "onload=", "onerror="]
|
| 291 |
+
if any(pattern in text.lower() for pattern in harmful_patterns):
|
| 292 |
+
return False, "Potentially harmful input detected"
|
| 293 |
+
|
| 294 |
+
return True, text.strip()
|
| 295 |
+
|
| 296 |
# Chat input - FIXED VERSION (moved outside of tabs)
|
| 297 |
user_input = st.chat_input("Type your message here...", disabled=st.session_state.is_processing)
|
| 298 |
|
| 299 |
# Process message when received
|
| 300 |
if user_input and not st.session_state.is_processing:
|
| 301 |
+
# Validate input
|
| 302 |
+
is_valid, validated_input = validate_user_input(user_input)
|
| 303 |
+
if not is_valid:
|
| 304 |
+
st.error(validated_input)
|
| 305 |
+
st.session_state.is_processing = False
|
| 306 |
+
else:
|
| 307 |
+
st.session_state.is_processing = True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 308 |
|
| 309 |
+
# Display user message
|
| 310 |
+
with st.chat_message("user"):
|
| 311 |
+
st.markdown(validated_input)
|
| 312 |
+
|
| 313 |
+
# Add to message history - ensure proper format
|
| 314 |
+
st.session_state.messages.append({
|
| 315 |
+
"role": "user",
|
| 316 |
+
"content": validated_input,
|
| 317 |
+
"timestamp": datetime.now().strftime("%H:%M:%S")
|
| 318 |
+
})
|
| 319 |
+
|
| 320 |
+
# Process AI response
|
| 321 |
+
with st.chat_message("assistant"):
|
| 322 |
+
response_placeholder = st.empty()
|
| 323 |
+
status_placeholder = st.empty()
|
| 324 |
|
| 325 |
try:
|
| 326 |
+
# Get conversation history
|
| 327 |
+
user_session = session_manager.get_session("default_user")
|
| 328 |
+
conversation = user_session.get("conversation", [])
|
| 329 |
+
conversation_history = conversation[-5:] # Last 5 messages
|
| 330 |
+
conversation_history.append({"role": "user", "content": validated_input})
|
| 331 |
+
|
| 332 |
+
# Try Ollama with proper error handling
|
| 333 |
+
status_placeholder.info(PROCESSING_STAGES["ollama"])
|
| 334 |
+
ai_response = None
|
| 335 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 336 |
try:
|
| 337 |
+
ai_response = send_to_ollama(
|
| 338 |
+
validated_input,
|
| 339 |
+
conversation_history,
|
| 340 |
+
st.session_state.ngrok_url_temp,
|
| 341 |
+
st.session_state.selected_model
|
| 342 |
+
)
|
| 343 |
+
|
| 344 |
if ai_response:
|
| 345 |
response_placeholder.markdown(ai_response)
|
| 346 |
+
status_placeholder.success("✅ Response received!")
|
| 347 |
else:
|
| 348 |
+
status_placeholder.warning("⚠️ Empty response from Ollama")
|
| 349 |
+
|
| 350 |
+
except Exception as ollama_error:
|
| 351 |
+
user_msg = translate_error(ollama_error)
|
| 352 |
status_placeholder.error(f"⚠️ {user_msg}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 353 |
|
| 354 |
+
# Fallback to HF if available
|
| 355 |
+
if config.hf_token and not ai_response:
|
| 356 |
+
status_placeholder.info(PROCESSING_STAGES["hf_init"])
|
| 357 |
+
try:
|
| 358 |
+
ai_response = send_to_hf(validated_input, conversation_history)
|
| 359 |
+
if ai_response:
|
| 360 |
+
response_placeholder.markdown(ai_response)
|
| 361 |
+
status_placeholder.success("✅ HF response received!")
|
| 362 |
+
else:
|
| 363 |
+
status_placeholder.error("❌ No response from HF")
|
| 364 |
+
except Exception as hf_error:
|
| 365 |
+
user_msg = translate_error(hf_error)
|
| 366 |
+
status_placeholder.error(f"⚠️ {user_msg}")
|
| 367 |
+
|
| 368 |
+
# Save response if successful
|
| 369 |
+
if ai_response:
|
| 370 |
+
# Update conversation history
|
| 371 |
+
conversation.append({"role": "user", "content": validated_input})
|
| 372 |
+
conversation.append({"role": "assistant", "content": ai_response})
|
| 373 |
+
user_session["conversation"] = conversation
|
| 374 |
+
session_manager.update_session("default_user", user_session)
|
| 375 |
+
|
| 376 |
+
# Add to message history - ensure proper format
|
| 377 |
+
st.session_state.messages.append({
|
| 378 |
+
"role": "assistant",
|
| 379 |
+
"content": ai_response,
|
| 380 |
+
"timestamp": datetime.now().strftime("%H:%M:%S")
|
| 381 |
+
})
|
| 382 |
+
|
| 383 |
+
# Add feedback buttons
|
| 384 |
+
st.divider()
|
| 385 |
+
col1, col2 = st.columns(2)
|
| 386 |
+
with col1:
|
| 387 |
+
if st.button("👍 Helpful", key=f"helpful_{len(st.session_state.messages)}"):
|
| 388 |
+
st.success("Thanks for your feedback!")
|
| 389 |
+
with col2:
|
| 390 |
+
if st.button("👎 Not Helpful", key=f"not_helpful_{len(st.session_state.messages)}"):
|
| 391 |
+
st.success("Thanks for your feedback!")
|
| 392 |
+
else:
|
| 393 |
+
st.session_state.messages.append({
|
| 394 |
+
"role": "assistant",
|
| 395 |
+
"content": "Sorry, I couldn't process your request. Please try again.",
|
| 396 |
+
"timestamp": datetime.now().strftime("%H:%M:%S")
|
| 397 |
+
})
|
| 398 |
+
|
| 399 |
+
except Exception as e:
|
| 400 |
+
user_msg = translate_error(e)
|
| 401 |
+
response_placeholder.error(f"⚠️ {user_msg}")
|
| 402 |
st.session_state.messages.append({
|
| 403 |
"role": "assistant",
|
| 404 |
+
"content": f"⚠️ {user_msg}",
|
| 405 |
"timestamp": datetime.now().strftime("%H:%M:%S")
|
| 406 |
})
|
| 407 |
+
finally:
|
| 408 |
+
st.session_state.is_processing = False
|
| 409 |
+
time.sleep(0.5) # Brief pause
|
| 410 |
+
st.experimental_rerun()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 411 |
|
| 412 |
# Add evaluation dashboard tab (separate from chat interface)
|
| 413 |
st.divider()
|
|
|
|
| 461 |
research_keywords = ["study", "research", "paper", "effectiveness", "clinical trial", "vitamin", "drug", "metformin", "CRISPR"]
|
| 462 |
if any(kw in final_prompt.lower() for kw in research_keywords):
|
| 463 |
st.markdown("**Related Research Papers:**")
|
| 464 |
+
with st.spinner("Searching academic databases..."):
|
| 465 |
try:
|
| 466 |
papers = find_papers(final_prompt, limit=3)
|
| 467 |
if papers:
|
| 468 |
for i, paper in enumerate(papers):
|
| 469 |
+
with st.expander(f"📄 {paper['title'][:60]}..."):
|
| 470 |
st.markdown(f"**Authors:** {', '.join(paper['authors'][:3])}")
|
| 471 |
st.markdown(f"**Year:** {paper['year']}")
|
| 472 |
st.markdown(f"**Citations:** {paper['citation_count']}")
|
| 473 |
st.markdown(f"**Venue:** {paper['venue']}")
|
| 474 |
+
st.markdown(f"**Abstract:** {paper['abstract'][:200]}...")
|
| 475 |
+
st.markdown(f"[View Full Paper]({paper['url']})")
|
| 476 |
else:
|
| 477 |
+
st.info("No relevant academic papers found for this topic.")
|
| 478 |
except Exception as e:
|
| 479 |
st.warning(f"Could not fetch research papers: {translate_error(e)}")
|
| 480 |
|
|
|
|
| 565 |
features.append("Weather Data")
|
| 566 |
|
| 567 |
st.markdown(f"**Active Features:** {', '.join(features) if features else 'None'}")
|
| 568 |
+
|
| 569 |
+
# Conversation Analytics
|
| 570 |
+
st.subheader("📊 Conversation Analytics")
|
| 571 |
+
try:
|
| 572 |
+
user_session = session_manager.get_session("default_user")
|
| 573 |
+
conversation = user_session.get("conversation", [])
|
| 574 |
+
|
| 575 |
+
if conversation:
|
| 576 |
+
# Analyze conversation patterns
|
| 577 |
+
user_messages = [msg for msg in conversation if msg["role"] == "user"]
|
| 578 |
+
ai_messages = [msg for msg in conversation if msg["role"] == "assistant"]
|
| 579 |
+
|
| 580 |
+
col1, col2, col3 = st.columns(3)
|
| 581 |
+
col1.metric("Total Exchanges", len(user_messages))
|
| 582 |
+
col2.metric("Avg Response Length",
|
| 583 |
+
round(sum(len(msg.get("content", "")) for msg in ai_messages) / len(ai_messages)) if ai_messages else 0)
|
| 584 |
+
col3.metric("Topics Discussed", len(set(["life", "goal", "health", "career"]) &
|
| 585 |
+
set(" ".join([msg.get("content", "") for msg in conversation]).lower().split())))
|
| 586 |
+
|
| 587 |
+
# Show most common words/topics
|
| 588 |
+
all_text = " ".join([msg.get("content", "") for msg in conversation]).lower()
|
| 589 |
+
common_words = ["life", "goal", "health", "career", "productivity", "mindfulness"]
|
| 590 |
+
relevant_topics = [word for word in common_words if word in all_text]
|
| 591 |
+
if relevant_topics:
|
| 592 |
+
st.markdown(f"**Detected Topics:** {', '.join(relevant_topics)}")
|
| 593 |
+
else:
|
| 594 |
+
st.info("No conversation data available yet.")
|
| 595 |
+
|
| 596 |
+
except Exception as e:
|
| 597 |
+
st.warning(f"Could not analyze conversation: {translate_error(e)}")
|
| 598 |
|
| 599 |
with tab3:
|
| 600 |
st.header("ℹ️ About AI Life Coach")
|
requirements.txt
CHANGED
|
@@ -11,7 +11,3 @@ pygame==2.5.2
|
|
| 11 |
pydantic==1.10.7
|
| 12 |
typing-extensions>=4.5.0
|
| 13 |
semanticscholar>=0.1.8
|
| 14 |
-
semanticscholar>=0.1.8
|
| 15 |
-
tavily-python>=0.1.0,<1.0.0
|
| 16 |
-
semanticscholar>=0.1.8
|
| 17 |
-
tavily-python>=0.1.0,<1.0.0
|
|
|
|
| 11 |
pydantic==1.10.7
|
| 12 |
typing-extensions>=4.5.0
|
| 13 |
semanticscholar>=0.1.8
|
|
|
|
|
|
|
|
|
|
|
|
services/tts.py
DELETED
|
@@ -1,88 +0,0 @@
|
|
| 1 |
-
import os
|
| 2 |
-
import requests
|
| 3 |
-
from typing import Optional
|
| 4 |
-
from utils.config import config
|
| 5 |
-
|
| 6 |
-
class TTSService:
|
| 7 |
-
"""Service for converting text to speech"""
|
| 8 |
-
|
| 9 |
-
def __init__(self):
|
| 10 |
-
self.hf_token = config.hf_token
|
| 11 |
-
self.tts_model = "facebook/fastspeech2-en-ljspeech"
|
| 12 |
-
self.vocoder_model = "facebook/hifigan-universal"
|
| 13 |
-
|
| 14 |
-
def synthesize_speech(self, text: str) -> Optional[bytes]:
|
| 15 |
-
"""
|
| 16 |
-
Convert text to speech using Hugging Face API
|
| 17 |
-
|
| 18 |
-
Args:
|
| 19 |
-
text: Text to convert to speech
|
| 20 |
-
|
| 21 |
-
Returns:
|
| 22 |
-
Audio bytes or None if failed
|
| 23 |
-
"""
|
| 24 |
-
if not self.hf_token:
|
| 25 |
-
print("Hugging Face token not configured for TTS")
|
| 26 |
-
return None
|
| 27 |
-
|
| 28 |
-
try:
|
| 29 |
-
# First, generate speech with text-to-speech model
|
| 30 |
-
tts_headers = {
|
| 31 |
-
"Authorization": f"Bearer {self.hf_token}"
|
| 32 |
-
}
|
| 33 |
-
|
| 34 |
-
tts_payload = {
|
| 35 |
-
"inputs": text
|
| 36 |
-
}
|
| 37 |
-
|
| 38 |
-
tts_response = requests.post(
|
| 39 |
-
f"https://api-inference.huggingface.co/models/{self.tts_model}",
|
| 40 |
-
headers=tts_headers,
|
| 41 |
-
json=tts_payload
|
| 42 |
-
)
|
| 43 |
-
|
| 44 |
-
if tts_response.status_code != 200:
|
| 45 |
-
print(f"TTS model error: {tts_response.status_code} - {tts_response.text}")
|
| 46 |
-
return None
|
| 47 |
-
|
| 48 |
-
# Then, convert to audio with vocoder
|
| 49 |
-
vocoder_response = requests.post(
|
| 50 |
-
f"https://api-inference.huggingface.co/models/{self.vocoder_model}",
|
| 51 |
-
headers=tts_headers,
|
| 52 |
-
data=tts_response.content
|
| 53 |
-
)
|
| 54 |
-
|
| 55 |
-
if vocoder_response.status_code == 200:
|
| 56 |
-
return vocoder_response.content
|
| 57 |
-
else:
|
| 58 |
-
print(f"Vocoder error: {vocoder_response.status_code} - {vocoder_response.text}")
|
| 59 |
-
return None
|
| 60 |
-
|
| 61 |
-
except Exception as e:
|
| 62 |
-
print(f"Error synthesizing speech: {e}")
|
| 63 |
-
return None
|
| 64 |
-
|
| 65 |
-
def save_audio_file(self, text: str, filename: str) -> bool:
|
| 66 |
-
"""
|
| 67 |
-
Synthesize speech and save to file
|
| 68 |
-
|
| 69 |
-
Args:
|
| 70 |
-
text: Text to convert to speech
|
| 71 |
-
filename: Output filename (.wav)
|
| 72 |
-
|
| 73 |
-
Returns:
|
| 74 |
-
Boolean indicating success
|
| 75 |
-
"""
|
| 76 |
-
audio_data = self.synthesize_speech(text)
|
| 77 |
-
if audio_data:
|
| 78 |
-
try:
|
| 79 |
-
with open(filename, 'wb') as f:
|
| 80 |
-
f.write(audio_data)
|
| 81 |
-
return True
|
| 82 |
-
except Exception as e:
|
| 83 |
-
print(f"Error saving audio file: {e}")
|
| 84 |
-
return False
|
| 85 |
-
return False
|
| 86 |
-
|
| 87 |
-
# Global TTS service instance
|
| 88 |
-
tts_service = TTSService()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|