Spaces:
Runtime error
Runtime error
| """ | |
| HR-AI Interview Simulation V2 - Full Premium UI | |
| Complete Flask Application with Premium Frontend | |
| Deployed: December 2024 | |
| """ | |
| import os | |
| from flask import Flask, request, jsonify, send_from_directory | |
| from flask_cors import CORS | |
| from groq import Groq | |
| import json | |
| import uuid | |
| import re | |
| from datetime import datetime | |
| from PyPDF2 import PdfReader | |
| # Configuration | |
| API_KEY = os.getenv('GROQ_API_KEY', '') | |
| GROQ_MODEL = 'llama-3.3-70b-versatile' | |
| # Get the directory where app.py is located | |
| BASE_DIR = os.path.dirname(os.path.abspath(__file__)) | |
| STATIC_DIR = os.path.join(BASE_DIR, 'static') | |
| print(f"BASE_DIR: {BASE_DIR}") | |
| print(f"STATIC_DIR: {STATIC_DIR}") | |
| print(f"Static dir exists: {os.path.exists(STATIC_DIR)}") | |
| if os.path.exists(STATIC_DIR): | |
| print(f"Static contents: {os.listdir(STATIC_DIR)}") | |
| app = Flask(__name__, static_folder=STATIC_DIR, static_url_path='/static') | |
| CORS(app) | |
| client = None | |
| if API_KEY: | |
| client = Groq(api_key=API_KEY) | |
| sessions = {} | |
| def get_or_create_session(session_id): | |
| if session_id not in sessions: | |
| sessions[session_id] = { | |
| 'candidate_profile': None, | |
| 'interview_questions': [], | |
| 'interview_responses': [], | |
| 'interview_start_time': None, | |
| 'interview_end_time': None | |
| } | |
| return sessions[session_id] | |
| def extract_text_from_pdf(pdf_file): | |
| try: | |
| reader = PdfReader(pdf_file) | |
| return "".join(p.extract_text() or "" for p in reader.pages) | |
| except: | |
| return "" | |
| def extract_json_from_response(text): | |
| match = re.search(r'```(?:json)?\s*([\s\S]*?)\s*```', text) | |
| if match: | |
| try: return json.loads(match.group(1)) | |
| except: pass | |
| if '{' in text: | |
| try: return json.loads(text[text.find('{'):text.rfind('}')+1]) | |
| except: pass | |
| return None | |
| def generate_content_with_groq(prompt): | |
| if not client: return None | |
| try: | |
| resp = client.chat.completions.create( | |
| messages=[{"role": "system", "content": "Return only valid JSON."}, {"role": "user", "content": prompt}], | |
| model=GROQ_MODEL, temperature=0.7, max_tokens=4096 | |
| ) | |
| data = extract_json_from_response(resp.choices[0].message.content) | |
| return json.dumps(data) if data else None | |
| except Exception as e: | |
| print(f"Groq Error: {e}") | |
| return None | |
| # Serve main page - redirect to signup first | |
| def home(): | |
| return send_from_directory(STATIC_DIR, 'signup.html') | |
| # Serve dashboard | |
| def dashboard(): | |
| return send_from_directory(STATIC_DIR, 'index.html') | |
| # Serve login page | |
| def login(): | |
| return send_from_directory(STATIC_DIR, 'login.html') | |
| # Serve signup page | |
| def signup(): | |
| return send_from_directory(STATIC_DIR, 'signup.html') | |
| # Serve static assets - CSS and JS | |
| def serve_assets(filename): | |
| assets_dir = os.path.join(STATIC_DIR, 'assets') | |
| return send_from_directory(assets_dir, filename) | |
| # Catch-all for any other static files in root | |
| def serve_static(filename): | |
| # Check if file exists in static directory | |
| file_path = os.path.join(STATIC_DIR, filename) | |
| if os.path.exists(file_path): | |
| return send_from_directory(STATIC_DIR, filename) | |
| # For any non-existent file, redirect to dashboard instead of 404 | |
| return send_from_directory(STATIC_DIR, 'index.html') | |
| # API Endpoints | |
| def upload_resume(): | |
| session_id = request.headers.get('X-User-Session-Id', str(uuid.uuid4())) | |
| session = get_or_create_session(session_id) | |
| if 'resume' not in request.files: | |
| return jsonify({'error': 'No resume file'}), 400 | |
| file = request.files['resume'] | |
| text = extract_text_from_pdf(file) | |
| if not text.strip(): | |
| return jsonify({'error': 'Cannot extract text from PDF'}), 400 | |
| prompt = f'''Analyze this resume and extract information. Return JSON with these exact fields: | |
| - name: candidate's full name (string) | |
| - email: email address (string) | |
| - experience: work experience summary as a simple string like "5 years in software development" or "2 roles (3 months, 2 months)" | |
| - key_skills: array of skill strings like ["Python", "JavaScript", "React"] | |
| - inferred_position: best matching job title (string) | |
| Return ONLY valid JSON: {{"name":"","email":"","experience":"","key_skills":[],"inferred_position":""}} | |
| Resume text: | |
| {text[:8000]}''' | |
| resp = generate_content_with_groq(prompt) | |
| if resp: | |
| profile = json.loads(resp) | |
| if not isinstance(profile.get('key_skills'), list): profile['key_skills'] = [] | |
| # Ensure experience is a string | |
| if isinstance(profile.get('experience'), dict): | |
| exp = profile['experience'] | |
| profile['experience'] = f"{exp.get('years', '')} years" if 'years' in exp else str(exp) | |
| elif isinstance(profile.get('experience'), list): | |
| profile['experience'] = ', '.join(str(e) for e in profile['experience']) | |
| session['candidate_profile'] = profile | |
| return jsonify({'message': 'Success', 'candidate_profile': profile, 'session_id': session_id}), 200 | |
| return jsonify({'error': 'AI failed'}), 500 | |
| def setup_interview(): | |
| session_id = request.headers.get('X-User-Session-Id') | |
| if not session_id or session_id not in sessions: | |
| return jsonify({'error': 'Invalid session'}), 400 | |
| session = sessions[session_id] | |
| data = request.get_json() | |
| position = data.get('position_role', '') | |
| profile = session.get('candidate_profile') | |
| if not position or not profile: | |
| return jsonify({'error': 'Position and profile required'}), 400 | |
| skills = ", ".join(profile.get('key_skills', [])) | |
| experience = profile.get('experience', 'N/A') | |
| # Generate in-depth concept questions based on resume skills | |
| prompt = f'''Generate 15 IN-DEPTH interview questions for {profile.get('name','Candidate')} applying for "{position}". | |
| Candidate Skills: {skills} | |
| Experience: {experience} | |
| Generate questions that test DEEP understanding of concepts. Questions should be: | |
| - Based on the candidate's actual skills from their resume | |
| - Testing theoretical knowledge, architecture decisions, best practices | |
| - Asking about real-world scenarios and problem-solving approaches | |
| - NO coding/programming challenges - only conceptual questions | |
| Question Distribution: | |
| - 10 Technical questions (in-depth concepts about their listed skills like {skills}) | |
| - 3 Soft Skills questions (teamwork, leadership, conflict resolution) | |
| - 2 Communication questions (explaining technical concepts, stakeholder management) | |
| Example good technical questions: | |
| - "Explain the difference between SQL and NoSQL databases and when you would choose each" | |
| - "What are the SOLID principles and how have you applied them?" | |
| - "Describe microservices architecture and its trade-offs vs monolithic" | |
| - "How does garbage collection work in [language]?" | |
| Return JSON: {{"questions":[{{"id":"q1","question":"...","tags":["technical"]}}]}}''' | |
| resp = generate_content_with_groq(prompt) | |
| if resp: | |
| result = json.loads(resp) | |
| session['interview_questions'] = result.get('questions', []) | |
| session['interview_responses'] = [] | |
| session['interview_start_time'] = datetime.now().isoformat() | |
| return jsonify({'message': 'Generated', 'questions': result.get('questions', []), 'is_coding_role': False}), 200 | |
| return jsonify({'error': 'Failed'}), 500 | |
| def submit_answer(): | |
| session_id = request.headers.get('X-User-Session-Id') | |
| if not session_id or session_id not in sessions: | |
| return jsonify({'error': 'Invalid session'}), 400 | |
| session = sessions[session_id] | |
| data = request.get_json() | |
| qid = data.get('question_id') | |
| answer = data.get('response_text', '') | |
| duration = data.get('duration', '00:00') | |
| q = next((x for x in session['interview_questions'] if x['id'] == qid), None) | |
| if not q: return jsonify({'error': 'Question not found'}), 404 | |
| prompt = f'''Evaluate strictly: | |
| Q: {q['question']} | |
| A: {answer} | |
| Return: {{"technicalScore":85,"communicationScore":90,"relevanceScore":88,"feedback":"..."}}''' | |
| resp = generate_content_with_groq(prompt) | |
| if resp: | |
| ev = json.loads(resp) | |
| ev['score'] = round((ev.get('technicalScore',0)+ev.get('communicationScore',0)+ev.get('relevanceScore',0))/3) | |
| session['interview_responses'].append({'question_id':qid,'question':q['question'],'tags':q.get('tags',[]),'response':answer,'duration':duration,'evaluation':ev}) | |
| return jsonify({'message': 'Evaluated', 'evaluation': ev}), 200 | |
| return jsonify({'error': 'Failed'}), 500 | |
| def get_assessment(): | |
| session_id = request.headers.get('X-User-Session-Id') | |
| if not session_id or session_id not in sessions: | |
| return jsonify({'error': 'Invalid session'}), 400 | |
| session = sessions[session_id] | |
| if not session.get('interview_responses'): | |
| return jsonify({'error': 'No responses'}), 400 | |
| profile = session['candidate_profile'] | |
| responses = session['interview_responses'] | |
| avg = sum(r['evaluation']['score'] for r in responses) / len(responses) | |
| summary = "\n".join([f"Q: {r['question'][:60]}... Score: {r['evaluation']['score']}%" for r in responses[:5]]) | |
| prompt = f'''Assessment for {profile.get('name','Candidate')}. Avg: {avg:.1f}%. Questions: {len(responses)}. | |
| {summary} | |
| Return: {{"overallScore":85,"recommendation":"Recommended","keyStrengths":["..."],"areasForImprovement":["..."],"detailedScores":{{"technicalSkills":85,"communication":80,"softSkills":78}}}}''' | |
| resp = generate_content_with_groq(prompt) | |
| if resp: | |
| a = json.loads(resp) | |
| a['detailedQuestionAnalysis'] = [{'question':r['question'],'score':r['evaluation']['score'],'technicalScore':r['evaluation'].get('technicalScore',0),'communicationScore':r['evaluation'].get('communicationScore',0),'relevanceScore':r['evaluation'].get('relevanceScore',0)} for r in responses] | |
| return jsonify({'message': 'Generated', 'assessment': a}), 200 | |
| return jsonify({'assessment': {'overallScore': round(avg), 'recommendation': 'Recommended' if avg >= 70 else 'Needs Improvement', 'keyStrengths': [], 'areasForImprovement': [], 'detailedScores': {'technicalSkills': round(avg), 'communication': round(avg), 'softSkills': round(avg)}}}), 200 | |
| def log_security(): | |
| return jsonify({'message': 'Logged'}), 200 | |
| if __name__ == '__main__': | |
| port = int(os.environ.get('PORT', 7860)) | |
| app.run(host='0.0.0.0', port=port, debug=False) | |