Spaces:
Sleeping
Sleeping
| # app.py | |
| import gradio as gr | |
| import os | |
| import time | |
| import datetime | |
| import random | |
| import config | |
| from modules.tts_handler import text_to_speech_file | |
| from modules.stt_handler import transcribe_audio | |
| from modules.doc_processor import extract_text_from_document | |
| from modules.llm_handler import generate_coaching_question, evaluate_response, generate_coaching_feedback, get_overall_score, parse_scores_from_evaluation, get_score_interpretation | |
| from modules.report_generator import generate_pdf_report | |
| def test_recording(): | |
| """Simple test to enable recording""" | |
| return gr.update(interactive=True), "β Recording enabled! Try recording something now." | |
| def start_coaching_session(coaching_type, doc_file, name, num_questions): | |
| if not coaching_type: | |
| return ( | |
| {}, # state | |
| [{"role": "assistant", "content": "Please select a product management focus area to begin your coaching session."}], # chatbot | |
| None, # audio_out | |
| gr.update(interactive=False), # audio_in | |
| gr.update(interactive=True) # start_btn | |
| ) | |
| doc_text = "" | |
| if doc_file: | |
| doc_text = extract_text_from_document(doc_file.name) | |
| if "Error" in doc_text or "Unsupported" in doc_text: | |
| return ( | |
| {}, # state | |
| [{"role": "assistant", "content": f"Error processing document: {doc_text}. Continuing without document context."}], # chatbot | |
| None, # audio_out | |
| gr.update(interactive=False), # audio_in | |
| gr.update(interactive=True) # start_btn | |
| ) | |
| initial_state = { | |
| "coaching_type": coaching_type, | |
| "doc_text": doc_text, | |
| "name": name if name else "Product Manager", | |
| "question_count": int(num_questions), | |
| "current_question_num": 1, | |
| "coaching_log": [] | |
| } | |
| first_question = generate_coaching_question(coaching_type, doc_text, 1) | |
| initial_state["current_question_text"] = first_question | |
| greeting = f"Hello {initial_state['name']}! Welcome to your personal AI product coaching session on {coaching_type}. We'll explore {int(num_questions)} scenarios together. Let's start with the first one:" | |
| full_message = f"{greeting}\n\n{first_question}\n\nποΈ Click the microphone button above to record your response!" | |
| tts_prompt = f"{greeting} {first_question}" | |
| ai_voice_path = text_to_speech_file(tts_prompt) | |
| return ( | |
| initial_state, # state | |
| [{"role": "assistant", "content": full_message}], # chatbot | |
| ai_voice_path, # audio_out | |
| gr.update(interactive=True), # audio_in - ENABLE recording | |
| gr.update(interactive=False) # start_btn - disable after starting | |
| ) | |
| def handle_coaching_turn(user_audio, chatbot_history, current_state): | |
| """Handle each coaching turn - one question at a time with immediate feedback""" | |
| print(f"ποΈ Audio recording received: {user_audio}") | |
| print(f"π Current state: {current_state is not None}") | |
| print(f"π¬ Chat history length: {len(chatbot_history) if chatbot_history else 0}") | |
| # Check if we have valid inputs | |
| if not user_audio: | |
| print("β No audio received - returning early") | |
| return chatbot_history, current_state, gr.update(interactive=True), gr.update(visible=False), None | |
| if not current_state or "current_question_text" not in current_state: | |
| print("β Invalid session state - returning early") | |
| return chatbot_history, current_state, gr.update(interactive=True), gr.update(visible=False), None | |
| print(f"β Processing audio file: {user_audio}") | |
| # Transcribe user response | |
| try: | |
| user_response_text = transcribe_audio(user_audio) | |
| print(f"β Transcribed: {user_response_text[:100]}...") | |
| if user_response_text.startswith("Sorry, I") or "error" in user_response_text.lower(): | |
| # Transcription failed, return error message | |
| if not chatbot_history: | |
| chatbot_history = [] | |
| chatbot_history.append({"role": "assistant", "content": user_response_text}) | |
| return chatbot_history, current_state, gr.update(interactive=True), gr.update(visible=False), None | |
| except Exception as e: | |
| print(f"β Transcription error: {e}") | |
| error_msg = "Sorry, I couldn't process your audio. Please try recording again." | |
| if not chatbot_history: | |
| chatbot_history = [] | |
| chatbot_history.append({"role": "assistant", "content": error_msg}) | |
| return chatbot_history, current_state, gr.update(interactive=True), gr.update(visible=False), None | |
| # Initialize chat history if empty | |
| if not chatbot_history: | |
| chatbot_history = [] | |
| # Add user response to chat | |
| chatbot_history.append({"role": "user", "content": user_response_text}) | |
| # Get feedback from AI | |
| try: | |
| feedback_text = evaluate_response( | |
| current_state["current_question_text"], | |
| user_response_text, | |
| current_state["coaching_type"] | |
| ) | |
| # Parse scores | |
| scores = parse_scores_from_evaluation(feedback_text) | |
| overall_score = get_overall_score(scores) | |
| score_interpretation = get_score_interpretation(overall_score) | |
| # Add score to feedback | |
| if overall_score > 0: | |
| score_summary = f"π SCORE: {overall_score}/10 - {score_interpretation}\n\n" | |
| feedback_with_score = score_summary + feedback_text | |
| else: | |
| feedback_with_score = feedback_text | |
| except Exception as e: | |
| print(f"β Evaluation error: {e}") | |
| feedback_with_score = "Thank you for your response. Let me give you some feedback on your approach." | |
| overall_score = 7 # Default score | |
| scores = {} | |
| # Store in coaching log | |
| current_state["coaching_log"].append({ | |
| "question": current_state["current_question_text"], | |
| "response": user_response_text, | |
| "feedback": feedback_with_score, | |
| "scores": scores, | |
| "overall_score": overall_score | |
| }) | |
| # Add AI feedback to chat | |
| chatbot_history.append({"role": "assistant", "content": feedback_with_score}) | |
| # Generate voice feedback (with error handling) | |
| try: | |
| ai_feedback_voice = text_to_speech_file(f"Your score is {overall_score} out of 10. {feedback_text[:100]}...") | |
| except Exception as e: | |
| print(f"β TTS error: {e}") | |
| ai_feedback_voice = None | |
| # Check if session is complete | |
| if current_state["current_question_num"] >= current_state["question_count"]: | |
| # Session complete - generate report | |
| session_scores = [item.get("overall_score", 0) for item in current_state["coaching_log"]] | |
| avg_score = sum(session_scores) / len(session_scores) if session_scores else 0 | |
| session_interpretation = get_score_interpretation(avg_score) | |
| end_message = f"π Session Complete!\n\nπ FINAL AVERAGE: {avg_score:.1f}/10 - {session_interpretation}\n\nGenerating your report..." | |
| chatbot_history.append({"role": "assistant", "content": end_message}) | |
| # Generate PDF report | |
| try: | |
| pdf_path = generate_pdf_file(current_state) | |
| print(f"β Report generated: {pdf_path}") | |
| except Exception as e: | |
| print(f"β Report generation error: {e}") | |
| pdf_path = None | |
| try: | |
| end_voice = text_to_speech_file("Congratulations! Your coaching session is complete.") | |
| except Exception as e: | |
| print(f"β TTS error: {e}") | |
| end_voice = ai_feedback_voice | |
| return ( | |
| chatbot_history, | |
| current_state, | |
| gr.update(interactive=False), # Disable recording | |
| gr.update(value=pdf_path, visible=True) if pdf_path else gr.update(visible=False), # Show download button | |
| end_voice | |
| ) | |
| else: | |
| # Move to next question | |
| current_state["current_question_num"] += 1 | |
| next_question = generate_coaching_question( | |
| current_state["coaching_type"], | |
| current_state["doc_text"], | |
| current_state["current_question_num"] | |
| ) | |
| current_state["current_question_text"] = next_question | |
| q_num = current_state["current_question_num"] | |
| next_message = f"π― Question {q_num}/{current_state['question_count']}:\n\n{next_question}" | |
| chatbot_history.append({"role": "assistant", "content": next_message}) | |
| # Generate voice for next question | |
| try: | |
| next_voice = text_to_speech_file(f"Question {q_num}. {next_question}") | |
| except Exception as e: | |
| print(f"β TTS error: {e}") | |
| next_voice = ai_feedback_voice | |
| return ( | |
| chatbot_history, | |
| current_state, | |
| gr.update(interactive=True), # Keep recording enabled | |
| gr.update(visible=False), # Keep download hidden | |
| next_voice | |
| ) | |
| def generate_pdf_file(state): | |
| final_data = { | |
| "name": state["name"], | |
| "type": state["coaching_type"], | |
| "q_and_a": state["coaching_log"] | |
| } | |
| file_name = f"Product_Coaching_Report_{state['name']}_{datetime.datetime.now().strftime('%Y-%m-%d')}.pdf" | |
| file_path = os.path.join(config.REPORT_FOLDER, file_name) | |
| generate_pdf_report(final_data, file_path) | |
| return file_path | |
| with gr.Blocks(theme=gr.themes.Default()) as app: | |
| state = gr.State({}) | |
| gr.Markdown("# π Personal AI Product Coach") | |
| gr.Markdown("### Elevate your product management skills with personalized coaching sessions") | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| gr.Markdown("### Setup Your Coaching Session") | |
| user_name = gr.Textbox(label="Your Name", placeholder="Enter your name") | |
| coaching_type_dd = gr.Dropdown( | |
| config.COACHING_TYPES, | |
| label="Product Management Focus Area" | |
| ) | |
| gr.Markdown("*Choose the area you want to improve*") | |
| num_questions_slider = gr.Slider( | |
| minimum=1, maximum=10, value=5, step=1, | |
| label="Number of Scenarios" | |
| ) | |
| gr.Markdown("*How many coaching scenarios do you want to practice?*") | |
| doc_uploader = gr.File( | |
| label="Upload Your Resume/Portfolio (Optional)", | |
| file_types=[".pdf", ".docx", ".txt"] | |
| ) | |
| gr.Markdown("*Upload your resume or product portfolio for personalized coaching*") | |
| start_btn = gr.Button("π― Start Coaching Session", variant="primary") | |
| with gr.Column(scale=2): | |
| chatbot = gr.Chatbot( | |
| label="AI Product Coach", | |
| height=500, | |
| type="messages" | |
| ) | |
| with gr.Row(): | |
| # Use a simpler audio input approach | |
| audio_in = gr.Audio( | |
| sources=["microphone"], | |
| type="filepath", | |
| label="ποΈ Record Your Response", | |
| interactive=False, | |
| show_download_button=False | |
| ) | |
| # Add manual submit button for reliability | |
| submit_audio_btn = gr.Button("π€ Submit Audio", variant="secondary", visible=False) | |
| # Add a test button to check if recording works | |
| test_btn = gr.Button("π§ͺ Test Recording", variant="secondary", visible=True) | |
| status_text = gr.Textbox(label="Status", value="Click 'Start Coaching Session' to begin", interactive=False) | |
| download_pdf_btn = gr.File(label="π Download Coaching Report", visible=False) | |
| audio_out = gr.Audio(visible=False, autoplay=True) | |
| start_btn.click( | |
| fn=start_coaching_session, | |
| inputs=[coaching_type_dd, doc_uploader, user_name, num_questions_slider], | |
| outputs=[state, chatbot, audio_out, audio_in, start_btn] | |
| ) | |
| test_btn.click( | |
| fn=test_recording, | |
| outputs=[audio_in, status_text] | |
| ) | |
| # Multiple event handlers for better reliability | |
| audio_in.change( | |
| fn=handle_coaching_turn, | |
| inputs=[audio_in, chatbot, state], | |
| outputs=[chatbot, state, audio_in, download_pdf_btn, audio_out] | |
| ) | |
| # Also try upload event | |
| audio_in.upload( | |
| fn=handle_coaching_turn, | |
| inputs=[audio_in, chatbot, state], | |
| outputs=[chatbot, state, audio_in, download_pdf_btn, audio_out] | |
| ) | |
| # Manual submit button as backup | |
| submit_audio_btn.click( | |
| fn=handle_coaching_turn, | |
| inputs=[audio_in, chatbot, state], | |
| outputs=[chatbot, state, audio_in, download_pdf_btn, audio_out] | |
| ) | |
| # Add footer with helpful information | |
| gr.Markdown(""" | |
| --- | |
| ### π‘ Coaching Tips | |
| - **Speak naturally** - Think out loud and explain your reasoning | |
| - **Use frameworks** - Apply PM frameworks like RICE, Jobs-to-be-Done, etc. | |
| - **Be specific** - Provide concrete examples and metrics when possible | |
| - **Ask questions** - Great PMs ask clarifying questions about requirements | |
| ### π― What Makes a Great Response? | |
| β Clear problem identification | |
| β Structured thinking approach | |
| β Data-driven reasoning | |
| β Stakeholder consideration | |
| β Action-oriented next steps | |
| """) | |
| if __name__ == "__main__": | |
| # Ensure required directories exist | |
| os.makedirs(config.UPLOAD_FOLDER, exist_ok=True) | |
| os.makedirs(config.REPORT_FOLDER, exist_ok=True) | |
| # Print startup message | |
| print("π Personal AI Product Coach Starting...") | |
| print("π Focus Areas Available:", ", ".join(config.COACHING_TYPES)) | |
| print("π― Ready to help you improve your product management skills!") | |
| print("=" * 60) | |
| # Check for API key | |
| if not config.GROQ_API_KEY: | |
| print("β οΈ WARNING: GROQ_API_KEY not found!") | |
| print("Please set your Groq API key as an environment variable.") | |
| print("Get your free key from: https://console.groq.com/keys") | |
| # Launch the app - Configure for both local and HuggingFace deployment | |
| app.launch( | |
| server_name="0.0.0.0" if os.getenv("SPACE_ID") else "127.0.0.1", | |
| server_port=None if os.getenv("SPACE_ID") else 7861, | |
| share=True, | |
| show_error=True | |
| ) |