Parimal Kalpande commited on
Commit
5348e91
·
1 Parent(s): cdd559d
.dockerignore ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ __pycache__/
2
+ *.pyc
3
+ *.pyo
4
+ *.pyd
5
+ venv/
6
+ reports/
7
+ uploads/
8
+ .git/
9
+ .env
.gitignore ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Python virtual environment
2
+ venv/
3
+ /venv/
4
+ __pycache__/
5
+ *.pyc
6
+ *.pyo
7
+ *.pyd
8
+
9
+ # User-specific files
10
+ uploads/
11
+ reports/
12
+
13
+ # Configuration and secrets
14
+ # IMPORTANT: This prevents your API keys from being uploaded to GitHub
15
+ .env
16
+
17
+ # IDE and editor files
18
+ .vscode/
19
+ .idea/
20
+ *.swp
21
+ *.swo
22
+
23
+ # OS-specific files
24
+ .DS_Store
25
+ Thumbs.db
26
+
27
+ # Matplotlib cache
28
+ *.png
29
+ !/path/to/keep/some/images/
DOCKERFILE ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use an official Python runtime as a parent image
2
+ FROM python:3.11-slim
3
+
4
+ # Set the working directory in the container
5
+ WORKDIR /app
6
+
7
+ # Copy the requirements file into the container
8
+ COPY requirements.txt .
9
+
10
+ # Install any needed system dependencies (like for audio)
11
+ RUN apt-get update && apt-get install -y --no-install-recommends \
12
+ ffmpeg \
13
+ && rm -rf /var/lib/apt/lists/*
14
+
15
+ # Install the Python dependencies
16
+ RUN pip install --no-cache-dir -r requirements.txt
17
+
18
+ # Copy the rest of the application's code into the container
19
+ COPY . .
20
+
21
+ # Expose the port that Gradio runs on
22
+ EXPOSE 7860
23
+
24
+ # Define the command to run your application
25
+ CMD ["python3", "app.py"]
app.py ADDED
@@ -0,0 +1,119 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app.py
2
+ import gradio as gr
3
+ import os
4
+ import time
5
+ import datetime
6
+ import random
7
+ import config
8
+ from modules.tts_handler import text_to_speech_file
9
+ from modules.stt_handler import transcribe_audio
10
+ from modules.doc_processor import extract_text_from_document
11
+ from modules.llm_handler import generate_question, evaluate_answer
12
+ from modules.report_generator import generate_pdf_report
13
+
14
+ def start_interview(interview_type, doc_file, name, num_questions):
15
+ if not interview_type or not doc_file:
16
+ return {
17
+ chatbot: gr.update(value=[[None, "Please select an interview type and upload a document to begin."]]),
18
+ audio_in: gr.update(interactive=False)
19
+ }
20
+ doc_text = extract_text_from_document(doc_file.name)
21
+ if "Error" in doc_text or "Unsupported" in doc_text:
22
+ return {
23
+ chatbot: gr.update(value=[[None, f"Error: {doc_text}"]]),
24
+ audio_in: gr.update(interactive=False)
25
+ }
26
+ initial_state = {
27
+ "interview_type": interview_type, "doc_text": doc_text,
28
+ "name": name if name else "User", "question_count": int(num_questions),
29
+ "current_question_num": 1, "interview_log": []
30
+ }
31
+ first_question = generate_question(interview_type, doc_text)
32
+ initial_state["current_question_text"] = first_question
33
+ greeting = f"Hello {initial_state['name']}. We'll go through {int(num_questions)} questions today. Here is your first question:"
34
+ tts_prompt = f"{greeting} {first_question}"
35
+ ai_voice_path = text_to_speech_file(tts_prompt)
36
+ return {
37
+ state: initial_state,
38
+ chatbot: gr.update(value=[[None, f"{greeting}\n\n{first_question}"]]),
39
+ audio_out: gr.update(value=ai_voice_path, autoplay=True),
40
+ audio_in: gr.update(interactive=True),
41
+ start_btn: gr.update(interactive=False)
42
+ }
43
+
44
+ def handle_interview_turn(user_audio, chatbot_history, current_state):
45
+ user_answer_text = transcribe_audio(user_audio)
46
+ chatbot_history.append([user_answer_text, None])
47
+ yield {chatbot: chatbot_history, audio_in: gr.update(interactive=False)}
48
+
49
+ evaluation_text = evaluate_answer(current_state["current_question_text"], user_answer_text)
50
+ current_state["interview_log"].append({
51
+ "question": current_state["current_question_text"],
52
+ "answer": user_answer_text,
53
+ "evaluation": evaluation_text
54
+ })
55
+ if current_state["current_question_num"] >= current_state["question_count"]:
56
+ end_message = "This concludes the interview. Generating your final report now."
57
+ chatbot_history.append([None, end_message])
58
+ pdf_path = generate_pdf_file(current_state)
59
+ ai_voice_path = text_to_speech_file(end_message)
60
+ yield {
61
+ chatbot: chatbot_history,
62
+ audio_out: gr.update(value=ai_voice_path, autoplay=True),
63
+ download_pdf_btn: gr.update(value=pdf_path, visible=True)
64
+ }
65
+ else:
66
+ current_state["current_question_num"] += 1
67
+ next_question = generate_question(current_state["interview_type"], current_state["doc_text"])
68
+ current_state["current_question_text"] = next_question
69
+ q_num = current_state["current_question_num"]
70
+ transition_message = f"Thank you. Here is question {q_num}:\n\n{next_question}"
71
+ chatbot_history.append([None, transition_message])
72
+ ai_voice_path = text_to_speech_file(transition_message)
73
+ yield {
74
+ state: current_state,
75
+ chatbot: chatbot_history,
76
+ audio_out: gr.update(value=ai_voice_path, autoplay=True),
77
+ audio_in: gr.update(interactive=True)
78
+ }
79
+
80
+ def generate_pdf_file(state):
81
+ # --- THIS IS THE CORRECTED LINE ---
82
+ final_data = { "name": state["name"], "type": state["interview_type"], "q_and_a": state["interview_log"] }
83
+ file_name = f"Report_{state['name']}_{datetime.datetime.now().strftime('%Y-%m-%d')}.pdf"
84
+ file_path = os.path.join(config.REPORT_FOLDER, file_name)
85
+ generate_pdf_report(final_data, file_path)
86
+ return file_path
87
+
88
+ with gr.Blocks(theme=gr.themes.Default()) as app:
89
+ state = gr.State({})
90
+ gr.Markdown("# 🤖 AI Interview Coach")
91
+ with gr.Row():
92
+ with gr.Column(scale=1):
93
+ gr.Markdown("### Setup")
94
+ user_name = gr.Textbox(label="Your Name")
95
+ interview_type_dd = gr.Dropdown(config.INTERVIEW_TYPES, label="Interview Type")
96
+ num_questions_slider = gr.Slider(minimum=2, maximum=10, value=5, step=1, label="Number of Questions")
97
+ doc_uploader = gr.File(label="Upload Resume/CV")
98
+ start_btn = gr.Button("Start Interview", variant="primary")
99
+ with gr.Column(scale=2):
100
+ chatbot = gr.Chatbot(label="Conversation", height=500)
101
+ audio_in = gr.Audio(sources=["microphone"], type="filepath", label="Record Your Answer", interactive=False)
102
+ download_pdf_btn = gr.File(label="Download Report", visible=False)
103
+ audio_out = gr.Audio(visible=False, autoplay=True)
104
+
105
+ start_btn.click(
106
+ fn=start_interview,
107
+ inputs=[interview_type_dd, doc_uploader, user_name, num_questions_slider],
108
+ outputs=[state, chatbot, audio_out, audio_in, start_btn]
109
+ )
110
+ audio_in.stop_recording(
111
+ fn=handle_interview_turn,
112
+ inputs=[audio_in, chatbot, state],
113
+ outputs=[state, chatbot, audio_out, audio_in, download_pdf_btn]
114
+ )
115
+
116
+ if __name__ == "__main__":
117
+ os.makedirs(config.UPLOAD_FOLDER, exist_ok=True)
118
+ os.makedirs(config.REPORT_FOLDER, exist_ok=True)
119
+ app.launch(debug=True)
config.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # config.py
2
+ import os
3
+
4
+ # -- Ollama Configuration --
5
+ OLLAMA_MODEL = 'llama3.1'
6
+
7
+ # -- Interview Configuration --
8
+ INTERVIEW_TYPES = ['HR', 'Technical', 'UPSC', 'Group Discussion (GD)', 'Mock IELTS']
9
+
10
+ # -- Piper TTS Configuration --
11
+ PIPER_VOICE_MODEL = './voice_model/en_US-lessac-medium.onnx'
12
+
13
+ # -- Directories --
14
+ UPLOAD_FOLDER = 'uploads'
15
+ REPORT_FOLDER = 'reports'
modules/__init__.py ADDED
File without changes
modules/doc_processor.py ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # modules/doc_processor.py
2
+
3
+ import fitz # PyMuPDF for PDFs
4
+ import docx # python-docx for DOCX files
5
+ import os
6
+
7
+ def extract_text_from_document(file_path):
8
+ """
9
+ Extracts text from a given document (PDF or DOCX).
10
+ """
11
+ text = ""
12
+ try:
13
+ _, file_extension = os.path.splitext(file_path)
14
+
15
+ if file_extension.lower() == '.pdf':
16
+ with fitz.open(file_path) as doc:
17
+ for page in doc:
18
+ text += page.get_text()
19
+ elif file_extension.lower() == '.docx':
20
+ doc = docx.Document(file_path)
21
+ for para in doc.paragraphs:
22
+ text += para.text + "\n"
23
+ else:
24
+ return "Unsupported file format. Please upload a .pdf or .docx file."
25
+
26
+ except Exception as e:
27
+ return f"Error reading document: {e}"
28
+
29
+ return text
modules/llm_handler.py ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # modules/llm_handler.py
2
+ import os
3
+ from groq import Groq
4
+ from modules.web_search import search_for_example_answers
5
+
6
+ # Initialize the Groq client
7
+ # The API key will be automatically read from the HF Space's secrets
8
+ client = Groq(api_key=os.environ.get("GROQ_API_KEY"))
9
+
10
+ def generate_question(interview_type, document_text):
11
+ prompt = f"As an expert {interview_type} interviewer, ask one relevant question based on this document: --- {document_text} ---"
12
+ try:
13
+ chat_completion = client.chat.completions.create(
14
+ messages=[{"role": "user", "content": prompt}],
15
+ model="llama3-8b-8192",
16
+ )
17
+ return chat_completion.choices[0].message.content
18
+ except Exception as e:
19
+ return f"Error generating question: {e}"
20
+
21
+ def evaluate_answer(question, answer):
22
+ example_answers = search_for_example_answers(question)
23
+ prompt = f"""
24
+ You are an interview coach. Compare the candidate's answer to the expert examples and provide a concise evaluation.
25
+ Question: "{question}"
26
+ Candidate's Answer: "{answer}"
27
+ Expert Examples: --- {example_answers} ---
28
+ """
29
+ try:
30
+ chat_completion = client.chat.completions.create(
31
+ messages=[{"role": "user", "content": prompt}],
32
+ model="llama3-8b-8192",
33
+ )
34
+ return chat_completion.choices[0].message.content
35
+ except Exception as e:
36
+ return f"An error occurred during evaluation: {e}"
modules/report_generator.py ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # modules/report_generator.py
2
+
3
+ import datetime
4
+ import os
5
+ import numpy as np
6
+ import matplotlib.pyplot as plt
7
+ from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, PageBreak, Image, Frame, PageTemplate
8
+ from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
9
+ from reportlab.lib.enums import TA_JUSTIFY, TA_CENTER, TA_LEFT
10
+ from reportlab.lib.units import inch
11
+ from reportlab.lib import colors
12
+ from modules.llm_handler import generate_holistic_feedback, parse_scores_from_evaluation
13
+ import config
14
+
15
+ # --- Page Template with Header and Footer (Unchanged) ---
16
+ class ReportPageTemplate(PageTemplate):
17
+ def __init__(self, id, pagesize):
18
+ frame = Frame(inch, inch, pagesize[0] - 2 * inch, pagesize[1] - 2 * inch, id='normal')
19
+ PageTemplate.__init__(self, id, [frame])
20
+
21
+ def beforeDrawPage(self, canvas, doc):
22
+ canvas.saveState()
23
+ canvas.setFont('Helvetica', 9)
24
+ canvas.setFillColor(colors.grey)
25
+ footer_text = f"Page {doc.page} | AI Interview Coach Report | Generated on {datetime.datetime.now().strftime('%Y-%m-%d')}"
26
+ canvas.drawCentredString(doc.width / 2 + inch, 0.75 * inch, footer_text)
27
+ canvas.restoreState()
28
+
29
+ def create_radar_chart(labels, scores, file_path):
30
+ # This function is unchanged
31
+ num_vars = len(labels)
32
+ angles = np.linspace(0, 2 * np.pi, num_vars, endpoint=False).tolist()
33
+ scores += scores[:1]
34
+ angles += angles[:1]
35
+ fig, ax = plt.subplots(figsize=(6, 6), subplot_kw=dict(polar=True))
36
+ ax.fill(angles, scores, color='#4A90E2', alpha=0.2)
37
+ ax.plot(angles, scores, color='#4A90E2', linewidth=2, linestyle='solid')
38
+ ax.set_yticklabels([])
39
+ ax.set_xticks(angles[:-1])
40
+ ax.set_xticklabels(labels, size=12, color='grey')
41
+ ax.set_rlabel_position(30)
42
+ ax.set_ylim(0, 10)
43
+ for angle, score in zip(angles[:-1], scores[:-1]):
44
+ ax.text(angle, score + 1.5, str(score), ha='center', va='center', size=14, color="#000000", weight='bold')
45
+ plt.title('Performance Snapshot', size=20, color='#333333', y=1.1)
46
+ os.makedirs(os.path.dirname(file_path), exist_ok=True)
47
+ plt.savefig(file_path, transparent=True, dpi=150)
48
+ plt.close(fig)
49
+ print(f"📈 Radar chart saved to {file_path}")
50
+
51
+ def generate_pdf_report(interview_data, file_path):
52
+ doc = SimpleDocTemplate(file_path, pagesize=(8.5 * inch, 11 * inch),
53
+ leftMargin=inch, rightMargin=inch, topMargin=inch, bottomMargin=inch)
54
+ doc.addPageTemplates([ReportPageTemplate('main_template', (8.5 * inch, 11 * inch))])
55
+
56
+ styles = getSampleStyleSheet()
57
+
58
+ # --- THIS IS THE CORRECTED SECTION ---
59
+ # Instead of adding styles with existing names, we create new ones with unique names.
60
+ styles.add(ParagraphStyle(name='ReportTitle', parent=styles['h1'], fontSize=28, alignment=TA_CENTER, spaceAfter=24))
61
+ styles.add(ParagraphStyle(name='ReportSubTitle', parent=styles['h2'], fontSize=16, alignment=TA_CENTER, spaceAfter=12, textColor=colors.HexColor('#555555')))
62
+ styles.add(ParagraphStyle(name='Justify', alignment=TA_JUSTIFY, spaceAfter=12, leading=14))
63
+ styles.add(ParagraphStyle(name='MainHeader', parent=styles['h1'], fontSize=22, spaceBefore=12, spaceAfter=20, alignment=TA_LEFT, textColor=colors.HexColor('#2c3e50')))
64
+ styles.add(ParagraphStyle(name='QuestionTitle', parent=styles['h2'], spaceBefore=20, spaceAfter=10, textColor=colors.HexColor('#2980b9')))
65
+ styles.add(ParagraphStyle(name='SectionTitle', parent=styles['h3'], spaceBefore=12, spaceAfter=6, textColor=colors.HexColor('#34495e')))
66
+ # --- END OF CORRECTION ---
67
+
68
+ story = []
69
+
70
+ # --- 1. The Title Page ---
71
+ story.append(Paragraph("Interview Performance Report", styles['ReportTitle']))
72
+ story.append(Spacer(1, 0.5 * inch))
73
+ story.append(Paragraph(f"Prepared for: <b>{interview_data.get('name', 'N/A')}</b>", styles['ReportSubTitle']))
74
+ story.append(Spacer(1, 0.2 * inch))
75
+ story.append(Paragraph(f"Interview Type: <b>{interview_data['type']}</b>", styles['ReportSubTitle']))
76
+ story.append(Spacer(1, 0.2 * inch))
77
+ story.append(Paragraph(f"Date of Report: <b>{datetime.datetime.now().strftime('%B %d, %Y')}</b>", styles['ReportSubTitle']))
78
+ story.append(PageBreak())
79
+
80
+ # --- 2. Overall Performance & Graph Section ---
81
+ story.append(Paragraph("Overall Performance Analysis", styles['MainHeader']))
82
+ full_log_text = ""
83
+ all_scores = []
84
+ skill_labels = ['Factual Accuracy', 'Relevance & Directness', 'Structure & Clarity']
85
+ for i, qa in enumerate(interview_data['q_and_a']):
86
+ full_log_text += f"Q{i+1}: {qa['question']}\nA: {qa['answer']}\n---\n"
87
+ scores = parse_scores_from_evaluation(qa['evaluation'])
88
+ all_scores.append([scores.get(label, 0) for label in skill_labels])
89
+ holistic_feedback = generate_holistic_feedback(full_log_text).replace('\n', '<br/>')
90
+ story.append(Paragraph(holistic_feedback, styles['Justify']))
91
+ story.append(Spacer(1, 0.3 * inch))
92
+
93
+ if all_scores:
94
+ avg_scores = np.mean(all_scores, axis=0).tolist()
95
+ chart_path = os.path.join(config.REPORT_FOLDER, "skill_chart.png")
96
+ if os.path.exists(chart_path): os.remove(chart_path)
97
+ create_radar_chart(skill_labels, avg_scores, chart_path)
98
+ story.append(Image(chart_path, width=4.5*inch, height=4.5*inch, hAlign='CENTER'))
99
+ story.append(PageBreak())
100
+
101
+ # --- 3. Detailed Question-by-Question Analysis ---
102
+ story.append(Paragraph("Detailed Question Analysis", styles['MainHeader']))
103
+ for i, qa in enumerate(interview_data['q_and_a']):
104
+ story.append(Paragraph(f"Question {i+1}: {qa['question']}", styles['QuestionTitle']))
105
+ story.append(Paragraph("Your Answer:", styles['SectionTitle']))
106
+ story.append(Paragraph(qa.get('answer', 'N/A'), styles['Justify']))
107
+ story.append(Paragraph("AI Evaluation:", styles['SectionTitle']))
108
+ story.append(Paragraph(qa.get('evaluation', 'N/A').replace('\n', '<br/>'), styles['Justify']))
109
+
110
+ try:
111
+ doc.build(story)
112
+ print(f"\n✅ Professional report generated successfully: {file_path}")
113
+ except Exception as e:
114
+ print(f"💥 Error generating professional PDF report: {e}")
modules/stt_handler.py ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # modules/stt_handler.py
2
+ import speech_recognition as sr
3
+ import os
4
+
5
+ def transcribe_audio(audio_filepath):
6
+ if not audio_filepath or not os.path.exists(audio_filepath):
7
+ print("STT Error: No audio file provided or file does not exist.")
8
+ return "[Transcription error: Invalid audio file path]"
9
+
10
+ recognizer = sr.Recognizer()
11
+ try:
12
+ with sr.AudioFile(audio_filepath) as source:
13
+ audio_data = recognizer.record(source)
14
+ print("Transcribing with Whisper...")
15
+ text = recognizer.recognize_whisper(audio_data, language="english")
16
+ print(f"User transcribed as: {text}")
17
+ return text
18
+ except sr.UnknownValueError:
19
+ print("STT Error: Whisper could not understand the audio.")
20
+ return "[Could not understand audio]"
21
+ except sr.RequestError as e:
22
+ print(f"STT Error: Could not request results from Whisper service; {e}")
23
+ return f"[Transcription error: Whisper service issue - {e}]"
24
+ except Exception as e:
25
+ print(f"STT Error: An unexpected error occurred during transcription: {e}")
26
+ return f"[Transcription error: {e}]"
27
+ finally:
28
+ if os.path.exists(audio_filepath):
29
+ try:
30
+ os.remove(audio_filepath)
31
+ except OSError as e:
32
+ print(f"Error deleting temp audio file {audio_filepath}: {e}")
modules/tts_handler.py ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # modules/tts_handler.py
2
+ import subprocess
3
+ import platform
4
+ import sys
5
+ import os
6
+ import config
7
+ from pydub import AudioSegment
8
+ import tempfile
9
+
10
+ def text_to_speech_file(text_to_speak):
11
+ print(f"AI generating audio for: {text_to_speak}")
12
+ piper_executable = 'piper' # Use system PATH
13
+ try:
14
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".raw") as raw_file:
15
+ raw_filename = raw_file.name
16
+
17
+ command = [piper_executable, '--model', config.PIPER_VOICE_MODEL, '--output-file', raw_filename]
18
+ process = subprocess.Popen(command, stdin=subprocess.PIPE)
19
+ process.communicate(input=text_to_speak.encode('utf-8'))
20
+
21
+ raw_audio = AudioSegment.from_file(raw_filename, format="raw", frame_rate=22050, channels=1, sample_width=2)
22
+
23
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as wav_file:
24
+ wav_filename = wav_file.name
25
+
26
+ raw_audio.export(wav_filename, format="wav")
27
+ os.remove(raw_filename)
28
+ return wav_filename
29
+ except Exception as e:
30
+ print(f"An error occurred during TTS generation: {e}")
31
+ return None
modules/web_search.py ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # modules/web_search.py
2
+
3
+ from ddgs import DDGS
4
+
5
+ def search_for_example_answers(query: str, num_results: int = 2):
6
+ """
7
+ Performs a targeted web search for high-quality example answers to an interview question.
8
+ """
9
+ # Refine the query to find expert answers
10
+ search_query = f"expert sample answer for interview question: \"{query}\""
11
+ print(f"🌐 Searching for expert answers with query: '{search_query}'")
12
+
13
+ try:
14
+ with DDGS(timeout=10) as ddgs:
15
+ results = list(ddgs.text(search_query, max_results=num_results))
16
+
17
+ if not results:
18
+ print(" -> No example answers found.")
19
+ return "No example answers found on the web."
20
+
21
+ formatted_results = ""
22
+ for i, res in enumerate(results):
23
+ formatted_results += f"Example Answer Source {i+1}:\nTitle: {res.get('title', 'N/A')}\nSnippet: {res.get('body', 'N/A')}\n\n"
24
+
25
+ print(f" -> Found {len(results)} example answers.")
26
+ return formatted_results
27
+
28
+ except Exception as e:
29
+ print(f"💥 Web search failed: {e}")
30
+ return "Web search for example answers failed."
requirements.txt ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ollama
2
+ openai-whisper
3
+ gradio
4
+ pydub
5
+ soundfile
6
+ pyaudio
7
+ piper-tts
8
+ PyMuPDF
9
+ python-docx
10
+ reportlab
11
+ speechrecognition
12
+ duckduckgo-search
13
+ ddgs
14
+ matplotlib
15
+ regex
16
+ groq
voice_model/en_US-lessac-medium.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5efe09e69902187827af646e1a6e9d269dee769f9877d17b16b1b46eeaaf019f
3
+ size 63201294
voice_model/en_US-lessac-medium.onnx.json ADDED
@@ -0,0 +1,493 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "audio": {
3
+ "sample_rate": 22050,
4
+ "quality": "medium"
5
+ },
6
+ "espeak": {
7
+ "voice": "en-us"
8
+ },
9
+ "inference": {
10
+ "noise_scale": 0.667,
11
+ "length_scale": 1,
12
+ "noise_w": 0.8
13
+ },
14
+ "phoneme_type": "espeak",
15
+ "phoneme_map": {},
16
+ "phoneme_id_map": {
17
+ "_": [
18
+ 0
19
+ ],
20
+ "^": [
21
+ 1
22
+ ],
23
+ "$": [
24
+ 2
25
+ ],
26
+ " ": [
27
+ 3
28
+ ],
29
+ "!": [
30
+ 4
31
+ ],
32
+ "'": [
33
+ 5
34
+ ],
35
+ "(": [
36
+ 6
37
+ ],
38
+ ")": [
39
+ 7
40
+ ],
41
+ ",": [
42
+ 8
43
+ ],
44
+ "-": [
45
+ 9
46
+ ],
47
+ ".": [
48
+ 10
49
+ ],
50
+ ":": [
51
+ 11
52
+ ],
53
+ ";": [
54
+ 12
55
+ ],
56
+ "?": [
57
+ 13
58
+ ],
59
+ "a": [
60
+ 14
61
+ ],
62
+ "b": [
63
+ 15
64
+ ],
65
+ "c": [
66
+ 16
67
+ ],
68
+ "d": [
69
+ 17
70
+ ],
71
+ "e": [
72
+ 18
73
+ ],
74
+ "f": [
75
+ 19
76
+ ],
77
+ "h": [
78
+ 20
79
+ ],
80
+ "i": [
81
+ 21
82
+ ],
83
+ "j": [
84
+ 22
85
+ ],
86
+ "k": [
87
+ 23
88
+ ],
89
+ "l": [
90
+ 24
91
+ ],
92
+ "m": [
93
+ 25
94
+ ],
95
+ "n": [
96
+ 26
97
+ ],
98
+ "o": [
99
+ 27
100
+ ],
101
+ "p": [
102
+ 28
103
+ ],
104
+ "q": [
105
+ 29
106
+ ],
107
+ "r": [
108
+ 30
109
+ ],
110
+ "s": [
111
+ 31
112
+ ],
113
+ "t": [
114
+ 32
115
+ ],
116
+ "u": [
117
+ 33
118
+ ],
119
+ "v": [
120
+ 34
121
+ ],
122
+ "w": [
123
+ 35
124
+ ],
125
+ "x": [
126
+ 36
127
+ ],
128
+ "y": [
129
+ 37
130
+ ],
131
+ "z": [
132
+ 38
133
+ ],
134
+ "æ": [
135
+ 39
136
+ ],
137
+ "ç": [
138
+ 40
139
+ ],
140
+ "ð": [
141
+ 41
142
+ ],
143
+ "ø": [
144
+ 42
145
+ ],
146
+ "ħ": [
147
+ 43
148
+ ],
149
+ "ŋ": [
150
+ 44
151
+ ],
152
+ "œ": [
153
+ 45
154
+ ],
155
+ "ǀ": [
156
+ 46
157
+ ],
158
+ "ǁ": [
159
+ 47
160
+ ],
161
+ "ǂ": [
162
+ 48
163
+ ],
164
+ "ǃ": [
165
+ 49
166
+ ],
167
+ "ɐ": [
168
+ 50
169
+ ],
170
+ "ɑ": [
171
+ 51
172
+ ],
173
+ "ɒ": [
174
+ 52
175
+ ],
176
+ "ɓ": [
177
+ 53
178
+ ],
179
+ "ɔ": [
180
+ 54
181
+ ],
182
+ "ɕ": [
183
+ 55
184
+ ],
185
+ "ɖ": [
186
+ 56
187
+ ],
188
+ "ɗ": [
189
+ 57
190
+ ],
191
+ "ɘ": [
192
+ 58
193
+ ],
194
+ "ə": [
195
+ 59
196
+ ],
197
+ "ɚ": [
198
+ 60
199
+ ],
200
+ "ɛ": [
201
+ 61
202
+ ],
203
+ "ɜ": [
204
+ 62
205
+ ],
206
+ "ɞ": [
207
+ 63
208
+ ],
209
+ "ɟ": [
210
+ 64
211
+ ],
212
+ "ɠ": [
213
+ 65
214
+ ],
215
+ "ɡ": [
216
+ 66
217
+ ],
218
+ "ɢ": [
219
+ 67
220
+ ],
221
+ "ɣ": [
222
+ 68
223
+ ],
224
+ "ɤ": [
225
+ 69
226
+ ],
227
+ "ɥ": [
228
+ 70
229
+ ],
230
+ "ɦ": [
231
+ 71
232
+ ],
233
+ "ɧ": [
234
+ 72
235
+ ],
236
+ "ɨ": [
237
+ 73
238
+ ],
239
+ "ɪ": [
240
+ 74
241
+ ],
242
+ "ɫ": [
243
+ 75
244
+ ],
245
+ "ɬ": [
246
+ 76
247
+ ],
248
+ "ɭ": [
249
+ 77
250
+ ],
251
+ "ɮ": [
252
+ 78
253
+ ],
254
+ "ɯ": [
255
+ 79
256
+ ],
257
+ "ɰ": [
258
+ 80
259
+ ],
260
+ "ɱ": [
261
+ 81
262
+ ],
263
+ "ɲ": [
264
+ 82
265
+ ],
266
+ "ɳ": [
267
+ 83
268
+ ],
269
+ "ɴ": [
270
+ 84
271
+ ],
272
+ "ɵ": [
273
+ 85
274
+ ],
275
+ "ɶ": [
276
+ 86
277
+ ],
278
+ "ɸ": [
279
+ 87
280
+ ],
281
+ "ɹ": [
282
+ 88
283
+ ],
284
+ "ɺ": [
285
+ 89
286
+ ],
287
+ "ɻ": [
288
+ 90
289
+ ],
290
+ "ɽ": [
291
+ 91
292
+ ],
293
+ "ɾ": [
294
+ 92
295
+ ],
296
+ "ʀ": [
297
+ 93
298
+ ],
299
+ "ʁ": [
300
+ 94
301
+ ],
302
+ "ʂ": [
303
+ 95
304
+ ],
305
+ "ʃ": [
306
+ 96
307
+ ],
308
+ "ʄ": [
309
+ 97
310
+ ],
311
+ "ʈ": [
312
+ 98
313
+ ],
314
+ "ʉ": [
315
+ 99
316
+ ],
317
+ "ʊ": [
318
+ 100
319
+ ],
320
+ "ʋ": [
321
+ 101
322
+ ],
323
+ "ʌ": [
324
+ 102
325
+ ],
326
+ "ʍ": [
327
+ 103
328
+ ],
329
+ "ʎ": [
330
+ 104
331
+ ],
332
+ "ʏ": [
333
+ 105
334
+ ],
335
+ "ʐ": [
336
+ 106
337
+ ],
338
+ "ʑ": [
339
+ 107
340
+ ],
341
+ "ʒ": [
342
+ 108
343
+ ],
344
+ "ʔ": [
345
+ 109
346
+ ],
347
+ "ʕ": [
348
+ 110
349
+ ],
350
+ "ʘ": [
351
+ 111
352
+ ],
353
+ "ʙ": [
354
+ 112
355
+ ],
356
+ "ʛ": [
357
+ 113
358
+ ],
359
+ "ʜ": [
360
+ 114
361
+ ],
362
+ "ʝ": [
363
+ 115
364
+ ],
365
+ "ʟ": [
366
+ 116
367
+ ],
368
+ "ʡ": [
369
+ 117
370
+ ],
371
+ "ʢ": [
372
+ 118
373
+ ],
374
+ "ʲ": [
375
+ 119
376
+ ],
377
+ "ˈ": [
378
+ 120
379
+ ],
380
+ "ˌ": [
381
+ 121
382
+ ],
383
+ "ː": [
384
+ 122
385
+ ],
386
+ "ˑ": [
387
+ 123
388
+ ],
389
+ "˞": [
390
+ 124
391
+ ],
392
+ "β": [
393
+ 125
394
+ ],
395
+ "θ": [
396
+ 126
397
+ ],
398
+ "χ": [
399
+ 127
400
+ ],
401
+ "ᵻ": [
402
+ 128
403
+ ],
404
+ "ⱱ": [
405
+ 129
406
+ ],
407
+ "0": [
408
+ 130
409
+ ],
410
+ "1": [
411
+ 131
412
+ ],
413
+ "2": [
414
+ 132
415
+ ],
416
+ "3": [
417
+ 133
418
+ ],
419
+ "4": [
420
+ 134
421
+ ],
422
+ "5": [
423
+ 135
424
+ ],
425
+ "6": [
426
+ 136
427
+ ],
428
+ "7": [
429
+ 137
430
+ ],
431
+ "8": [
432
+ 138
433
+ ],
434
+ "9": [
435
+ 139
436
+ ],
437
+ "̧": [
438
+ 140
439
+ ],
440
+ "̃": [
441
+ 141
442
+ ],
443
+ "̪": [
444
+ 142
445
+ ],
446
+ "̯": [
447
+ 143
448
+ ],
449
+ "̩": [
450
+ 144
451
+ ],
452
+ "ʰ": [
453
+ 145
454
+ ],
455
+ "ˤ": [
456
+ 146
457
+ ],
458
+ "ε": [
459
+ 147
460
+ ],
461
+ "↓": [
462
+ 148
463
+ ],
464
+ "#": [
465
+ 149
466
+ ],
467
+ "\"": [
468
+ 150
469
+ ],
470
+ "↑": [
471
+ 151
472
+ ],
473
+ "̺": [
474
+ 152
475
+ ],
476
+ "̻": [
477
+ 153
478
+ ]
479
+ },
480
+ "num_symbols": 256,
481
+ "num_speakers": 1,
482
+ "speaker_id_map": {},
483
+ "piper_version": "1.0.0",
484
+ "language": {
485
+ "code": "en_US",
486
+ "family": "en",
487
+ "region": "US",
488
+ "name_native": "English",
489
+ "name_english": "English",
490
+ "country_english": "United States"
491
+ },
492
+ "dataset": "lessac"
493
+ }