Add AI Resume Upload & Automatic Parsing feature

This commit is contained in:
2026-07-22 17:12:51 -04:00
parent 735b0eb298
commit 9ccaec0e7c
3 changed files with 177 additions and 9 deletions
+77 -8
View File
@@ -99,6 +99,54 @@ export const ProfileSettings: React.FC<ProfileSettingsProps> = ({
handleUpdateField('skills', updated);
};
const [isParsingResume, setIsParsingResume] = useState(false);
const [parseError, setParseError] = useState<string | null>(null);
const handleResumeFileUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
setIsParsingResume(true);
setParseError(null);
try {
const text = await file.text();
const res = await fetch('/api/gemini/parse-resume', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
resumeText: text,
llmConfig: profile.llmConfig
})
});
const data = await res.json();
if (data.success && data.profile) {
const parsed = data.profile;
setProfile(prev => ({
...prev,
fullName: parsed.fullName || prev.fullName,
email: parsed.email || prev.email,
phone: parsed.phone || prev.phone,
location: parsed.location || prev.location,
linkedinUrl: parsed.linkedinUrl || prev.linkedinUrl,
githubUrl: parsed.githubUrl || prev.githubUrl,
portfolioUrl: parsed.portfolioUrl || prev.portfolioUrl,
summary: parsed.summary || prev.summary,
skills: Array.isArray(parsed.skills) && parsed.skills.length > 0 ? parsed.skills : prev.skills,
experience: Array.isArray(parsed.experience) && parsed.experience.length > 0 ? parsed.experience : prev.experience,
education: Array.isArray(parsed.education) && parsed.education.length > 0 ? parsed.education : prev.education
}));
} else {
setParseError(data.error || 'Failed to parse resume content.');
}
} catch (err: any) {
setParseError(`Failed to read/parse file: ${err.message}`);
} finally {
setIsParsingResume(false);
}
};
const handleSave = async () => {
setIsSaving(true);
try {
@@ -126,16 +174,37 @@ export const ProfileSettings: React.FC<ProfileSettingsProps> = ({
</p>
</div>
<button
onClick={handleSave}
disabled={isSaving}
className="px-5 py-2.5 rounded-xl bg-indigo-600 hover:bg-indigo-500 text-white font-bold text-xs shadow-lg shadow-indigo-600/30 disabled:opacity-50 flex items-center gap-2 transition-all shrink-0"
>
{savedSuccess ? <CheckCircle2 className="w-4 h-4 text-emerald-300" /> : <Save className="w-4 h-4" />}
<span>{isSaving ? 'Saving...' : savedSuccess ? 'Saved to Firestore!' : 'Save Master Profile'}</span>
</button>
<div className="flex items-center gap-3">
<label className="px-4 py-2.5 rounded-xl bg-slate-800 hover:bg-slate-700 border border-slate-700 text-slate-200 font-semibold text-xs cursor-pointer flex items-center gap-2 transition-all">
<Sparkles className="w-4 h-4 text-amber-400" />
<span>{isParsingResume ? 'Parsing Resume with AI...' : 'Upload & Parse Resume'}</span>
<input
type="file"
accept=".txt,.md,.json,.pdf,.docx"
onChange={handleResumeFileUpload}
disabled={isParsingResume}
className="hidden"
/>
</label>
<button
onClick={handleSave}
disabled={isSaving}
className="px-5 py-2.5 rounded-xl bg-indigo-600 hover:bg-indigo-500 text-white font-bold text-xs shadow-lg shadow-indigo-600/30 disabled:opacity-50 flex items-center gap-2 transition-all shrink-0"
>
{savedSuccess ? <CheckCircle2 className="w-4 h-4 text-emerald-300" /> : <Save className="w-4 h-4" />}
<span>{isSaving ? 'Saving...' : savedSuccess ? 'Saved to Firestore!' : 'Save Master Profile'}</span>
</button>
</div>
</div>
{parseError && (
<div className="p-4 rounded-xl bg-rose-500/10 border border-rose-500/30 text-rose-300 text-xs flex items-center gap-2">
<AlertCircle className="w-4 h-4 text-rose-400 shrink-0" />
<span>{parseError}</span>
</div>
)}
{/* Main Profile Forms */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">