Nihal2000 commited on
Commit
ffc9670
·
1 Parent(s): 85ffb4f

fixed all bugs

Browse files
Files changed (3) hide show
  1. app.py +351 -340
  2. services/elevenlabs_service.py +13 -10
  3. services/llamaindex_service.py +36 -32
app.py CHANGED
@@ -271,6 +271,14 @@ class ContentOrganizerMCPServer:
271
  return {"success": False, "error": str(e)}
272
 
273
  mcp_server = ContentOrganizerMCPServer()
 
 
 
 
 
 
 
 
274
 
275
  def get_document_list():
276
  try:
@@ -345,6 +353,9 @@ def upload_and_process_file(file):
345
  file_type = Path(file_path).suffix.lower().strip('.')
346
  logger.info(f"Processing file: {file_path}, type: {file_type}")
347
  result = mcp_server.run_async(mcp_server.ingest_document_async(file_path, file_type))
 
 
 
348
 
349
  doc_list_updated = get_document_list()
350
  doc_choices_updated = get_document_choices()
@@ -634,278 +645,278 @@ voice_conversation_state = {
634
  "transcript": []
635
  }
636
 
637
- voice_conversation_state = {
638
- "session_id": None,
639
- "active": False,
640
- "transcript": []
641
- }
642
 
643
- def start_voice_conversation():
644
- """
645
- Start a new voice conversation session
646
 
647
- Returns:
648
- Tuple of (status_message, start_button_state, stop_button_state, chatbot_history)
649
- """
650
- try:
651
- # Check if service is available
652
- if not mcp_server.elevenlabs_service.is_available():
653
- return (
654
- "⚠️ Voice assistant not configured.\n\n"
655
- "**Setup Instructions:**\n"
656
- "1. Get API key from: https://elevenlabs.io/app/settings/api-keys\n"
657
- "2. Create an agent at: https://elevenlabs.io/app/conversational-ai\n"
658
- "3. Add to .env file:\n"
659
- " - ELEVENLABS_API_KEY=your_api_key\n"
660
- " - ELEVENLABS_AGENT_ID=your_agent_id\n"
661
- "4. Restart the application",
662
- gr.update(interactive=True), # start button enabled
663
- gr.update(interactive=False), # stop button disabled
664
- []
665
- )
666
 
667
- # Create new session
668
- session_id = str(uuid.uuid4())
669
- result = mcp_server.run_async(
670
- mcp_server.elevenlabs_service.start_conversation(session_id)
671
- )
672
 
673
- if result.get("success"):
674
- voice_conversation_state["session_id"] = session_id
675
- voice_conversation_state["active"] = True
676
- voice_conversation_state["transcript"] = []
677
 
678
- # Initialize chatbot with welcome message
679
- initial_message = {
680
- "role": "assistant",
681
- "content": "👋 Hello! I'm your AI librarian. Ask me anything about your documents!"
682
- }
683
 
684
- return (
685
- "✅ Voice assistant is ready!\n\n"
686
- "You can now ask questions about your uploaded documents.",
687
- gr.update(interactive=False), # start button disabled
688
- gr.update(interactive=True), # stop button enabled
689
- [initial_message]
690
- )
691
- else:
692
- error_msg = result.get("error", "Unknown error")
693
- return (
694
- f"❌ Failed to start: {error_msg}\n\n"
695
- "**Troubleshooting:**\n"
696
- "• Check your API key is valid\n"
697
- "• Verify agent ID is correct\n"
698
- "• Check internet connection",
699
- gr.update(interactive=True),
700
- gr.update(interactive=False),
701
- []
702
- )
703
- except Exception as e:
704
- logger.error(f"Error starting voice conversation: {str(e)}", exc_info=True)
705
- return (
706
- f"❌ Error: {str(e)}",
707
- gr.update(interactive=True),
708
- gr.update(interactive=False),
709
- []
710
- )
711
-
712
- def stop_voice_conversation():
713
- """
714
- Stop active voice conversation
715
 
716
- Returns:
717
- Tuple of (status_message, start_button_state, stop_button_state, chatbot_history)
718
- """
719
- try:
720
- if not voice_conversation_state["active"]:
721
- return (
722
- "ℹ️ No active conversation",
723
- gr.update(interactive=True),
724
- gr.update(interactive=False),
725
- voice_conversation_state["transcript"]
726
- )
727
 
728
- session_id = voice_conversation_state["session_id"]
729
- if session_id:
730
- mcp_server.run_async(
731
- mcp_server.elevenlabs_service.end_conversation(session_id)
732
- )
733
 
734
- # Get conversation stats
735
- message_count = len(voice_conversation_state["transcript"])
736
 
737
- voice_conversation_state["active"] = False
738
- voice_conversation_state["session_id"] = None
739
 
740
- return (
741
- f"✅ Conversation ended\n\n"
742
- f"📊 Stats: {message_count} messages exchanged",
743
- gr.update(interactive=True),
744
- gr.update(interactive=False),
745
- voice_conversation_state["transcript"]
746
- )
747
- except Exception as e:
748
- logger.error(f"Error stopping conversation: {str(e)}")
749
- return (
750
- f"❌ Error: {str(e)}",
751
- gr.update(interactive=True),
752
- gr.update(interactive=False),
753
- voice_conversation_state["transcript"]
754
- )
755
-
756
- def send_voice_message_v6(message, chat_history):
757
- """
758
- Send message in voice conversation - Gradio 6+ format
759
 
760
- Args:
761
- message: User's text message
762
- chat_history: Current chat history (list of message dicts)
763
 
764
- Returns:
765
- Tuple of (updated_chat_history, cleared_input_box)
766
- """
767
- try:
768
- # Validate state
769
- if not voice_conversation_state["active"]:
770
- chat_history.append({
771
- "role": "assistant",
772
- "content": "⚠️ Please start a conversation first by clicking 'Start Conversation'"
773
- })
774
- return chat_history, ""
775
 
