/* ai-panel.jsx — AI Resume Coach drawer */ function AIPanel({ data, patch, coverLetter, setCoverLetter, showToast, onGotoCoverLetter, onClose }) { const { useState: S, useRef: R, useEffect: E } = React; const isPro = !!window.IS_PRO; const [messages, setMessages] = S([ { id: 0, role: "assistant", content: "Hi! I'm your BDRecruit AI Assistant. Use the quick actions below or ask me anything about your résumé.", done: true } ]); const [input, setInput] = S(""); const [busy, setBusy] = S(false); const [limitHit, setLimitHit] = S(false); const [remaining, setRemaining] = S(() => { var v = window.getAIUsesRemaining ? window.getAIUsesRemaining() : null; return v; }); const msgRef = R(null); const inputRef = R(null); const nextId = R(1); const scrollDown = () => { setTimeout(() => { if (msgRef.current) msgRef.current.scrollTop = msgRef.current.scrollHeight; }, 30); }; const send = (userContent, applyFn) => { if (busy || limitHit) return; if (!window.groqStream) return; setBusy(true); const uid = ++nextId.current; setMessages(m => [ ...m, { id: uid - 1, role: "user", content: userContent, done: true }, { id: uid, role: "assistant", content: "", done: false, applyFn: applyFn || null }, ]); scrollDown(); const history = messages.slice(-16).map(x => ({ role: x.role === "assistant" ? "assistant" : "user", content: x.content, })).filter(x => x.content); const ctx = window.buildResumeCtx ? window.buildResumeCtx(data) : ""; window.groqStream({ messages: [ { role: "system", content: (window.GROQ_SYSTEM || "") + "\n\nUser's current résumé:\n" + ctx }, ...history, { role: "user", content: userContent }, ], maxTokens: 600, onChunk: (_, full) => { setMessages(m => m.map(x => x.id === uid ? { ...x, content: full } : x)); scrollDown(); }, onDone: (full) => { setBusy(false); setMessages(m => m.map(x => x.id === uid ? { ...x, content: full, done: true } : x)); // Update remaining count const r = window.getAIUsesRemaining ? window.getAIUsesRemaining() : null; setRemaining(r); scrollDown(); }, onError: (err) => { setBusy(false); if (err === "free_limit_reached") { setLimitHit(true); setMessages(m => m.map(x => x.id === uid ? { ...x, content: "", done: true, error: true, limitHit: true, applyFn: null } : x)); } else { setMessages(m => m.map(x => x.id === uid ? { ...x, content: "⚠️ " + err, done: true, error: true, applyFn: null } : x)); } }, }); }; const applyMsg = (msg) => { if (msg.applyFn) { msg.applyFn(msg.content); setMessages(m => m.map(x => x.id === msg.id ? { ...x, applied: true } : x)); } }; /* ── Quick actions ─────────────────────────────────────────── */ const qa_summary = () => send( "Write a compelling 2–3 sentence professional summary for my résumé. Use strong language, quantify where possible, no first-person pronouns. Return ONLY the summary text.", (content) => patch(d => ({ ...d, summary: content.replace(/^["']|["']$/g, "").trim() })) ); const qa_bullets = () => { const pairs = ((data.experience || [])).flatMap(x => ((x.bullets || []).filter(b => b.trim()).map(b => (x.role || "") + " at " + (x.company || "") + ': "' + b + '"')) ); if (!pairs.length) { send("There are no experience bullets in the résumé yet — please add some work experience first!", null); return; } send( "Rewrite each of these experience bullet points to be more impactful — stronger action verbs, quantified results where inferrable. Return EXACTLY the same number of bullets, one per line, no bullet prefix, no numbering:\n\n" + pairs.join("\n"), (content) => { const improved = content.split("\n").map(l => l.replace(/^[•·\-\*\d.)]+\s*/, "").trim()).filter(Boolean); var idx = 0; patch(d => ({ ...d, experience: (d.experience || []).map(x => ({ ...x, bullets: (x.bullets || []).filter(b => b.trim()).map(() => improved[idx++] || "").filter(Boolean), })), })); } ); }; const qa_coverletter = () => send( "Write a full cover letter based on my résumé. Use 3 paragraphs separated by blank lines: opening (genuine interest in role + who I am), body (2 specific achievements from my background), closing (call to action). Under 350 words. Professional but warm.", (content) => { setCoverLetter(cl => ({ ...cl, body: content.trim() })); if (showToast) showToast("Cover letter saved — opening tab…"); if (onGotoCoverLetter) setTimeout(onGotoCoverLetter, 900); } ); const qa_review = () => send( "Review my entire résumé and give me your top 5 most impactful, specific improvements I can make right now. Be direct and focus on highest-impact changes only — no generic advice." ); const qa_skills = () => send( "Based on my experience and background, what 10–15 additional skills or keywords am I missing that would strengthen my résumé for ATS matching? Group them by category (Tools, Methodologies, Soft Skills, etc.)." ); const qa_email = () => send( "Write a short, compelling cold outreach email I could send when applying for a role. Under 150 words. Include: subject line on its first line, then the email body. Use my most impressive achievement as the hook." ); const quickActions = [ { icon: "text", label: "Write summary", fn: qa_summary }, { icon: "sparkle", label: "Improve bullets", fn: qa_bullets }, { icon: "letter", label: "Cover letter", fn: qa_coverletter }, { icon: "eye", label: "Review résumé", fn: qa_review }, { icon: "bolt", label: "Suggest skills", fn: qa_skills }, { icon: "mail", label: "Cold email", fn: qa_email }, ]; const handleSend = () => { const txt = input.trim(); if (!txt || busy || limitHit) return; setInput(""); send(txt, null); if (inputRef.current) inputRef.current.style.height = "38px"; }; const usageBar = !isPro && remaining !== null && remaining !== Infinity; const usedCount = usageBar ? Math.max(0, 10 - remaining) : 0; return ( <>
{/* Header */}

BDRecruit AI Assistant

Powered by BDRecruit AI

{isPro && ( Pro )}
{/* Usage bar — free users only, after first call */} {usageBar && (
{remaining} / 10 free {remaining === 1 ? "use" : "uses"} left
)} {/* Limit hit banner */} {limitHit && (
You've used all 10 free AI requests
Upgrade to Pro for unlimited AI coaching, the Tailor to Job feature, and priority support.
Upgrade to Pro →
)} {/* Quick actions */}
{quickActions.map((a, i) => ( ))}
{/* Messages */}
{messages.map((msg) => (
{msg.role === "assistant" &&
}
{msg.limitHit ? (
Free limit reached — upgrade to Pro for unlimited AI.
) : (
{msg.content} {!msg.done && !msg.error && }
)} {msg.role === "assistant" && msg.done && !msg.error && msg.content && (
{msg.applyFn && !msg.applied && ( <> )} {msg.applied && ✅ Applied}
)}
))} {busy && (
)}
{/* Input area */}