Spaces:
Sleeping
Sleeping
APPLE commited on
Commit ·
f964ea3
1
Parent(s): 11839ff
initial deploy
Browse files- .gitignore +5 -0
- Dockerfile +15 -0
- public/index.html +2 -2
- requirements.txt +10 -16
- src/analytics.py +80 -0
- 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>
|
| 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 |
-
|
| 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 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 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
|
| 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"
|
| 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")
|