776
- # Validate input
777
- if not message or not message.strip():
778
- return chat_history, message
779
 
780
- session_id = voice_conversation_state["session_id"]
781
 
782
- # Add user message to display
783
- chat_history.append({
784
- "role": "user",
785
- "content": message
786
- })
787
 
788
- # Show typing indicator
789
- chat_history.append({
790
- "role": "assistant",
791
- "content": "🤔 Thinking..."
792
- })
793
 
794
- # Get AI response
795
- result = mcp_server.run_async(
796
- mcp_server.voice_tool.voice_qa(message, session_id)
797
- )
798
 
799
- # Remove typing indicator
800
- chat_history = chat_history[:-1]
801
 
802
- # Add response
803
- if result.get("success"):
804
- answer = result.get("answer", "No response")
805
 
806
- # Add helpful context if RAG was used
807
- if "document" in answer.lower() or "file" in answer.lower():
808
- footer = "\n\n💡 *Answer based on your documents*"
809
- else:
810
- footer = ""
811
 
812
- chat_history.append({
813
- "role": "assistant",
814
- "content": answer + footer
815
- })
816
- else:
817
- error_msg = result.get("error", "Unknown error")
818
- chat_history.append({
819
- "role": "assistant",
820
- "content": f"❌ Error: {error_msg}\n\n"
821
- "**Suggestions:**\n"
822
- "• Try rephrasing your question\n"
823
- "• Make sure you have uploaded relevant documents\n"
824
- "• Check if the question is about your document library"
825
- })
826
 
827
- # Update conversation state
828
- voice_conversation_state["transcript"] = chat_history
829
 
830
- return chat_history, ""
831
 
832
- except Exception as e:
833
- logger.error(f"Error in voice message: {str(e)}", exc_info=True)
834
 
835
- # Remove typing indicator if present
836
- if chat_history and chat_history[-1]["role"] == "assistant" and "Thinking" in chat_history[-1]["content"]:
837
- chat_history = chat_history[:-1]
838
 
839
- chat_history.append({
840
- "role": "assistant",
841
- "content": f"❌ An error occurred: {str(e)}\n\nPlease try again."
842
- })
843
- return chat_history, ""
844
-
845
- def test_voice_connection():
846
- """
847
- Test voice assistant connection
848
 
849
- Returns:
850
- Status message with test results
851
- """
852
- try:
853
- result = mcp_server.run_async(
854
- mcp_server.voice_tool.test_connection()
855
- )
856
 
857
- if result.get("success"):
858
- return (
859
- "✅ **Connection Test Passed**\n\n"
860
- f"• API Status: Connected\n"
861
- f"• Voices Available: {result.get('voices_available', 0)}\n"
862
- f"• RAG Tool: {'✓ Working' if result.get('rag_tool_working') else '✗ Failed'}\n"
863
- f"• Client Tools: {'✓ Registered' if result.get('client_tools_registered') else '✗ Not Registered'}\n\n"
864
- "🎉 Voice assistant is ready to use!"
865
- )
866
- else:
867
- return (
868
- "❌ **Connection Test Failed**\n\n"
869
- f"Error: {result.get('message', 'Unknown error')}\n\n"
870
- "**Troubleshooting:**\n"
871
- "1. Verify ELEVENLABS_API_KEY in .env\n"
872
- "2. Check ELEVENLABS_AGENT_ID is set\n"
873
- "3. Ensure API key is valid\n"
874
- "4. Check internet connection"
875
- )
876
- except Exception as e:
877
- logger.error(f"Connection test error: {str(e)}")
878
- return (
879
- f"❌ **Test Error**\n\n{str(e)}\n\n"
880
- "Please check your configuration and try again."
881
- )
882
-
883
- def get_conversation_stats():
884
- """
885
- Get statistics about current conversation
886
 
887
- Returns:
888
- Formatted stats string
889
- """
890
- try:
891
- if not voice_conversation_state["active"]:
892
- return "ℹ️ No active conversation"
893
 
894
- transcript = voice_conversation_state["transcript"]
895
- user_msgs = sum(1 for msg in transcript if msg["role"] == "user")
896
- ai_msgs = sum(1 for msg in transcript if msg["role"] == "assistant")
897
 
898
- return (
899
- "📊 **Conversation Statistics**\n\n"
900
- f"��� Session ID: {voice_conversation_state['session_id'][:8]}...\n"
901
- f"• Your messages: {user_msgs}\n"
902
- f"• AI responses: {ai_msgs}\n"
903
- f"• Total exchanges: {user_msgs}\n"
904
- f"• Status: {'🟢 Active' if voice_conversation_state['active'] else '🔴 Inactive'}"
905
- )
906
- except Exception as e:
907
- logger.error(f"Error getting stats: {str(e)}")
908
- return f"❌ Error: {str(e)}"
909
 
910
  def generate_podcast_ui(doc_ids, style, duration, voice1, voice2):
911
  """UI wrapper for podcast generation"""
@@ -1340,120 +1351,120 @@ def create_gradio_interface():
1340
  outputs=[content_output_display]
1341
  )
1342
 
1343
- with gr.Tab("🎙️ Voice Assistant"):
1344
- # Simple header
1345
- gr.Markdown("### Ask questions about your documents using AI")
1346
 
1347
- with gr.Row():
1348
- # Compact left sidebar (25% width)
1349
- with gr.Column(scale=1):
1350
- # Status box
1351
- voice_status_display = gr.Textbox(
1352
- label="Status",
1353
- value="Click 'Start' to begin",
1354
- interactive=False,
1355
- lines=3,
1356
- max_lines=3
1357
- )
1358
 
1359
- # Control buttons stacked vertically
1360
- start_voice_btn = gr.Button(
1361
- "🎤 Start",
1362
- variant="primary",
1363
- size="lg"
1364
- )
1365
 
