Auto-deploy: 2026-05-17 02:14:05
This commit is contained in:
98
content.js
98
content.js
@@ -567,6 +567,13 @@
|
|||||||
analyzeBtn.disabled = true;
|
analyzeBtn.disabled = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if (tab === 'qa') {
|
||||||
|
const freshData = extractApplicationQuestions();
|
||||||
|
if (freshData.length > 0) {
|
||||||
|
jobData.questions = freshData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const prompt = buildPromptV2(tab, jobData, settings.userProfile, settings.language);
|
const prompt = buildPromptV2(tab, jobData, settings.userProfile, settings.language);
|
||||||
const response = await chrome.runtime.sendMessage({
|
const response = await chrome.runtime.sendMessage({
|
||||||
type: 'GEMINI_REQUEST',
|
type: 'GEMINI_REQUEST',
|
||||||
@@ -749,6 +756,25 @@ Brief honest assessment of this opportunity for my profile`
|
|||||||
// ─── Markdown Renderer ───────────────────────────────────────────────────
|
// ─── Markdown Renderer ───────────────────────────────────────────────────
|
||||||
|
|
||||||
function renderMarkdown(text) {
|
function renderMarkdown(text) {
|
||||||
|
try {
|
||||||
|
const startIdx = text.indexOf('{');
|
||||||
|
const endIdx = text.lastIndexOf('}');
|
||||||
|
if (startIdx !== -1 && endIdx !== -1) {
|
||||||
|
const parsed = JSON.parse(text.substring(startIdx, endIdx + 1));
|
||||||
|
let html = '<div style="display:flex;flex-direction:column;gap:12px;">';
|
||||||
|
for (const [q, a] of Object.entries(parsed)) {
|
||||||
|
html += `<div style="background: rgba(255,255,255,0.05); padding: 10px; border-radius: 6px;">
|
||||||
|
<div style="font-weight: 600; font-size: 13px; color: #fff; margin-bottom: 4px;">❓ ${q}</div>
|
||||||
|
<div style="color: #4caf50; font-size: 13px;">💡 ${a}</div>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
html += '</div>';
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
} catch(e) {
|
||||||
|
// Not JSON, continue to normal markdown rendering
|
||||||
|
}
|
||||||
|
|
||||||
return text
|
return text
|
||||||
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
||||||
.replace(/^## (.+)$/gm, '<h3>$1</h3>')
|
.replace(/^## (.+)$/gm, '<h3>$1</h3>')
|
||||||
@@ -766,25 +792,77 @@ Brief honest assessment of this opportunity for my profile`
|
|||||||
|
|
||||||
function autoFillAnswers(qaText, root) {
|
function autoFillAnswers(qaText, root) {
|
||||||
showPanelToast(root, '🔄 Attempting to fill answers...');
|
showPanelToast(root, '🔄 Attempting to fill answers...');
|
||||||
// Parse numbered answers from AI response
|
let answers = {};
|
||||||
const answerLines = qaText.split('\n');
|
try {
|
||||||
const inputs = document.querySelectorAll('.jobs-easy-apply-content input[type="text"], .jobs-easy-apply-content textarea');
|
const startIndex = qaText.indexOf('{');
|
||||||
|
const endIndex = qaText.lastIndexOf('}');
|
||||||
|
if (startIndex !== -1 && endIndex !== -1) {
|
||||||
|
answers = JSON.parse(qaText.substring(startIndex, endIndex + 1));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to parse AI answers JSON:', e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(answers).length === 0) {
|
||||||
|
showPanelToast(root, '⚠️ Could not parse answers. Please copy manually.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let filled = 0;
|
let filled = 0;
|
||||||
inputs.forEach((input, idx) => {
|
const labels = document.querySelectorAll('.jobs-easy-apply-content label, .jobs-easy-apply-content legend, .jobs-easy-apply-content .fb-dash-form-element__label');
|
||||||
const answerLine = answerLines.find(l => l.match(new RegExp(`^${idx + 1}\\.`)));
|
|
||||||
if (answerLine) {
|
labels.forEach(label => {
|
||||||
const answer = answerLine.replace(/^\d+\.\s*/, '').trim();
|
const qText = label.textContent.trim().toLowerCase();
|
||||||
if (answer) {
|
if (!qText) return;
|
||||||
input.value = answer;
|
|
||||||
|
const matchedKey = Object.keys(answers).find(k => {
|
||||||
|
const kLower = k.toLowerCase();
|
||||||
|
return kLower.includes(qText) || qText.includes(kLower);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (matchedKey && answers[matchedKey]) {
|
||||||
|
const parent = label.closest('.fb-dash-form-element') || label.parentElement;
|
||||||
|
const answerVal = String(answers[matchedKey]);
|
||||||
|
|
||||||
|
// 1. Fill Text inputs / Textareas
|
||||||
|
const input = parent.querySelector('input[type="text"], textarea');
|
||||||
|
if (input) {
|
||||||
|
input.value = answerVal;
|
||||||
input.dispatchEvent(new Event('input', { bubbles: true }));
|
input.dispatchEvent(new Event('input', { bubbles: true }));
|
||||||
input.dispatchEvent(new Event('change', { bubbles: true }));
|
input.dispatchEvent(new Event('change', { bubbles: true }));
|
||||||
filled++;
|
filled++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. Select dropdowns
|
||||||
|
const select = parent.querySelector('select');
|
||||||
|
if (select) {
|
||||||
|
const options = Array.from(select.options);
|
||||||
|
const matchedOpt = options.find(o => o.text.toLowerCase() === answerVal.toLowerCase() || o.value.toLowerCase() === answerVal.toLowerCase() || o.text.toLowerCase().includes(answerVal.toLowerCase()));
|
||||||
|
if (matchedOpt) {
|
||||||
|
select.value = matchedOpt.value;
|
||||||
|
select.dispatchEvent(new Event('change', { bubbles: true }));
|
||||||
|
filled++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Radio Buttons (Yes/No)
|
||||||
|
const radios = parent.querySelectorAll('input[type="radio"]');
|
||||||
|
if (radios.length > 0) {
|
||||||
|
let radioClicked = false;
|
||||||
|
radios.forEach(radio => {
|
||||||
|
const radioLabel = radio.parentElement.textContent.trim().toLowerCase();
|
||||||
|
const aLower = answerVal.toLowerCase();
|
||||||
|
if (radioLabel === aLower || (aLower === 'yes' && radioLabel.includes('yes')) || (aLower === 'no' && radioLabel.includes('no'))) {
|
||||||
|
radio.click();
|
||||||
|
radioClicked = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (radioClicked) filled++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
showPanelToast(root, filled > 0 ? `✅ Filled ${filled} fields` : '⚠️ Could not auto-fill. Copy answers manually.');
|
showPanelToast(root, filled > 0 ? `✅ Filled ${filled} fields` : '⚠️ No matching fields found to auto-fill.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Utilities ───────────────────────────────────────────────────────────
|
// ─── Utilities ───────────────────────────────────────────────────────────
|
||||||
|
|||||||
49
prompts.js
49
prompts.js
@@ -112,47 +112,28 @@ Respond EXACTLY:
|
|||||||
## NEW BULLET POINTS TO ADD
|
## NEW BULLET POINTS TO ADD
|
||||||
- [2-3 new achievement bullets ready to paste into CV — in English]`;
|
- [2-3 new achievement bullets ready to paste into CV — in English]`;
|
||||||
|
|
||||||
P.qa = `You are a career coach. Generate ready-to-paste application answers.
|
const dynamicQuestions = job.questions && job.questions.length > 0
|
||||||
IMPORTANT: ALL answers MUST be in English regardless of the job posting language.
|
? 'Answer THESE specific application questions:\n' + job.questions.map((q, i) => `${i + 1}. ${q.question}`).join('\n')
|
||||||
|
: 'Answer these common application questions:\n1. Why are you interested in this role?\n2. What is your relevant experience?\n3. What are your salary expectations?\n4. When can you start?\n5. Do you require visa sponsorship?';
|
||||||
|
|
||||||
|
P.qa = `You are a career coach.
|
||||||
|
IMPORTANT: ALL answers MUST be in English. Be highly concise (1 sentence max for text inputs, Yes/No for radio inputs).
|
||||||
|
|
||||||
${prof}
|
${prof}
|
||||||
|
|
||||||
JOB:
|
JOB:
|
||||||
${ctx}
|
${ctx}
|
||||||
|
|
||||||
Generate answers for ALL:
|
${dynamicQuestions}
|
||||||
|
|
||||||
## 1. Why are you interested in this role?
|
RETURN EXACTLY A RAW JSON OBJECT where keys are the exact questions above and values are your concise answers.
|
||||||
[3-4 sentences specific to ${co} and this role — in English]
|
Do NOT use markdown code blocks like \`\`\`json. Just return the raw JSON.
|
||||||
|
Example:
|
||||||
## 2. Why ${co}?
|
{
|
||||||
[2-3 sentences referencing something specific about the company]
|
"Have you completed the following level of education: Bachelor's Degree?": "Yes",
|
||||||
|
"How many years of work experience do you have with Infrastructure?": "6",
|
||||||
## 3. Relevant experience
|
"Why are you interested in this role?": "My background in mapping perfectly aligns with your needs."
|
||||||
[4-5 sentences — most relevant achievements for this role]
|
}`;
|
||||||
|
|
||||||
## 4. Expected salary
|
|
||||||
[Competitive range for ${loc} market — USD and local currency]
|
|
||||||
|
|
||||||
## 5. When can you start?
|
|
||||||
Available immediately or within 2 weeks.
|
|
||||||
|
|
||||||
## 6. Visa sponsorship needed?
|
|
||||||
Based in Jordan. Open to relocation. Willing to process visa requirements.
|
|
||||||
|
|
||||||
## 7. Notice period
|
|
||||||
Available immediately — currently seeking new opportunities.
|
|
||||||
|
|
||||||
## 8. Willing to relocate?
|
|
||||||
Yes, open to ${loc} and broader Middle East.
|
|
||||||
|
|
||||||
## 9. Why are you the best candidate?
|
|
||||||
[3-4 sentences — unique differentiators for THIS role]
|
|
||||||
|
|
||||||
## 10. Key technology experience
|
|
||||||
[Identify top 2-3 technologies from the job description and write specific answers about my experience]
|
|
||||||
|
|
||||||
RULES: ALL answers in English. Ready to paste. No brackets. Concise. Use numbers.`;
|
|
||||||
|
|
||||||
P.benefits = `You are a career analyst specializing in tech compensation in MENA.
|
P.benefits = `You are a career analyst specializing in tech compensation in MENA.
|
||||||
IMPORTANT: Respond ENTIRELY in English regardless of the job posting language.
|
IMPORTANT: Respond ENTIRELY in English regardless of the job posting language.
|
||||||
|
|||||||
Reference in New Issue
Block a user