APPLE commited on
Commit
f964ea3
·
1 Parent(s): 11839ff

initial deploy

Browse files
Files changed (6) hide show
  1. .gitignore +5 -0
  2. Dockerfile +15 -0
  3. public/index.html +2 -2
  4. requirements.txt +10 -16
  5. src/analytics.py +80 -0
  6. src/main.py +3 -3
.gitignore CHANGED
@@ -1,5 +1,10 @@
 
1
  venv/
 
2
  __pycache__/
 
3
  .env
4
  *.pyc
 
5
  .DS_Store
 
 
1
+
2
  venv/
3
+
4
  __pycache__/
5
+
6
  .env
7
  *.pyc
8
+
9
  .DS_Store
10
+
Dockerfile ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ FROM python:3.11-slim
3
+
4
+ WORKDIR /app
5
+
6
+ COPY requirements.txt .
7
+
8
+ RUN pip install --no-cache-dir -r requirements.txt
9
+
10
+ COPY . .
11
+
12
+ EXPOSE 7860
13
+
14
+ CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "7860"]
15
+
public/index.html CHANGED
@@ -3,7 +3,7 @@
3
  <head>
4
  <meta charset="UTF-8"/>
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
6
- <title>nofoxAI</title>
7
  <link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@300;400;500;600&family=DM+Mono:wght@400;500&display=swap" rel="stylesheet"/>
8
  <style>