1366
- stop_voice_btn = gr.Button(
1367
- "⏹️ Stop",
1368
- variant="stop",
1369
- size="lg",
1370
- interactive=False
1371
- )
1372
 
1373
- test_connection_btn = gr.Button(
1374
- "🔧 Test",
1375
- variant="secondary",
1376
- size="sm"
1377
- )
1378
 
1379
- gr.Markdown("---")
1380
 
1381
- # Quick tips
1382
- gr.Markdown("""
1383
- **Quick Tips:**
1384
- • Upload documents first
1385
- • Ask specific questions
1386
- • Press Enter to send
1387
- """, elem_classes=["small-text"])
1388
 
1389
- # Main chat area (75% width)
1390
- with gr.Column(scale=3):
1391
- # Large chat window
1392
- voice_chatbot = gr.Chatbot(
1393
- type="messages",
1394
- height=550,
1395
- show_copy_button=True,
1396
- avatar_images=(None, "🤖"),
1397
- show_label=False,
1398
- container=True,
1399
- bubble_full_width=False
1400
- )
1401
 
1402
- # Input row
1403
- with gr.Row():
1404
- voice_input_text = gr.Textbox(
1405
- placeholder="Ask me anything about your documents...",
1406
- lines=2,
1407
- max_lines=4,
1408
- scale=4,
1409
- show_label=False,
1410
- container=False,
1411
- autofocus=True
1412
- )
1413
- send_voice_btn = gr.Button(
1414
- "Send",
1415
- scale=1,
1416
- variant="primary"
1417
- )
1418
 
1419
- # Footer actions
1420
- with gr.Row():
1421
- clear_chat_btn = gr.Button("Clear", size="sm")
1422
- with gr.Column(scale=3):
1423
- gr.Markdown("*Tip: Type your question and press Enter*")
1424
 
1425
- # Event handlers
1426
- start_voice_btn.click(
1427
- fn=start_voice_conversation,
1428
- outputs=[voice_status_display, start_voice_btn, stop_voice_btn, voice_chatbot]
1429
- )
1430
 
1431
- stop_voice_btn.click(
1432
- fn=stop_voice_conversation,
1433
- outputs=[voice_status_display, start_voice_btn, stop_voice_btn, voice_chatbot]
1434
- )
1435
 
1436
- send_voice_btn.click(
1437
- fn=send_voice_message_v6,
1438
- inputs=[voice_input_text, voice_chatbot],
1439
- outputs=[voice_chatbot, voice_input_text]
1440
- )
1441
 
1442
- voice_input_text.submit(
1443
- fn=send_voice_message_v6,
1444
- inputs=[voice_input_text, voice_chatbot],
1445
- outputs=[voice_chatbot, voice_input_text]
1446
- )
1447
 
1448
- clear_chat_btn.click(
1449
- fn=lambda: [],
1450
- outputs=[voice_chatbot]
1451
- )
1452
 
1453
- test_connection_btn.click(
1454
- fn=test_voice_connection,
1455
- outputs=[voice_status_display]
1456
- )
1457
 
1458
  with gr.Tab("🎧 Podcast Studio"):
