lakshmisravya123 commited on
Commit
539a59c
·
1 Parent(s): 5d94d12

Upgrade: week-by-week roadmap, salary impact, portfolio projects, quick wins

Browse files
.gitignore CHANGED
@@ -1,4 +1,4 @@
1
  node_modules/
2
- .env
3
  dist/
 
4
  .DS_Store
 
1
  node_modules/
 
2
  dist/
3
+ .env
4
  .DS_Store
backend/services/ai.js CHANGED
@@ -8,7 +8,15 @@ async function callAI(prompt) {
8
  const res = await fetch('https://api.groq.com/openai/v1/chat/completions', {
9
  method: 'POST',
10
  headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${GROQ_API_KEY}` },
11
- body: JSON.stringify({ model: GROQ_MODEL, messages: [{ role: 'user', content: prompt }], temperature: 0.7 }),
 
 
 
 
 
 
 
 
12
  });
13
  if (res.ok) { const data = await res.json(); return data.choices[0].message.content; }
14
  console.warn('Groq failed, falling back to Ollama...');
@@ -24,32 +32,126 @@ async function callAI(prompt) {
24
 
25
  function parseJSON(text) {
26
  try { return JSON.parse(text.trim()); }
27
- catch { const m = text.match(/\{[\s\S]*\}/); if (m) return JSON.parse(m[0]); throw new Error('Failed to parse AI response'); }
 
 
 
 
28
  }
29
 
30
  async function analyzeGap(jobPosting, userSkills) {
31
- const text = await callAI(`You are a career development expert and skill gap analyst.
32
 
33
- JOB POSTING:
34
  ${jobPosting}
35
 
36
- CANDIDATE'S CURRENT SKILLS/RESUME:
37
  ${userSkills}
38
 
39
- Analyze the gap. Return ONLY valid JSON:
 
40
  {
41
  "jobTitle": "<extracted job title>",
42
- "company": "<extracted company or 'Not specified'>",
43
- "matchScore": <1-100>,
44
- "matchedSkills": [{"skill":"<name>","level":"<strong|moderate|basic>","note":"<note>"}],
45
- "missingSkills": [{"skill":"<name>","importance":"<critical|important|nice-to-have>","difficulty":"<easy|medium|hard>","timeToLearn":"<e.g. 2 weeks>"}],
46
- "learningPath": [{"phase":1,"title":"<title>","duration":"<duration>","skills":["<skill>"],"resources":[{"name":"<name>","type":"<course|book|tutorial|project>","free":true}]}],
47
- "quickWins": ["<action>"],
48
- "resumeTips": ["<tip>"],
49
- "verdict": "<Ready to Apply|Almost Ready|Need More Preparation|Significant Gap>"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  }
51
 
52
- Return ONLY JSON, no markdown.`);
 
 
 
 
 
 
 
 
 
53
  return parseJSON(text);
54
  }
55
 
 
8
  const res = await fetch('https://api.groq.com/openai/v1/chat/completions', {
9
  method: 'POST',
10
  headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${GROQ_API_KEY}` },
11
+ body: JSON.stringify({
12
+ model: GROQ_MODEL,
13
+ messages: [
14
+ { role: 'system', content: 'You are a world-class career advisor, skill gap analyst, and technical recruiter with 20 years of experience. You provide extremely detailed, actionable analysis. Always return valid JSON only.' },
15
+ { role: 'user', content: prompt }
16
+ ],
17
+ temperature: 0.7,
18
+ max_tokens: 4096
19
+ }),
20
  });
21
  if (res.ok) { const data = await res.json(); return data.choices[0].message.content; }
22
  console.warn('Groq failed, falling back to Ollama...');
 
32
 
33
  function parseJSON(text) {
34
  try { return JSON.parse(text.trim()); }
35
+ catch {
36
+ const m = text.match(/\{[\s\S]*\}/);
37
+ if (m) return JSON.parse(m[0]);
38
+ throw new Error('Failed to parse AI response');
39
+ }
40
  }
41
 
