/* achievement.jsx — Achievement quantifier: flags vague bullets, suggests metrics */ function AchievementDrawer({ data, patch, onClose }) { const { useState: S, useEffect: E } = React; const [stage, setStage] = S("idle"); const [flags, setFlags] = S([]); const [errMsg, setErrMsg] = S(""); const [applied, setApplied] = S({}); const [limitHit, setLimitHit] = S(false); E(() => { const onKey = e => { if (e.key === "Escape") onClose(); }; window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, []); const analyze = async () => { if (!window.groqStream) { setErrMsg("AI not available."); setStage("error"); return; } setStage("loading"); setFlags([]); setErrMsg(""); setApplied({}); // Collect all bullets with location keys const bullets = []; (data.experience || []).forEach((x, xi) => (x.bullets || []).forEach((b, bi) => { if (b.trim()) bullets.push({ key: `exp_${xi}_${bi}`, text: b, section: x.role + " @ " + x.company }); }) ); (data.projects || []).forEach((x, xi) => (x.bullets || []).forEach((b, bi) => { if (b.trim()) bullets.push({ key: `proj_${xi}_${bi}`, text: b, section: "Project: " + x.name }); }) ); if (!bullets.length) { setErrMsg("No bullets found. Add experience bullets first."); setStage("error"); return; } let raw = ""; await window.groqStream({ messages: [ { role: "system", content: window.GROQ_SYSTEM || "" }, { role: "user", content: `Analyze these resume bullets. Flag vague ones that lack measurable impact. For each vague bullet, suggest a stronger quantified version. Return ONLY a JSON array. Each item: { "key": "exp_0_0", "vague": true, "reason": "One phrase explaining what's missing (e.g., 'No metric for scale or impact')", "suggestion": "Rewritten bullet with placeholder metric, e.g. 'Led migration of X services, reducing deploy time by Y%'" } Only include bullets where vague=true. Skip already-quantified bullets entirely. Bullets: ${JSON.stringify(bullets.map(b => ({ key: b.key, text: b.text })))}` }, ], maxTokens: 2000, onChunk: (_, full) => { raw = full; }, onDone: () => { try { let s = raw.trim().replace(/^```json\s*/i,"").replace(/^```\s*/i,"").replace(/\s*```$/i,""); const arr = JSON.parse(s.match(/\[[\s\S]*\]/)?.[0] || s); // Merge with original bullet text + section label const merged = arr.map(f => { const orig = bullets.find(b => b.key === f.key); return { ...f, original: orig?.text || "", section: orig?.section || "" }; }); setFlags(merged); setStage("done"); } catch(_) { setErrMsg("Could not parse AI response. Try again."); setStage("error"); } }, onError: e => { if (e === "free_limit_reached") { setLimitHit(true); } else { setErrMsg(typeof e==="string"?e:"AI error."); } setStage("error"); }, }); }; const applyFix = (flag) => { const parts = flag.key.split("_"); const type = parts[0], xi = +parts[1], bi = +parts[2]; patch(d => { const nd = { ...d }; if (type === "exp") { const exp = d.experience.map((x, i) => { if (i !== xi) return x; const bullets = [...x.bullets]; bullets[bi] = flag.suggestion; return { ...x, bullets }; }); return { ...nd, experience: exp }; } else { const projs = d.projects.map((x, i) => { if (i !== xi) return x; const bullets = [...x.bullets]; bullets[bi] = flag.suggestion; return { ...x, bullets }; }); return { ...nd, projects: projs }; } }); setApplied(a => ({ ...a, [flag.key]: true })); }; return ( <>
Flags vague bullets — AI suggests metrics
Scans every bullet in experience and projects. Flags vague phrases like "responsible for" or "helped with" and rewrites them with measurable impact.
Analyzing your bullets…
{errMsg}
No vague bullets found. Your resume already reads with impact.