1459
  gr.Markdown("""
@@ -1636,7 +1647,7 @@ def create_gradio_interface():
1636
  show_copy_button=True
1637
  )
1638
 
1639
- all_dropdowns_to_update = [delete_doc_dropdown_visible, doc_dropdown_content, doc_dropdown_tag_visible,podcast_doc_selector]
1640
 
1641
  refresh_outputs = [document_list_display] + [dd for dd in all_dropdowns_to_update]
1642
  refresh_btn_library.click(fn=refresh_library, outputs=refresh_outputs)
 
271
  return {"success": False, "error": str(e)}
272
 
273
  mcp_server = ContentOrganizerMCPServer()
274
+ try:
275
+ print("⏳ Initializing LlamaIndex Service...")
276
+ # Use the fixed run_async method to safely initialize
277
+ mcp_server.run_async(mcp_server.llamaindex_service.initialize())
278
+ print("✅ LlamaIndex Initialized Successfully!")
279
+ except Exception as e:
280
+ print(f"⚠️ Warning during LlamaIndex init: {e}")
281
+
282
 
283
  def get_document_list():
284
  try:
 
353
  file_type = Path(file_path).suffix.lower().strip('.')
354
  logger.info(f"Processing file: {file_path}, type: {file_type}")
355
  result = mcp_server.run_async(mcp_server.ingest_document_async(file_path, file_type))
356
+ if result["success"]:
357
+ logger.info("Syncing LlamaIndex with new document...")
358
+ mcp_server.run_async(mcp_server.llamaindex_service.sync_on_demand())
359
 
360
  doc_list_updated = get_document_list()
361
  doc_choices_updated = get_document_choices()
 
645
  "transcript": []
646
  }
647
 
648
+ # voice_conversation_state = {
649
+ # "session_id": None,
650
+ # "active": False,
651
+ # "transcript": []
652
+ # }
653
 
654
+ # def start_voice_conversation():
655
+ # """
656
+ # Start a new voice conversation session
657
 
658
+ # Returns:
659
+ # Tuple of (status_message, start_button_state, stop_button_state, chatbot_history)
660
+ # """
661
+ # try:
662
+ # # Check if service is available
663
+ # if not mcp_server.elevenlabs_service.is_available():
664
+ # return (
665
+ # "⚠️ Voice assistant not configured.\n\n"
666
+ # "**Setup Instructions:**\n"
667
+ # "1. Get API key from: https://elevenlabs.io/app/settings/api-keys\n"
668
+ # "2. Create an agent at: https://elevenlabs.io/app/conversational-ai\n"
669
+ # "3. Add to .env file:\n"
670
+ # " - ELEVENLABS_API_KEY=your_api_key\n"
671
+ # " - ELEVENLABS_AGENT_ID=your_agent_id\n"
672
+ # "4. Restart the application",
673
+ # gr.update(interactive=True), # start button enabled
674
+ # gr.update(interactive=False), # stop button disabled
675
+ # []
676
+ # )
677
 
678
+ # # Create new session
679
+ # session_id = str(uuid.uuid4())
680
+ # result = mcp_server.run_async(
681
+ # mcp_server.elevenlabs_service.start_conversation(session_id)
682
+ # )
683
 
684
+ # if result.get("success"):
685
+ # voice_conversation_state["session_id"] = session_id
686
+ # voice_conversation_state["active"] = True
687
+ # voice_conversation_state["transcript"] = []
688
 
689
+ # # Initialize chatbot with welcome message
690
+ # initial_message = {
691
+ # "role": "assistant",
692
+ # "content": "👋 Hello! I'm your AI librarian. Ask me anything about your documents!"
693
+ # }
694
 
695
+ # return (
696
+ # "✅ Voice assistant is ready!\n\n"
697
+ # "You can now ask questions about your uploaded documents.",
698
+ # gr.update(interactive=False), # start button disabled
699
+ # gr.update(interactive=True), # stop button enabled
700
+ # [initial_message]
701
+ # )
702
+ # else:
703
+ # error_msg = result.get("error", "Unknown error")
704
+ # return (
705
+ # f"❌ Failed to start: {error_msg}\n\n"
706
+ # "**Troubleshooting:**\n"
707
+ # "• Check your API key is valid\n"
708
+ # "• Verify agent ID is correct\n"
709
+ # "• Check internet connection",
710
+ # gr.update(interactive=True),
711
+ # gr.update(interactive=False),
712
+ # []
713
+ # )
714
+ # except Exception as e:
715
+ # logger.error(f"Error starting voice conversation: {str(e)}", exc_info=True)
716
+ # return (
717
+ # f"❌ Error: {str(e)}",
718
+ # gr.update(interactive=True),
719
+ # gr.update(interactive=False),
720
+ # []
721
+ # )
722
+
723
+ # def stop_voice_conversation():
724
+ # """
725
+ # Stop active voice conversation
726
 
727
+ # Returns:
728
+ # Tuple of (status_message, start_button_state, stop_button_state, chatbot_history)
729
+ # """
730
+ # try:
731
+ # if not voice_conversation_state["active"]:
732
+ # return (
733
+ # "ℹ️ No active conversation",
734
+ # gr.update(interactive=True),
735
+ # gr.update(interactive=False),
736
+ # voice_conversation_state["transcript"]
737
+ # )
738
 
739
+ # session_id = voice_conversation_state["session_id"]
740
+ # if session_id:
741
+ # mcp_server.run_async(
742
+ # mcp_server.elevenlabs_service.end_conversation(session_id)
743
+ # )
744
 
745
+ # # Get conversation stats
746
+ # message_count = len(voice_conversation_state["transcript"])
747
 
748
+ # voice_conversation_state["active"] = False
749
+ # voice_conversation_state["session_id"] = None
750
 
751
+ # return (
752
+ # f"✅ Conversation ended\n\n"
753
+ # f"📊 Stats: {message_count} messages exchanged",
754
+ # gr.update(interactive=True),
755
+ # gr.update(interactive=False),
756
+ # voice_conversation_state["transcript"]
757
+ # )
758
+ # except Exception as e:
759
+ # logger.error(f"Error stopping conversation: {str(e)}")
760
+ # return (
761
+ # f"❌ Error: {str(e)}",
762
+ # gr.update(interactive=True),
763
+ # gr.update(interactive=False),
764
+ # voice_conversation_state["transcript"]
765
+ # )
766
+
767
+ # def send_voice_message_v6(message, chat_history):
768
+ # """
769
+ # Send message in voice conversation - Gradio 6+ format
770
 
771
+ # Args:
772
+ # message: User's text message
773
+ # chat_history: Current chat history (list of message dicts)
774
 
775
+ # Returns:
776
+ # Tuple of (updated_chat_history, cleared_input_box)
777
+ # """
778
+ # try:
779
+ # # Validate state
780
+ # if not voice_conversation_state["active"]:
781
+ # chat_history.append({
782
+ # "role": "assistant",
783
+ # "content": "⚠️ Please start a conversation first by clicking 'Start Conversation'"
784
+ # })
785
+ # return chat_history, ""
786
 
787
+ # # Validate input
788
+ # if not message or not message.strip():
789
+ # return chat_history, message
790
 
791
+ # session_id = voice_conversation_state["session_id"]
792
 
793
+ # # Add user message to display
794
+ # chat_history.append({
795
+ # "role": "user",
796
+ # "content": message
797
+ # })
798
 
799
+ # # Show typing indicator
800
+ # chat_history.append({
801
+ # "role": "assistant",
802
+ # "content": "🤔 Thinking..."
803
+ # })
804
 
805
+ # # Get AI response
806
+ # result = mcp_server.run_async(
807
+ # mcp_server.voice_tool.voice_qa(message, session_id)
808
+ # )
809
 
810
+ # # Remove typing indicator
811
+ # chat_history = chat_history[:-1]
812
 
813
+ # # Add response
814
+ # if result.get("success"):
815
+ # answer = result.get("answer", "No response")
816
 
817
+ # # Add helpful context if RAG was used
818
+ # if "document" in answer.lower() or "file" in answer.lower():
819
+ # footer = "\n\n💡 *Answer based on your documents*"
820
+ # else:
821
+ # footer = ""
822
 
823
+ # chat_history.append({
824
+ # "role": "assistant",
825
+ # "content": answer + footer
826
+ # })
827
+ # else:
828
+ # error_msg = result.get("error", "Unknown error")
829
+ # chat_history.append({
830
+ # "role": "assistant",
831
+ # "content": f"❌ Error: {error_msg}\n\n"
832
+ # "**Suggestions:**\n"
833
+ # "• Try rephrasing your question\n"
834
+ # "• Make sure you have uploaded relevant documents\n"
835
+ # "• Check if the question is about your document library"
836
+ # })
837
 
838
+ # # Update conversation state
839
+ # voice_conversation_state["transcript"] = chat_history
840
 
841
+ # return chat_history, ""
842
 
843
+ # except Exception as e:
844
+ # logger.error(f"Error in voice message: {str(e)}", exc_info=True)
845
 
846
+ # # Remove typing indicator if present
847
+ # if chat_history and chat_history[-1]["role"] == "assistant" and "Thinking" in chat_history[-1]["content"]:
848
+ # chat_history = chat_history[:-1]
849
 
850
+ # chat_history.append({
851
+ # "role": "assistant",
852
+ # "content": f"❌ An error occurred: {str(e)}\n\nPlease try again."
853
+ # })
854
+ # return chat_history, ""
855
+
856
+ # def test_voice_connection():
857
+ # """
858
+ # Test voice assistant connection
859
 
860
+ # Returns:
861
+ # Status message with test results
862
+ # """
863
+ # try:
864
+ # result = mcp_server.run_async(
865
+ # mcp_server.voice_tool.test_connection()
866
+ # )
867
 
868
+ # if result.get("success"):
869
+ # return (
870
+ # "✅ **Connection Test Passed**\n\n"
871
+ # f"• API Status: Connected\n"
872
+ # f"• Voices Available: {result.get('voices_available', 0)}\n"
873
+ # f"• RAG Tool: {'✓ Working' if result.get('rag_tool_working') else '✗ Failed'}\n"
874
+ # f"• Client Tools: {'✓ Registered' if result.get('client_tools_registered') else '✗ Not Registered'}\n\n"
875
+ # "🎉 Voice assistant is ready to use!"
876
+ # )
877
+ # else:
878
+ # return (
879
+ # "❌ **Connection Test Failed**\n\n"
880
+ # f"Error: {result.get('message', 'Unknown error')}\n\n"
881
+ # "**Troubleshooting:**\n"
882
+ # "1. Verify ELEVENLABS_API_KEY in .env\n"
883
+ # "2. Check ELEVENLABS_AGENT_ID is set\n"
884
+ # "3. Ensure API key is valid\n"
885
+ # "4. Check internet connection"
886
+ # )
887
+ # except Exception as e:
888
+ # logger.error(f"Connection test error: {str(e)}")
889
+ # return (
890
+ # f"❌ **Test Error**\n\n{str(e)}\n\n"
891
+ # "Please check your configuration and try again."
892
+ # )
893
+
894
+ # def get_conversation_stats():
895
+ # """
896
+ # Get statistics about current conversation
897
 
898
+ # Returns:
899
+ # Formatted stats string
900
+ # """
901
+ # try:
902
+ # if not voice_conversation_state["active"]:
903
+ # return "ℹ️ No active conversation"
904
 
905
+ # transcript = voice_conversation_state["transcript"]
906
+ # user_msgs = sum(1 for msg in transcript if msg["role"] == "user")
907
+ # ai_msgs = sum(1 for msg in transcript if msg["role"] == "assistant")
908
 
909
+ # return (
910
+ # "📊 **Conversation Statistics**\n\n"
911
+ # f" Session ID: {voice_conversation_state['session_id'][:8]}...\n"
912
+ # f"• Your messages: {user_msgs}\n"
913
+ # f"• AI responses: {ai_msgs}\n"
914
+ # f"• Total exchanges: {user_msgs}\n"
915
+ # f"• Status: {'🟢 Active' if voice_conversation_state['active'] else '🔴 Inactive'}"
916
+ # )
917
+ # except Exception as e:
918
+ # logger.error(f"Error getting stats: {str(e)}")
919
+ # return f"❌ Error: {str(e)}"
920
 
921
  def generate_podcast_ui(doc_ids, style, duration, voice1, voice2):
922
  """UI wrapper for podcast generation"""
 
1351
  outputs=[content_output_display]
1352
  )
