| import os
|
| import time
|
| import json
|
| import keyboard
|
| import numpy as np
|
| import faiss
|
| from datetime import datetime
|
| from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
|
|
|
| from stt import AudioProcessor
|
| from llm import LLMProcessor
|
|
|
| from vectorstore import (
|
| update_vectorstore_from_pdf,
|
| save_vectorstore_with_timestamp,
|
| load_vectorstore,
|
| search_vectorstore
|
| )
|
|
|
| from tts import chunk_text_for_tts, synthesize_speech_guideline, play_audio
|
|
|
|
|
| def combine_vectorstores(index1, chunks1, index2, chunks2):
|
| """
|
| Combine two FAISS indexes (both IndexFlatL2) by retrieving all embeddings,
|
| concatenating them, and building a new FAISS index.
|
| Also combines the two lists of text chunks.
|
| """
|
| embeddings1 = np.array([index1.reconstruct(i) for i in range(index1.ntotal)])
|
| embeddings2 = np.array([index2.reconstruct(i) for i in range(index2.ntotal)])
|
| combined_embeddings = np.concatenate([embeddings1, embeddings2], axis=0)
|
| combined_chunks = chunks1 + chunks2
|
| dimension = combined_embeddings.shape[1]
|
| combined_index = faiss.IndexFlatL2(dimension)
|
| combined_index.add(combined_embeddings)
|
| return combined_index, combined_chunks
|
|
|
|
|
| def build_and_save_vectorstore():
|
| """
|
| Process two PDFs, combine their embeddings into one vectorstore,
|
| save the combined index with a timestamp, and return the index,
|
| chunks, and embedder used.
|
| """
|
| base_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
| pdf_path1 = os.path.join(base_dir, "data", "PDF", "cancer_dictionary.pdf")
|
| pdf_path2 = os.path.join(base_dir, "data", "PDF", "Medical Dictionary.pdf")
|
|
|
| index1, chunks1, embedder = update_vectorstore_from_pdf(pdf_path1, chunk_size=600)
|
| index2, chunks2, _ = update_vectorstore_from_pdf(pdf_path2, chunk_size=600)
|
|
|
| combined_index, combined_chunks = combine_vectorstores(index1, chunks1, index2, chunks2)
|
|
|
| saved_index_path = save_vectorstore_with_timestamp(combined_index)
|
| print(f"Combined vectorstore saved at: {saved_index_path}")
|
|
|
| loaded_index = load_vectorstore(saved_index_path)
|
| return loaded_index, combined_chunks, embedder
|
|
|
|
|
| def synthesize_audio_chunks(text, max_chars=300):
|
| """
|
| Breaks the given text into chunks and asynchronously synthesizes them.
|
| Yields the output file for each synthesized audio chunk.
|
| """
|
| if not text:
|
| raise ValueError("No text provided for TTS synthesis.")
|
|
|
|
|
| chunks = chunk_text_for_tts(text, max_chars=max_chars)
|
| print("TTS Text chunks:")
|
| for i, chunk in enumerate(chunks, start=1):
|
| print(f"Chunk {i}: {chunk}\n")
|
|
|
|
|
| with ThreadPoolExecutor(max_workers=4) as executor:
|
|
|
| future_to_index = {
|
| executor.submit(synthesize_speech_guideline, chunk, output_file=f"audio_chunk_{i}.wav"): i
|
| for i, chunk in enumerate(chunks, start=1)
|
| }
|
|
|
|
|
| for future in as_completed(future_to_index):
|
| index = future_to_index[future]
|
| try:
|
| audio_file = future.result()
|
| if audio_file is None:
|
| print(f"Warning: Synthesis for chunk {index} returned None.")
|
| else:
|
| print(f"Chunk {index} synthesized successfully: {audio_file}")
|
| yield audio_file
|
| except Exception as exc:
|
| print(f"Error synthesizing chunk {index}: {exc}")
|
|
|
| def synthesize_audio_chunks(text, max_chars=300):
|
| """
|
| Breaks the given text into chunks and asynchronously synthesizes them.
|
| Yields the output file for each synthesized audio chunk.
|
| """
|
| if not text:
|
| raise ValueError("No text provided for TTS synthesis.")
|
| chunks = chunk_text_for_tts(text, max_chars=max_chars)
|
| print("TTS Text chunks:")
|
| for i, chunk in enumerate(chunks, 1):
|
| print(f"Chunk {i}: {chunk}\n")
|
|
|
| with ThreadPoolExecutor(max_workers=4) as executor:
|
|
|
| futures = [executor.submit(synthesize_speech_guideline, chunk, output_file=f"audio_chunk_{i}.wav")
|
| for i, chunk in enumerate(chunks)]
|
|
|
| for future in futures:
|
| audio_file = future.result()
|
| yield audio_file
|
|
|
|
|
| def main():
|
| print("Building vectorstore from PDFs...")
|
| vector_index, vector_chunks, embedder = build_and_save_vectorstore()
|
|
|
| processor = AudioProcessor()
|
| llm_processor = LLMProcessor()
|
|
|
| print("\nLooping process: Use push-to-talk to record and transcribe.")
|
| print("Press 's' at any time to stop the program.")
|
| Saved_response = ""
|
|
|
| while True:
|
| if keyboard.is_pressed('s'):
|
| print("Stop key pressed. Exiting program.")
|
| break
|
|
|
| print("\nReady to record. Press 'q' to start and 'q' again to stop recording.\n----------------\n")
|
| denoised_audio, fs = processor.record_and_denoise()
|
|
|
| if denoised_audio is None:
|
| print("No audio was recorded. Skipping transcription.")
|
| else:
|
| print("Transcribing audio...")
|
| try:
|
| transcription = processor.transcribe_audio(processor.default_output_file, language="en")
|
| print("Transcribed Text:\n", transcription)
|
|
|
| search_results = search_vectorstore(transcription, embedder, vector_index, vector_chunks, top_k=3)
|
| query = Saved_response + transcription
|
| for i, result in enumerate(search_results, 1):
|
| query += f"\n{i}. {result}"
|
|
|
| try:
|
| gemini_response = llm_processor.call_gemini_llm("gemini-2.0-flash", query)
|
| print("Gemini response:", gemini_response)
|
| except Exception as e:
|
| print("Gemini error:", e)
|
| gemini_response = f"Error: {str(e)}"
|
|
|
| Saved_response += "transcription: " + transcription + "\n" + "gemini_response: " + gemini_response + "\n"
|
| processor.log_conversation(transcription, bot_text=gemini_response)
|
|
|
|
|
| print("Synthesizing and playing LLM response via TTS...")
|
| print()
|
| audio_generator = synthesize_audio_chunks(gemini_response, max_chars=300)
|
|
|
| try:
|
| first_audio = next(audio_generator)
|
| play_audio(first_audio)
|
| except StopIteration:
|
| print("No audio chunks generated.")
|
|
|
|
|
| for audio_file in audio_generator:
|
| play_audio(audio_file)
|
|
|
| except Exception as e:
|
| print("Error during transcription:", e)
|
|
|
| print("Iteration complete. Waiting 5 seconds before next recording...")
|
| time.sleep(5)
|
|
|
| print("Program terminated.")
|
|
|
|
|
|
|
| if __name__ == "__main__":
|
| main() |