from __future__ import annotations import os from pathlib import Path import yaml import gradio as gr from typing import Optional from langchain_core.prompts import ChatPromptTemplate from langchain_core.messages import HumanMessage, SystemMessage from config.settings import settings from forms.schemas import ( SOAPNote, DAPNote, BIRPNote, PIRPNote, GIRPNote, SIRPNote, FAIRFDARPNote, DARENote, PIENote, SOAPIERNote, SOAPIENote, POMRNote, NarrativeNote, CBENote, SBARNote ) from utils.youtube import download_transcript from utils.youtube import extract_youtube_video_id from utils.text_processing import chunk_text from utils.audio import transcribe_audio from models.llm_provider import get_llm, get_model_identifier from utils.cache import CacheManager from config.auth import load_auth_credentials # Dictionary mapping form types to their schemas FORM_SCHEMAS = { "SOAP": SOAPNote, "DAP": DAPNote, "BIRP": BIRPNote, "PIRP": PIRPNote, "GIRP": GIRPNote, "SIRP": SIRPNote, "FAIR/F-DARP": FAIRFDARPNote, "DARE": DARENote, "PIE": PIENote, "SOAPIER": SOAPIERNote, "SOAPIE": SOAPIENote, "POMR": POMRNote, "Narrative": NarrativeNote, "CBE": CBENote, "SBAR": SBARNote, } # Initialize cache manager cache_manager = CacheManager() def load_prompt(note_type: str) -> tuple[str, str]: """Load the prompt template from YAML for the specified note type.""" prompt_path = Path("langhub/prompts/therapy_extraction_prompt.yaml") with open(prompt_path, "r") as f: data = yaml.safe_load(f) note_prompts = data.get("prompts", {}).get(note_type.lower()) if not note_prompts: raise ValueError(f"No prompt template found for note type: {note_type}") return note_prompts["system"], note_prompts["human"] def process_input( input_text: str, form_type: str, input_type: str = "text", audio_file: str | None = None, force_refresh: bool = False ) -> str: """Process input (text, YouTube URL, or audio) and generate notes.""" try: # Get transcript based on input type if input_type == "audio" and audio_file: print("Processing audio file...") transcript = transcribe_audio(audio_file) elif "youtube.com" in input_text or "youtu.be" in input_text: print(f"Downloading transcript from YouTube...") video_id = extract_youtube_video_id(input_text) # Check cache first if not force_refresh: cached_transcript = cache_manager.get_transcript(video_id) if cached_transcript: print("Using cached transcript...") transcript = cached_transcript else: transcript = download_transcript(input_text) cache_manager.store_transcript(video_id, transcript) else: transcript = download_transcript(input_text) cache_manager.store_transcript(video_id, transcript) else: print("Using provided text directly...") transcript = input_text # Initialize LLM llm = get_llm() model_id = get_model_identifier(llm) # Check extraction cache if not force_refresh: cached_result = cache_manager.get_extraction( transcript, form_type.lower(), model_id ) if cached_result: print("Using cached extraction result...") formatted_response = yaml.dump( cached_result, default_flow_style=False, sort_keys=False ) return f"## {form_type} Note:\n```yaml\n{formatted_response}\n```" # Get schema for selected form type schema = FORM_SCHEMAS.get(form_type) if not schema: return f"Error: Unsupported form type {form_type}" # Create structured LLM structured_llm = llm.with_structured_output(schema=schema) # Load prompts system_prompt, human_prompt = load_prompt(form_type.lower()) # Create prompt template prompt = ChatPromptTemplate.from_messages([ ("system", system_prompt), ("human", human_prompt) ]) # Process transcript print(f"Generating {form_type} note...") response = structured_llm.invoke(transcript) # Store result in cache result_dict = response.model_dump(exclude_unset=False, exclude_none=False) cache_manager.store_extraction( transcript, form_type.lower(), result_dict, model_id ) # Format the response formatted_response = yaml.dump( result_dict, default_flow_style=False, sort_keys=False ) return f"## {form_type} Note:\n```yaml\n{formatted_response}\n```" except Exception as e: return f"Error: {str(e)}" def create_ui() -> gr.Blocks: """Create the Gradio interface.""" # Load authorized users from config auth = load_auth_credentials() def check_auth(username: str, password: str) -> bool: """Check if username and password are valid.""" return username in auth and auth[username] == password with gr.Blocks(title="Therapy Note Generator") as demo: # Login interface with gr.Row(): with gr.Column(): username = gr.Textbox(label="Username") password = gr.Textbox(label="Password", type="password") login_btn = gr.Button("Login") login_msg = gr.Markdown() # Main interface (initially invisible) with gr.Column(visible=False) as main_interface: gr.Markdown("# Therapy Note Generator") gr.Markdown(""" Enter a YouTube URL, paste a transcript directly, or upload an audio file. Select the desired note format and click 'Generate' to create a structured note. """) with gr.Row(): with gr.Column(): # Input type selector input_type = gr.Radio( choices=["text", "youtube", "audio"], value="text", label="Input Type", info="Choose how you want to provide the therapy session" ) # Text input for transcript or YouTube URL input_text = gr.Textbox( label="Text Input", placeholder="Enter transcript or YouTube URL here...", lines=10, visible=True ) # Audio upload audio_input = gr.Audio( label="Audio Input", type="filepath", visible=False ) # Note format selector form_type = gr.Dropdown( choices=list(FORM_SCHEMAS.keys()), value="SOAP", label="Note Format" ) generate_btn = gr.Button("Generate Note", variant="primary") with gr.Column(): # Transcript output transcript_output = gr.Textbox( label="Generated Transcript", lines=10, visible=False, interactive=False ) # Structured note output note_output = gr.Markdown(label="Generated Note") # Update visibility based on input type def update_inputs(choice): return { input_text: gr.update(visible=choice in ["text", "youtube"]), audio_input: gr.update(visible=choice == "audio"), transcript_output: gr.update(visible=choice in ["youtube", "audio"]) } input_type.change( fn=update_inputs, inputs=input_type, outputs=[input_text, audio_input, transcript_output] ) def process_and_show_transcript( input_text: str, form_type: str, input_type: str = "text", audio_file: str | None = None, force_refresh: bool = False ) -> tuple[str, str]: """Process input and return both transcript and structured note.""" try: # Get transcript based on input type if input_type == "audio" and audio_file: print("Processing audio file...") transcript = transcribe_audio(audio_file) elif "youtube.com" in input_text or "youtu.be" in input_text: print(f"Downloading transcript from YouTube...") video_id = extract_youtube_video_id(input_text) # Check cache first if not force_refresh: cached_transcript = cache_manager.get_transcript(video_id) if cached_transcript: print("Using cached transcript...") transcript = cached_transcript else: transcript = download_transcript(input_text) cache_manager.store_transcript(video_id, transcript) else: transcript = download_transcript(input_text) cache_manager.store_transcript(video_id, transcript) else: print("Using provided text directly...") transcript = input_text # Process the transcript to generate the note note_output = process_input(input_text, form_type, input_type, audio_file, force_refresh) return transcript, note_output except Exception as e: error_msg = f"Error: {str(e)}" return error_msg, error_msg # Handle generate button click generate_btn.click( fn=process_and_show_transcript, inputs=[input_text, form_type, input_type, audio_input], outputs=[transcript_output, note_output] ) # Example inputs try: with open("data/sample_note.txt", "r") as f: sample_text = f.read() except FileNotFoundError: sample_text = "Sample therapy session transcript..." gr.Examples( examples=[ # Text example [sample_text, "SOAP", "text", None], # YouTube examples ["https://www.youtube.com/watch?v=KuHLL2AE-SE", "DAP", "youtube", None], ["https://www.youtube.com/watch?v=jS1KE3_Pqlc", "SOAPIER", "youtube", None], # Audio example [None, "BIRP", "audio", "data/CBT Role-Play.mp3"] ], inputs=[input_text, form_type, input_type, audio_input], outputs=[transcript_output, note_output], fn=process_and_show_transcript, cache_examples=False, label="Example Inputs", examples_per_page=4 ) def login(username: str, password: str): """Handle login and return updates for UI components.""" if check_auth(username, password): return [ gr.update(visible=True), # main_interface gr.update(value="✅ Login successful!", visible=True), # login_msg gr.update(visible=False), # username gr.update(visible=False), # password gr.update(visible=False), # login_btn ] else: return [ gr.update(visible=False), # main_interface gr.update(value="❌ Invalid credentials", visible=True), # login_msg gr.update(), # username - no change gr.update(), # password - no change gr.update(), # login_btn - no change ] login_btn.click( fn=login, inputs=[username, password], outputs=[main_interface, login_msg, username, password, login_btn] ) return demo if __name__ == "__main__": # Clean up any existing Gradio cache cache_manager.cleanup_gradio_cache() demo = create_ui() demo.launch( server_name="0.0.0.0", server_port=7860, share=True, show_error=True, auth=None # We're using our own auth system instead of Gradio's )