1353
 
1354
+ # with gr.Tab("🎙️ Voice Assistant"):
1355
+ # # Simple header
1356
+ # gr.Markdown("### Ask questions about your documents using AI")
1357
 
1358
+ # with gr.Row():
1359
+ # # Compact left sidebar (25% width)
1360
+ # with gr.Column(scale=1):
1361
+ # # Status box
1362
+ # voice_status_display = gr.Textbox(
1363
+ # label="Status",
1364
+ # value="Click 'Start' to begin",
1365
+ # interactive=False,
1366
+ # lines=3,
1367
+ # max_lines=3
1368
+ # )
1369
 
1370
+ # # Control buttons stacked vertically
1371
+ # start_voice_btn = gr.Button(
1372
+ # "🎤 Start",
1373
+ # variant="primary",
1374
+ # size="lg"
1375
+ # )
1376
 
1377
+ # stop_voice_btn = gr.Button(
1378
+ # "⏹️ Stop",
1379
+ # variant="stop",
1380
+ # size="lg",
1381
+ # interactive=False
1382
+ # )
1383
 
1384
+ # test_connection_btn = gr.Button(
1385
+ # "🔧 Test",
1386
+ # variant="secondary",
1387
+ # size="sm"
1388
+ # )
1389
 
1390
+ # gr.Markdown("---")
1391
 
1392
+ # # Quick tips
1393
+ # gr.Markdown("""
1394
+ # **Quick Tips:**
1395
+ # • Upload documents first
1396
+ # • Ask specific questions
1397
+ # • Press Enter to send
1398
+ # """, elem_classes=["small-text"])
1399
 