42
  async function analyzeGap(jobPosting, userSkills) {
43
+ const text = await callAI(`You are a world-class career development expert, technical recruiter, and skill gap analyst.
44
 
45
+ === JOB POSTING ===
46
  ${jobPosting}
47
 
48
+ === CANDIDATE CURRENT SKILLS / RESUME ===
49
  ${userSkills}
50
 
51
+ Perform an exhaustive skill gap analysis. Return ONLY valid JSON (no markdown, no code fences) with this EXACT structure:
52
+
53
  {
54
  "jobTitle": "<extracted job title>",
55
+ "company": "<extracted company or Not specified>",
56
+ "matchScore": 75,
57
+ "verdict": "<Ready to Apply|Almost Ready|Need More Preparation|Significant Gap>",
58
+
59
+ "matchedSkills": [
60
+ {
61
+ "skill": "<skill name>",
62
+ "level": "<strong|moderate|basic>",
63
+ "currentProficiency": "<advanced|intermediate|beginner>",
64
+ "targetProficiency": "<advanced|intermediate|beginner>",
65
+ "note": "<how this skill relates to the job>"
66
+ }
67
+ ],
68
+
69
+ "missingSkills": [
70
+ {
71
+ "skill": "<skill name>",
72
+ "importance": "<critical|important|nice-to-have>",
73
+ "priorityScore": 8,
74
+ "difficulty": "<easy|medium|hard>",
75
+ "timeToLearn": "<e.g. 2 weeks>",
76
+ "currentProficiency": "none",
77
+ "targetProficiency": "<advanced|intermediate|beginner>",
78
+ "salaryImpact": "<estimated annual salary increase in USD if this skill is acquired, e.g. +5000-10000>",
79
+ "industryDemand": "<very high|high|moderate|low>",
80
+ "demandTrend": "<rising|stable|declining>",
81
+ "portfolioProject": "<a specific project idea to demonstrate this skill>"
82
+ }
83
+ ],
84
+
85
+ "salaryAnalysis": {
86
+ "estimatedRangeWithCurrentSkills": "<e.g. 80000-95000>",
87
+ "estimatedRangeWithAllSkills": "<e.g. 110000-130000>",
88
+ "potentialIncrease": "<e.g. +25000-35000>",
89
+ "topPayingSkills": ["<skill that adds most salary value>", "<next>", "<next>"]
90
+ },
91
+
92
+ "candidateComparison": {
93
+ "strengthsVsTypical": ["<what makes this candidate stand out>"],
94
+ "weaknessesVsTypical": ["<where this candidate falls behind typical applicants>"],
95
+ "competitiveEdge": "<brief summary of how candidate compares to top applicants for this role>"
96
+ },
97
+
98
+ "quickWins": [
99
+ {
100
+ "action": "<specific action to take>",
101
+ "timeRequired": "<e.g. 2 hours, 1 day>",
102
+ "impact": "<high|medium|low>"
103
+ }
104
+ ],
105
+
106
+ "learningRoadmap": [
107
+ {
108
+ "week": "<e.g. Week 1-2>",
109
+ "phase": 1,
110
+ "title": "<phase title>",
111
+ "focusSkills": ["<skill>"],
112
+ "dailyHours": 2,
113
+ "milestones": ["<what you should be able to do by end of this phase>"],
114
+ "resources": [
115
+ {
116
+ "name": "<specific course/tutorial/book name>",
117
+ "url": "<actual URL if possible, otherwise empty string>",
118
+ "type": "<course|tutorial|book|project|video|documentation>",
119
+ "platform": "<e.g. freeCodeCamp, Coursera, YouTube, MDN, etc.>",
120
+ "free": true,
121
+ "estimatedHours": 10
122
+ }
123
+ ]
124
+ }
125
+ ],
126
+
127
+ "portfolioProjects": [
128
+ {
129
+ "title": "<project name>",
130
+ "description": "<what to build>",
131
+ "skillsDemonstrated": ["<skill>"],
132
+ "difficulty": "<beginner|intermediate|advanced>",
133
+ "estimatedTime": "<e.g. 1 week>",
134
+ "techStack": ["<technology>"]
135
+ }
136
+ ],
137
+
138
+ "resumeTips": ["<actionable tip to improve resume for this specific role>"],
139
+
140
+ "interviewPrep": [
141
+ "<likely interview question or topic for this role>"
142
+ ]
143
  }
144
 
145
+ IMPORTANT INSTRUCTIONS:
146
+ - Be extremely specific with resource recommendations - use real course names, real platforms
147
+ - For salary figures, use realistic market data for this role
148
+ - Priority scores should reflect how critical each skill is for getting hired
149
+ - Quick wins should be things achievable in under a week
150
+ - The learning roadmap should be week-by-week for 4-8 weeks
151
+ - Portfolio projects should be impressive enough to discuss in interviews
152
+ - Compare against what a strong candidate for this specific role looks like
153
+ - Return ONLY the JSON object, no other text`);
154
+
155
  return parseJSON(text);
156
  }