9
  :root {
@@ -550,7 +550,7 @@
550
  <nav>
551
  <div class="nav-logo">
552
  <div class="dot"></div>
553
- nofoxAI : No Fox Given
554
  </div>
555
  <div class="nav-pills">
556
  <div class="nav-pill" id="navMode">—</div>
 
3
  <head>
4
  <meta charset="UTF-8"/>
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
6
+ <title>NoFoxAI</title>
7
  <link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@300;400;500;600&family=DM+Mono:wght@400;500&display=swap" rel="stylesheet"/>
8
  <style>
9
  :root {
 
550
  <nav>
551
  <div class="nav-logo">
552
  <div class="dot"></div>
553
+ NoFoxAI : No Fox Given
554
  </div>
555
  <div class="nav-pills">
556
  <div class="nav-pill" id="navMode">—</div>
requirements.txt CHANGED
@@ -1,16 +1,10 @@
1
- streamlit==1.31.0
2
- faster-whisper==1.0.0
3
- langchain==0.1.10
4
- langchain-community==0.0.24
5
- langchain-huggingface==0.0.1
6
- langgraph==0.0.30
7
- chromadb==0.4.22
8
- sentence-transformers==2.3.1
9
- sounddevice==0.4.6
10
- soundfile==0.12.1
11
- numpy==1.26.3
12
- pydub==0.25.1
13
- requests==2.31.0
14
- pyyaml==6.0.1
15
- pyaudio==0.2.14
16
- pyttsx3==2.90
 
1
+ httpx
2
+ EOF
3
+ groq
4
+ fastapi
5
+ uvicorn
6
+ python-multipart
7
+ python-dotenv
8
+ python-docx
9
+ chromadb
10
+ sentence-transformers
 
 
 
 
 
 
src/analytics.py ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import os
3
+ from groq import Groq
4
+ from dotenv import load_dotenv
5
+
6
+ load_dotenv()
7
+ client = Groq(api_key=os.environ.get("GROQ_API_KEY"))
8
+
9
+ def analyze_interview(session: dict) -> dict:
10
+ messages = session.get("messages", [])
11
+ mode = session.get("mode", "general")
12
+ company = session.get("company", "")
13
+ role = session.get("role", "SDE")
14
+
15
+ # Build transcript
16
+ transcript = ""
17
+ for m in messages:
18
+ if m["role"] == "user":
19
+ transcript += f"CANDIDATE: {m['content']}\n\n"
20
+ elif m["role"] == "assistant":
21
+ transcript += f"INTERVIEWER: {m['content']}\n\n"
22
+
23
+ if not transcript.strip():
24
+ return {}
25
+
26
+ prompt = f"""You are an expert interview coach analyzing a {mode} interview for a {role} role{' at ' + company.capitalize() if company else ''}.
27
+
28
+ Here is the full interview transcript:
29
+ ---
30
+ {transcript[:6000]}
31
+ ---
32
+
33
+ Analyze this interview and return a JSON object with EXACTLY this structure (no extra text, just valid JSON):
34
+ {{
35
+ "overall_score": <number 0-100>,
36
+ "summary": "<2-3 sentence honest overall assessment>",
37
+ "strong_points": [
38
+ {{"title": "<strength title>", "detail": "<specific example from interview>"}},
39
+ {{"title": "<strength title>", "detail": "<specific example from interview>"}},
40
+ {{"title": "<strength title>", "detail": "<specific example from interview>"}}
41
+ ],
42
+ "pain_points": [
43
+ {{"title": "<weakness title>", "detail": "<specific example and why it matters>"}},
44
+ {{"title": "<weakness title>", "detail": "<specific example and why it matters>"}},
45
+ {{"title": "<weakness title>", "detail": "<specific example and why it matters>"}}
46
+ ],
47
+ "areas_of_improvement": [
48
+ {{"title": "<area>", "action": "<specific actionable advice>"}},
49
+ {{"title": "<area>", "action": "<specific actionable advice>"}},
50
+ {{"title": "<area>", "action": "<specific actionable advice>"}}
51
+ ],
52
+ "skill_scores": {{
53
+ "technical": <0-100>,
54
+ "communication": <0-100>,
55
+ "problem_solving": <0-100>,
56
+ "confidence": <0-100>,
57
+ "cultural_fit": <0-100>
58
+ }},
59
+ "hiring_verdict": "<Strong Hire | Hire | Maybe | No Hire>",
60
+ "verdict_reason": "<one sentence explanation>"
61
+ }}"""
62
+
63
+ response = client.chat.completions.create(
64
+ model="llama-3.3-70b-versatile",
65
+ messages=[{"role": "user", "content": prompt}],
66
+ max_tokens=1500
67
+ )
68
+
69
+ raw = response.choices[0].message.content.strip()
70
+ # Strip markdown if present
71
+ if raw.startswith("```"):
72
+ raw = raw.split("```")[1]
73
+ if raw.startswith("json"):
74
+ raw = raw[4:]
75
+ raw = raw.strip()
76
+
77
+ try:
78
+ return json.loads(raw)
79
+ except Exception:
80
+ return {"error": "Could not parse analysis", "raw": raw}
src/main.py CHANGED
@@ -110,7 +110,7 @@ def build_company_prompt(company: str, role: str, context: str) -> str:
110
  Structure this interview EXACTLY as follows:
111
 
112
  PHASE 1 - INTRODUCTION & RESUME (exchanges 1-3):
113
- - Warmly greet the candidate as a {company} interviewer would
114
  - Ask them to introduce themselves
115
  - Ask resume-based questions specific to {role} at {company}: past projects, tech stack, scale of systems built
116
  - Ask why they want to join {company} specifically — probe for genuine motivation
@@ -147,7 +147,7 @@ async def start_interview(request: Request):
147
  check_rate_limit(request.client.host)
148
  body = await request.json()
149
  mode = body.get("mode", "behavioral")
150
- company = body.get("company", "").lower()
151
  role = body.get("role", "SDE")
152
 
153
  session_id = str(time.time())
@@ -299,4 +299,4 @@ async def synthesize_speech(request: Request):
299
  async def serve_index():
300
  return FileResponse("public/index.html")
301
 
302
- app.mount("/", StaticFiles(directory="public"), name="static")
 
110
  Structure this interview EXACTLY as follows:
111
 
112
  PHASE 1 - INTRODUCTION & RESUME (exchanges 1-3):
113
+ - Warmly greet the candidate as a {company} interviewer. Introduce yourself as "Rohan" — a senior {role} interviewer at {company}. Sound human and natural, not robotic.
114
  - Ask them to introduce themselves
115
  - Ask resume-based questions specific to {role} at {company}: past projects, tech stack, scale of systems built
116
  - Ask why they want to join {company} specifically — probe for genuine motivation
 
147
  check_rate_limit(request.client.host)
148
  body = await request.json()
149
  mode = body.get("mode", "behavioral")
150
+ company = (body.get("company") or "").lower()
151
  role = body.get("role", "SDE")
152
 
153
  session_id = str(time.time())
 
299
  async def serve_index():
300
  return FileResponse("public/index.html")
301
 
302
+ app.mount("/", StaticFiles(directory="public"), name="static")