1400
+ # # Main chat area (75% width)
1401
+ # with gr.Column(scale=3):
1402
+ # # Large chat window
1403
+ # voice_chatbot = gr.Chatbot(
1404
+ # type="messages",
1405
+ # height=550,
1406
+ # show_copy_button=True,
1407
+ # avatar_images=(None, "🤖"),
1408
+ # show_label=False,
1409
+ # container=True,
1410
+ # bubble_full_width=False
1411
+ # )
1412
 
1413
+ # # Input row
1414
+ # with gr.Row():
1415
+ # voice_input_text = gr.Textbox(
1416
+ # placeholder="Ask me anything about your documents...",
1417
+ # lines=2,
1418
+ # max_lines=4,
1419
+ # scale=4,
1420
+ # show_label=False,
1421
+ # container=False,
1422
+ # autofocus=True
1423
+ # )
1424
+ # send_voice_btn = gr.Button(
1425
+ # "Send",
1426
+ # scale=1,
1427
+ # variant="primary"
1428
+ # )
1429
 
1430
+ # # Footer actions
1431
+ # with gr.Row():
1432
+ # clear_chat_btn = gr.Button("Clear", size="sm")
1433
+ # with gr.Column(scale=3):
1434
+ # gr.Markdown("*Tip: Type your question and press Enter*")
1435
 
1436
+ # # Event handlers
1437
+ # start_voice_btn.click(
1438
+ # fn=start_voice_conversation,
1439
+ # outputs=[voice_status_display, start_voice_btn, stop_voice_btn, voice_chatbot]
1440
+ # )
1441
 
1442
+ # stop_voice_btn.click(
1443
+ # fn=stop_voice_conversation,
1444
+ # outputs=[voice_status_display, start_voice_btn, stop_voice_btn, voice_chatbot]
1445
+ # )
1446
 
1447
+ # send_voice_btn.click(
1448
+ # fn=send_voice_message_v6,
1449
+ # inputs=[voice_input_text, voice_chatbot],
1450
+ # outputs=[voice_chatbot, voice_input_text]
1451
+ # )
1452
 
1453
+ # voice_input_text.submit(
1454
+ # fn=send_voice_message_v6,
1455
+ # inputs=[voice_input_text, voice_chatbot],
1456
+ # outputs=[voice_chatbot, voice_input_text]
1457
+ # )
1458
 
1459
+ # clear_chat_btn.click(
1460
+ # fn=lambda: [],
1461
+ # outputs=[voice_chatbot]
1462
+ # )
1463
 
1464
+ # test_connection_btn.click(
1465
+ # fn=test_voice_connection,
1466
+ # outputs=[voice_status_display]
1467
+ # )
1468
 
1469
  with gr.Tab("🎧 Podcast Studio"):