157
 
frontend/src/components/Results.jsx CHANGED
@@ -1,4 +1,4 @@
1
- import React from 'react';
2
 
3
  function scoreClass(score) {
4
  if (score >= 70) return 'good';
@@ -21,9 +21,9 @@ export default function Results({ data, onReset }) {
21
  {/* Matched Skills */}
22
  <div className="card">
23
  <h3>Skills You Have</h3>
24
- {a.matchedSkills.map((s, i) => (
25
  <span key={i} className={`skill-tag ${s.level}`}>
26
- {s.skill}
27
  </span>
28
  ))}
29
  </div>
@@ -31,11 +31,13 @@ export default function Results({ data, onReset }) {
31
  {/* Missing Skills */}
32
  <div className="card">
33
  <h3>Skills to Develop</h3>
34
- {a.missingSkills.map((s, i) => (
35
  <div key={i} className="missing-skill">
36
  <div>
37
- <div className="name">{s.skill}</div>
38
- <div className="meta">{s.difficulty} to learn &middot; ~{s.timeToLearn}</div>
 
 
39
  </div>
40
  <span className={`importance-badge ${s.importance}`}>{s.importance}</span>
41
  </div>
@@ -45,14 +47,14 @@ export default function Results({ data, onReset }) {
45
  {/* Learning Path */}
46
  <div className="card">
47
  <h3>Learning Path</h3>
48
- {a.learningPath.map((phase, i) => (
49
  <div key={i} className="phase-card">
50
  <h4>Phase {phase.phase}: {phase.title}</h4>
51
- <div className="duration">{phase.duration}</div>
52
- <div className="skills-list">Skills: {phase.skills.join(', ')}</div>
53
- {phase.resources.map((r, j) => (
54
  <div key={j} className="resource">
55
- <span className="type">[{r.type}]</span> {r.name} {r.free && <span className="free-badge">FREE</span>}
56
  </div>
57
  ))}
58
  </div>
@@ -62,15 +64,61 @@ export default function Results({ data, onReset }) {
62
  {/* Quick Wins */}
63
  <div className="card list-card wins">
64
  <h3>Quick Wins (Do This Week)</h3>
65
- <ul>{a.quickWins.map((w, i) => <li key={i}>{w}</li>)}</ul>
66
  </div>
67
 
68
  {/* Resume Tips */}
69
  <div className="card list-card tips">
70
  <h3>Resume Tailoring Tips</h3>
71
- <ul>{a.resumeTips.map((t, i) => <li key={i}>{t}</li>)}</ul>
72
  </div>
73
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  <button className="btn-secondary" onClick={onReset}>Analyze Another Job</button>
75
  </div>
76
  );
 
1
+ import React, { useState } from "react";
2
 
3
  function scoreClass(score) {
4
  if (score >= 70) return 'good';
 
21
  {/* Matched Skills */}
22
  <div className="card">
23
  <h3>Skills You Have</h3>
24
+ {(a.matchedSkills||[]).map((s, i) => (
25
  <span key={i} className={`skill-tag ${s.level}`}>
26
+ {s.skill}{s.currentProficiency?" ("+s.currentProficiency+")":""}
27
  </span>
28
  ))}
29
  </div>
 
31
  {/* Missing Skills */}
32
  <div className="card">
33
  <h3>Skills to Develop</h3>
34
+ {(a.missingSkills||[]).map((s, i) => (
35
  <div key={i} className="missing-skill">
36
  <div>
37
+ <div className="name">{s.skill} {s.priorityScore && <span className="priority-score">P{s.priorityScore}/10</span>}</div>
38
+ <div className="meta">{s.difficulty} to learn &middot; ~{s.timeToLearn}{s.industryDemand && <span className="demand-tag"> &middot; {s.industryDemand} demand{s.demandTrend==="rising"?" ↑":s.demandTrend==="declining"?" ↓":""}</span>}</div>
39
+ {s.salaryImpact && <div className="salary-inline">Salary impact: {s.salaryImpact}</div>}
40
+ {s.portfolioProject && <div className="portfolio-inline">Project: {s.portfolioProject}</div>}
41
  </div>
42
  <span className={`importance-badge ${s.importance}`}>{s.importance}</span>
43
  </div>
 
47
  {/* Learning Path */}
48
  <div className="card">
49
  <h3>Learning Path</h3>
50
+ {(a.learningRoadmap||a.learningPath||[]).map((phase, i) => (
51
  <div key={i} className="phase-card">
52
  <h4>Phase {phase.phase}: {phase.title}</h4>
53
+ <div className="duration">{phase.week||phase.duration}</div>
54
+ <div className="skills-list">Skills: {(phase.focusSkills||phase.skills||[]).join(', ')}</div>
55
+ {(phase.resources||[]).map((r, j) => (
56
  <div key={j} className="resource">
57
+ <span className="type">[{r.type}]</span> {r.url ? <a href={r.url} target="_blank" rel="noreferrer" style={{color:"var(--accent)"}}>{r.name}</a> : r.name}{r.platform ? " ("+r.platform+")" : ""} {r.free && <span className="free-badge">FREE</span>}
58
  </div>
59
  ))}
60
  </div>
 
64
  {/* Quick Wins */}
65
  <div className="card list-card wins">
66
  <h3>Quick Wins (Do This Week)</h3>
67
+ <ul>{(a.quickWins||[]).map((w, i) => { const item = typeof w==="string"?{action:w}:w; return <li key={i}>{item.action}{item.timeRequired?" ("+item.timeRequired+")":""}{item.impact?" - "+item.impact+" impact":""}</li>; })}</ul>
68
  </div>
69
 
70
  {/* Resume Tips */}
71
  <div className="card list-card tips">
72
  <h3>Resume Tailoring Tips</h3>
73
+ <ul>{(a.resumeTips||[]).map((t, i) => <li key={i}>{t}</li>)}</ul>
74
  </div>
75
 
76
+ {/* Candidate Comparison */}
77
+ {a.candidateComparison && (
78
+ <div className="card comparison-card">
79
+ <h3>How You Compare</h3>
80
+ <p className="comp-edge">{a.candidateComparison.competitiveEdge}</p>
81
+ <div className="comp-grid">
82
+ <div className="comp-col good">
83
+ <h4>Your Strengths</h4>
84
+ <ul>{(a.candidateComparison.strengthsVsTypical||[]).map((s,i)=><li key={i}>{s}</li>)}</ul>
85
+ </div>
86
+ <div className="comp-col warn">
87
+ <h4>Areas to Improve</h4>
88
+ <ul>{(a.candidateComparison.weaknessesVsTypical||[]).map((w,i)=><li key={i}>{w}</li>)}</ul>
89
+ </div>
90
+ </div>
91
+ </div>
92
+ )}
93
+
94
+ {/* Salary Analysis */}
95
+ {a.salaryAnalysis && (
96
+ <div className="card salary-card">
97
+ <h3>Salary Impact Analysis</h3>
98
+ <div className="salary-grid">
99
+ <div className="salary-box"><div className="sal-label">Current Skills</div><div className="sal-value">{a.salaryAnalysis.estimatedRangeWithCurrentSkills}</div></div>
100
+ <div className="salary-arrow">{"→"}</div>
101
+ <div className="salary-box target"><div className="sal-label">With All Skills</div><div className="sal-value">{a.salaryAnalysis.estimatedRangeWithAllSkills}</div></div>
102
+ </div>
103
+ <div className="sal-increase">Potential increase: <strong>{a.salaryAnalysis.potentialIncrease}</strong></div>
104
+ {a.salaryAnalysis.topPayingSkills && <div className="top-skills"><strong>Highest-value skills:</strong> {a.salaryAnalysis.topPayingSkills.join(", ")}</div>}
105
+ </div>
106
+ )}
107
+
108
+ {a.portfolioProjects&&a.portfolioProjects.length>0&&(
109
+ <div className={"card"}>
110
+ <h3>Portfolio Projects</h3>
111
+ {a.portfolioProjects.map((p,i)=><div key={i} className={"project-item"}><strong>{p.title}</strong><p>{p.description}</p><div>{p.estimatedTime} | {p.difficulty}</div></div>)}
112
+ </div>
113
+ )}
114
+
115
+ {a.interviewPrep&&a.interviewPrep.length>0&&(
116
+ <div className={"card list-card tips"}>
117
+ <h3>Interview Preparation</h3>
118
+ <ul>{a.interviewPrep.map((q,i)=><li key={i}>{q}</li>)}</ul>
119
+ </div>
120
+ )}
121
+
122
  <button className="btn-secondary" onClick={onReset}>Analyze Another Job</button>
123
  </div>
124
  );
frontend/src/styles/global.css CHANGED
@@ -183,3 +183,34 @@ body {
183
  @keyframes spin { to { transform: rotate(360deg); } }
184
 
185
  @media (max-width: 600px) { .header h1 { font-size: 1.6rem; } }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183
  @keyframes spin { to { transform: rotate(360deg); } }
184
 
185
  @media (max-width: 600px) { .header h1 { font-size: 1.6rem; } }
186
+
187
+
188
+ .priority-score { font-size:0.75rem; background:rgba(88,166,255,0.15); color:var(--accent); padding:0.1rem 0.4rem; border-radius:8px; margin-left:0.5rem; }
189
+ .demand-tag { color:var(--accent2); font-weight:500; }
190
+ .salary-inline { font-size:0.8rem; color:var(--accent2); margin-top:0.3rem; }
191
+ .portfolio-inline { font-size:0.8rem; color:var(--text-dim); margin-top:0.2rem; font-style:italic; }
192
+ .salary-card { }
193
+ .salary-grid { display:flex; align-items:center; justify-content:center; gap:1.5rem; margin:1rem 0; flex-wrap:wrap; }
194
+ .salary-box { background:var(--bg); padding:1.2rem 1.5rem; border-radius:10px; text-align:center; min-width:180px; }
195
+ .salary-box.target { border:2px solid var(--accent2); }
196
+ .sal-label { font-size:0.8rem; color:var(--text-dim); margin-bottom:0.3rem; }
197
+ .sal-value { font-size:1.4rem; font-weight:700; color:var(--text); }
198
+ .salary-arrow { font-size:2rem; color:var(--accent); }
199
+ .sal-increase { text-align:center; margin-top:1rem; font-size:1.1rem; color:var(--accent2); }
200
+ .top-skills { text-align:center; margin-top:0.8rem; color:var(--text-dim); font-size:0.9rem; }
201
+ .comparison-card { }
202
+ .comp-edge { color:var(--text-dim); font-size:0.95rem; margin-bottom:1rem; line-height:1.5; }
203
+ .comp-grid { display:grid; grid-template-columns:1fr 1fr; gap:1rem; }
204
+ .comp-col { background:var(--bg); padding:1rem; border-radius:8px; }
205
+ .comp-col h4 { margin-bottom:0.5rem; font-size:0.9rem; }
206
+ .comp-col.good { border-left:3px solid var(--accent2); }
207
+ .comp-col.good h4 { color:var(--accent2); }
208
+ .comp-col.warn { border-left:3px solid var(--warning); }
209
+ .comp-col.warn h4 { color:var(--warning); }
210
+ .comp-col ul { list-style:none; padding:0; }
211
+ .comp-col li { font-size:0.85rem; color:var(--text-dim); padding:0.3rem 0; }
212
+ .project-item { background:var(--bg); padding:1rem; border-radius:8px; margin-bottom:0.8rem; }
213
+ .project-item strong { color:var(--accent); }
214
+ .project-item p { color:var(--text-dim); font-size:0.9rem; margin:0.4rem 0; }
215
+ .proj-meta { font-size:0.8rem; color:var(--text-dim); }
216
+ @media(max-width:600px){.comp-grid{grid-template-columns:1fr;}.salary-grid{flex-direction:column;}}