1470
  gr.Markdown("""
 
1647
  show_copy_button=True
1648
  )
1649
 
1650
+ all_dropdowns_to_update = [delete_doc_dropdown_visible, doc_dropdown_content,podcast_doc_selector]
1651
 
1652
  refresh_outputs = [document_list_display] + [dd for dd in all_dropdowns_to_update]
1653
  refresh_btn_library.click(fn=refresh_library, outputs=refresh_outputs)
services/elevenlabs_service.py CHANGED
@@ -67,21 +67,23 @@ class ElevenLabsService:
67
  def _init_client_tools(self):
68
  """Initialize client tools for RAG integration"""
69
  try:
70
- # Get or create event loop for ClientTools
71
  try:
72
- loop = asyncio.get_running_loop()
73
- except RuntimeError:
74
- loop = asyncio.new_event_loop()
75
- asyncio.set_event_loop(loop)
76
-
77
- # Initialize ClientTools with the loop
78
- self.client_tools = ClientTools(loop=loop)
79
-
 
 
80
  # Register RAG query tool with proper metadata
81
  self.client_tools.register(
82
  "query_documents",
83
  handler=self._rag_query_handler,
84
- description="Search through the user's uploaded documents to find relevant information. Use this tool whenever the user asks questions about their documents, files, or content in their library.",
85
  parameters={
86
  "query": {
87
  "type": "string",
@@ -95,6 +97,7 @@ class ElevenLabsService:
95
 
96
  except Exception as e:
97
  logger.error(f"Error initializing client tools: {str(e)}")
 
98
  self.client_tools = None
99
 
100
  async def _rag_query_handler(self, params: Dict[str, Any]) -> Dict[str, Any]:
 
67
  def _init_client_tools(self):
68
  """Initialize client tools for RAG integration"""
69
  try:
70
+ # FIX: Try initializing without arguments first (Newer SDKs)
71
  try:
72
+ self.client_tools = ClientTools()
73
+ except TypeError:
74
+ # Fallback for older SDKs that might require a loop
75
+ try:
76
+ loop = asyncio.get_event_loop()
77
+ except RuntimeError:
78
+ loop = asyncio.new_event_loop()
79
+ asyncio.set_event_loop(loop)
80
+ self.client_tools = ClientTools(loop=loop)
81
+
82
  # Register RAG query tool with proper metadata
83
  self.client_tools.register(
84
  "query_documents",
85
  handler=self._rag_query_handler,
86
+ description="Search through the user's uploaded documents. Use this tool whenever the user asks questions about their documents, files, or content in their library.",
87
  parameters={
88
  "query": {
89
  "type": "string",
 
97
 
98
  except Exception as e:
99
  logger.error(f"Error initializing client tools: {str(e)}")
100
+ # Keep client_tools as None so we know it failed
101
  self.client_tools = None
102
 
103
  async def _rag_query_handler(self, params: Dict[str, Any]) -> Dict[str, Any]:
services/llamaindex_service.py CHANGED
@@ -15,8 +15,6 @@ from llama_index.core import (
15
  )
16
  from llama_index.core.tools import QueryEngineTool, ToolMetadata
17
  from llama_index.core.agent import ReActAgent
18
- from llama_index.core.selectors import LLMSingleSelector
19
- from llama_index.core.query_engine import RouterQueryEngine
20
  from llama_index.llms.openai import OpenAI
21
  from llama_index.embeddings.huggingface import HuggingFaceEmbedding
22
  from llama_index.embeddings.openai import OpenAIEmbedding
@@ -36,8 +34,7 @@ class LlamaIndexService:
36
  self.is_initialized = False
37
 
38
  self._initialize_settings()
39
- # We don't fully initialize index here because we need async access to doc store
40
- # But we try to load existing storage if available
41
  self._try_load_from_storage()
42
 
43
  def _initialize_settings(self):
@@ -45,11 +42,9 @@ class LlamaIndexService:
45
  try:
46
  # LLM Setup
47
  if self.config.OPENAI_API_KEY:
48
- # Use configured OpenAI model (gpt-5.1-chat-latest or similar)
49
  Settings.llm = OpenAI(model=self.config.OPENAI_MODEL, api_key=self.config.OPENAI_API_KEY)
50
  logger.info(f"LlamaIndex using OpenAI model: {self.config.OPENAI_MODEL}")
51
  elif self.config.NEBIUS_API_KEY:
52
- # Use Nebius as OpenAI-compatible provider
53
  Settings.llm = OpenAI(
54
  model=self.config.NEBIUS_MODEL,
55
  api_key=self.config.NEBIUS_API_KEY,
@@ -57,7 +52,7 @@ class LlamaIndexService:
57
  )
58
  logger.info(f"LlamaIndex using Nebius model: {self.config.NEBIUS_MODEL}")
59
  else:
60
- logger.warning("No API key found for LlamaIndex LLM (OpenAI or Nebius). Agentic features may fail.")
61
 
62
  # Embedding Setup
63
  if self.config.EMBEDDING_MODEL.startswith("text-embedding-"):
@@ -66,9 +61,7 @@ class LlamaIndexService:
66
  model=self.config.EMBEDDING_MODEL,
67
  api_key=self.config.OPENAI_API_KEY
68
  )
69
- logger.info(f"LlamaIndex using OpenAI embeddings: {self.config.EMBEDDING_MODEL}")
70
  else:
71
- logger.warning("OpenAI embedding model requested but no API key found. Falling back to HuggingFace.")
72
  Settings.embed_model = HuggingFaceEmbedding(
73
  model_name="sentence-transformers/all-MiniLM-L6-v2"
74
  )
@@ -76,7 +69,6 @@ class LlamaIndexService:
76
  Settings.embed_model = HuggingFaceEmbedding(
77
  model_name=self.config.EMBEDDING_MODEL
78
  )
79
- logger.info(f"LlamaIndex using HuggingFace embeddings: {self.config.EMBEDDING_MODEL}")
80
 
81
  except Exception as e:
82
  logger.error(f"Error initializing LlamaIndex settings: {str(e)}")
@@ -84,14 +76,14 @@ class LlamaIndexService:
84
  def _try_load_from_storage(self):
85
  """Try to load index from storage synchronously"""
86
  try:
87
- if self.storage_dir.exists():
88
  logger.info("Loading LlamaIndex from storage...")
89
  storage_context = StorageContext.from_defaults(persist_dir=str(self.storage_dir))
90
  self.index = load_index_from_storage(storage_context)
91
  self._initialize_agent()
92
  self.is_initialized = True
93
  else:
94
- logger.info("No existing LlamaIndex storage found. Waiting for async initialization.")
95
  except Exception as e:
96
  logger.error(f"Error loading LlamaIndex from storage: {str(e)}")
97
 
@@ -99,15 +91,11 @@ class LlamaIndexService:
99
  """Async initialization to sync documents and build index"""
100
  try:
101
  logger.info("Starting LlamaIndex async initialization...")
102
-
103
- # If we already have an index, we might still want to sync if it's empty or stale
104
- # For now, if no index exists, we definitely need to build it
105
  if self.index is None:
106
  await self.sync_from_document_store()
107
 
108
  self.is_initialized = True
109
  logger.info("LlamaIndex async initialization complete.")
110
-
111
  except Exception as e:
112
  logger.error(f"Error during LlamaIndex async initialization: {str(e)}")
113
 
@@ -116,18 +104,19 @@ class LlamaIndexService:
116
  try:
117
  logger.info("Syncing documents from DocumentStore to LlamaIndex...")
118
 
119
- # Fetch documents from async document store
120
- # Limit to 1000 for now to avoid memory issues
121
  docs = await self.document_store.list_documents(limit=1000)
122
 
123
  if not docs:
124
- logger.warning("No documents found in DocumentStore to sync.")
125
- # Create empty index if no docs
126
- self.index = VectorStoreIndex.from_documents([])
127
- else:
128
- # Convert to LlamaIndex documents
129
- llama_docs = []
130
- for doc in docs:
 
 
 
131
  llama_doc = Document(
132
  text=doc.content,
133
  metadata={
@@ -137,9 +126,13 @@ class LlamaIndexService:
137
  }
138
  )
139
  llama_docs.append(llama_doc)
140
-
141
- logger.info(f"Building LlamaIndex with {len(llama_docs)} documents...")
142
- self.index = VectorStoreIndex.from_documents(llama_docs)
 
 
 
 
143
 
144
  # Persist storage
145
  if not self.storage_dir.exists():
@@ -174,6 +167,7 @@ class LlamaIndexService:
174
  )
175
  )
176
 
 
177
  self.agent = ReActAgent.from_tools(
178
  [query_engine_tool],
179
  llm=Settings.llm,
@@ -186,14 +180,24 @@ class LlamaIndexService:
186
 
187
  async def query(self, query_text: str) -> str:
188
  """Process a query using the agent"""
 
 
 
 
 
 
 
189
  if not self.agent:
190
- if not self.is_initialized:
191
- return "Agent is initializing, please try again in a moment."
192
- return "Agent failed to initialize. Please check logs."
 
 
193
 
194
  try:
 
195
  response = await self.agent.achat(query_text)
196
  return str(response)
197
  except Exception as e:
198
  logger.error(f"Error querying LlamaIndex agent: {str(e)}")
199
- return f"Error processing query: {str(e)}"
 
15
  )
16
  from llama_index.core.tools import QueryEngineTool, ToolMetadata
17
  from llama_index.core.agent import ReActAgent
 
 
18
  from llama_index.llms.openai import OpenAI
19
  from llama_index.embeddings.huggingface import HuggingFaceEmbedding
20
  from llama_index.embeddings.openai import OpenAIEmbedding
 
34
  self.is_initialized = False
35
 
36
  self._initialize_settings()
37
+ # Attempt to load existing index, but don't fail if empty
 
38
  self._try_load_from_storage()
39
 
40
  def _initialize_settings(self):
 
42
  try:
43
  # LLM Setup
44
  if self.config.OPENAI_API_KEY:
 
45
  Settings.llm = OpenAI(model=self.config.OPENAI_MODEL, api_key=self.config.OPENAI_API_KEY)
46
  logger.info(f"LlamaIndex using OpenAI model: {self.config.OPENAI_MODEL}")
47
  elif self.config.NEBIUS_API_KEY:
 
48
  Settings.llm = OpenAI(
49
  model=self.config.NEBIUS_MODEL,
50
  api_key=self.config.NEBIUS_API_KEY,
 
52
  )
53
  logger.info(f"LlamaIndex using Nebius model: {self.config.NEBIUS_MODEL}")
54
  else:
55
+ logger.warning("No API key found for LlamaIndex LLM. Agentic features may fail.")
56
 
57
  # Embedding Setup
58
  if self.config.EMBEDDING_MODEL.startswith("text-embedding-"):
 
61
  model=self.config.EMBEDDING_MODEL,
62
  api_key=self.config.OPENAI_API_KEY
63
  )
 
64
  else:
 
65
  Settings.embed_model = HuggingFaceEmbedding(
66
  model_name="sentence-transformers/all-MiniLM-L6-v2"
67
  )
 
69
  Settings.embed_model = HuggingFaceEmbedding(
70
  model_name=self.config.EMBEDDING_MODEL
71
  )
 
72
 
73
  except Exception as e:
74
  logger.error(f"Error initializing LlamaIndex settings: {str(e)}")
 
76
  def _try_load_from_storage(self):
77
  """Try to load index from storage synchronously"""
78
  try:
79
+ if self.storage_dir.exists() and any(self.storage_dir.iterdir()):
80
  logger.info("Loading LlamaIndex from storage...")
81
  storage_context = StorageContext.from_defaults(persist_dir=str(self.storage_dir))
82
  self.index = load_index_from_storage(storage_context)
83
  self._initialize_agent()
84
  self.is_initialized = True
85
  else:
86
+ logger.info("No existing LlamaIndex storage found. Waiting for initialization.")
87
  except Exception as e:
88
  logger.error(f"Error loading LlamaIndex from storage: {str(e)}")
89
 
 
91
  """Async initialization to sync documents and build index"""
92
  try:
93
  logger.info("Starting LlamaIndex async initialization...")
 
 
 
94
  if self.index is None:
95
  await self.sync_from_document_store()
96
 
97
  self.is_initialized = True
98
  logger.info("LlamaIndex async initialization complete.")
 
99
  except Exception as e:
100
  logger.error(f"Error during LlamaIndex async initialization: {str(e)}")
101
 
 
104
  try:
105
  logger.info("Syncing documents from DocumentStore to LlamaIndex...")
106
 
 
 
107
  docs = await self.document_store.list_documents(limit=1000)
108
 
109
  if not docs:
110
+ logger.warning("No documents found in DocumentStore. Creating empty index.")
111
+ # FIX: Handle empty state gracefully
112
+ self.index = None
113
+ self.agent = None
114
+ return
115
+
116
+ # Convert to LlamaIndex documents
117
+ llama_docs = []
118
+ for doc in docs:
119
+ if doc.content and len(doc.content.strip()) > 0:
120
  llama_doc = Document(
121
  text=doc.content,
122
  metadata={
 
126
  }
127
  )
128
  llama_docs.append(llama_doc)
129
+
130
+ if not llama_docs:
131
+ logger.warning("Documents found but content was empty.")
132
+ return
133
+
134
+ logger.info(f"Building LlamaIndex with {len(llama_docs)} documents...")
135
+ self.index = VectorStoreIndex.from_documents(llama_docs)
136
 
137
  # Persist storage
138
  if not self.storage_dir.exists():
 
167
  )
168
  )
169
 
170
+ # ReAct Agent requires an LLM
171
  self.agent = ReActAgent.from_tools(
172
  [query_engine_tool],
173
  llm=Settings.llm,
 
180
 
181
  async def query(self, query_text: str) -> str:
182
  """Process a query using the agent"""
183
+
184
+ # 1. AUTO-RECOVERY: If agent is missing, try to initialize it now
185
+ if not self.agent:
186
+ logger.info("Agent not found during query. Attempting to initialize...")
187
+ await self.initialize()
188
+
189
+ # 2. Check if it's still missing after attempt
190
  if not self.agent:
191
+ # Check why it failed
192
+ if not self.index:
193
+ return "I can't answer that yet because there are no documents in the library. Please upload a document first."
194
+
195
+ return "System Error: The AI agent failed to start. Please check if your OPENAI_API_KEY is correct in the .env file."
196
 
197
  try:
198
+ # 3. Run the query
199
  response = await self.agent.achat(query_text)
200
  return str(response)
201
  except Exception as e:
202
  logger.error(f"Error querying LlamaIndex agent: {str(e)}")
203
+ return f"I encountered an error searching the documents: {str(e)}"