Files
cv/popup.js
2026-06-02 18:31:11 +03:00

432 lines
16 KiB
JavaScript

// popup.js — Settings popup logic
// ─── Helpers ────────────────────────────────────────────────────────────────
function showToast(msg, type = '') {
const toast = document.getElementById('toast');
toast.textContent = msg;
toast.className = 'toast show ' + type;
setTimeout(() => { toast.className = 'toast'; }, 2500);
}
function setKeyStatus(state) {
const badge = document.getElementById('key-status');
badge.style.display = 'inline-flex';
if (state === 'ok') {
badge.className = 'status-badge success';
badge.innerHTML = '<span class="status-dot"></span> Valid';
} else if (state === 'error') {
badge.className = 'status-badge error';
badge.innerHTML = '<span class="status-dot"></span> Invalid';
} else if (state === 'checking') {
badge.className = 'status-badge checking';
badge.innerHTML = '<span class="status-dot"></span> Testing...';
}
}
function updateUsageUI(count) {
const max = 1000;
const pct = Math.min((count / max) * 100, 100).toFixed(1);
document.getElementById('usage-count').textContent = `${count} / 1,000 requests`;
document.getElementById('usage-fill').style.width = pct + '%';
}
// ─── Load saved settings ────────────────────────────────────────────────────
function loadSettings() {
chrome.storage.sync.get(['apiKey', 'userProfile', 'language'], (data) => {
if (data.apiKey) {
document.getElementById('api-key-input').value = data.apiKey;
setKeyStatus('ok');
}
let profile = data.userProfile || DEFAULT_PROFILE;
if (profile.includes('hamzaayed@intaleqapp.com') || profile.includes('hamzaayedflutter@gmail.com') || profile.includes('hamzaayed@tripz-egypt.com')) {
profile = profile
.replace(/hamzaayed@intaleqapp\.com/g, 'hamzaayed.dev@gmail.com')
.replace(/hamzaayedflutter@gmail\.com/g, 'hamzaayed.dev@gmail.com')
.replace(/hamzaayed@tripz-egypt\.com/g, 'hamzaayed.dev@gmail.com');
chrome.storage.sync.set({ userProfile: profile });
}
document.getElementById('profile-textarea').value = profile;
document.getElementById('lang-select').value =
data.language || 'auto';
});
// Usage counter from local storage
const today = new Date().toDateString();
chrome.storage.local.get(['usageDate', 'usageCount'], (data) => {
if (data.usageDate === today) {
updateUsageUI(data.usageCount || 0);
} else {
updateUsageUI(0);
}
});
}
// ─── Save settings ──────────────────────────────────────────────────────────
document.getElementById('save-btn').addEventListener('click', () => {
const apiKey = document.getElementById('api-key-input').value.trim();
const userProfile = document.getElementById('profile-textarea').value.trim();
const language = document.getElementById('lang-select').value;
if (!apiKey) {
showToast('⚠️ Please enter your API Key', 'error');
return;
}
if (!userProfile) {
showToast('⚠️ Profile cannot be empty', 'error');
return;
}
chrome.storage.sync.set({ apiKey, userProfile, language }, () => {
showToast('✅ Settings saved!', 'success');
});
});
// ─── Toggle API key visibility ───────────────────────────────────────────────
document.getElementById('toggle-key').addEventListener('click', () => {
const input = document.getElementById('api-key-input');
const btn = document.getElementById('toggle-key');
if (input.type === 'password') {
input.type = 'text';
btn.textContent = '🙈';
} else {
input.type = 'password';
btn.textContent = '👁';
}
});
// ─── Test API Key ───────────────────────────────────────────────────────────
document.getElementById('test-key-btn').addEventListener('click', async () => {
const apiKey = document.getElementById('api-key-input').value.trim();
if (!apiKey) {
showToast('⚠️ Enter your API key first', 'error');
return;
}
setKeyStatus('checking');
try {
const response = await fetch(
`https://generativelanguage.googleapis.com/v1beta/models/gemini-flash-lite-latest:generateContent?key=${apiKey}`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
contents: [{ parts: [{ text: 'Say "OK" in one word.' }] }],
generationConfig: { maxOutputTokens: 5 }
})
}
);
if (response.ok) {
setKeyStatus('ok');
showToast('✅ API Key is valid!', 'success');
} else {
const err = await response.json();
setKeyStatus('error');
showToast('❌ Invalid key: ' + (err.error?.message || response.status), 'error');
}
} catch (e) {
setKeyStatus('error');
showToast('❌ Network error: ' + e.message, 'error');
}
});
// ─── Reset profile ──────────────────────────────────────────────────────────
document.getElementById('reset-profile-btn').addEventListener('click', () => {
if (confirm('Reset profile to default?')) {
document.getElementById('profile-textarea').value = DEFAULT_PROFILE;
showToast('🔄 Profile reset to default');
}
});
// ─── Clear cache ─────────────────────────────────────────────────────────────
document.getElementById('clear-cache-btn').addEventListener('click', () => {
chrome.storage.local.remove(['analysisCache'], () => {
showToast('🗑️ Analysis cache cleared');
});
});
// ─── Clear all data ──────────────────────────────────────────────────────────
document.getElementById('clear-all-btn').addEventListener('click', () => {
if (confirm('Clear ALL data including API key and profile?')) {
chrome.storage.sync.clear(() => {
chrome.storage.local.clear(() => {
document.getElementById('api-key-input').value = '';
document.getElementById('profile-textarea').value = DEFAULT_PROFILE;
document.getElementById('lang-select').value = 'auto';
document.getElementById('key-status').style.display = 'none';
updateUsageUI(0);
showToast('🗑️ All data cleared');
});
});
}
});
// ─── Autofill Functionality ──────────────────────────────────────────────────
function parseProfileText(text) {
const emailRegex = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/;
const phoneRegex = /\+?[0-9]{1,4}[ \t.-]?[0-9]{3,4}[ \t.-]?[0-9]{3,4}/;
const linkedinRegex = /(https?:\/\/)?(www\.)?linkedin\.com\/in\/[a-zA-Z0-9_-]+/;
const githubRegex = /(https?:\/\/)?(www\.)?github\.com\/[a-zA-Z0-9_-]+/;
const emailMatch = text.match(emailRegex);
const phoneMatch = text.match(phoneRegex);
const linkedinMatch = text.match(linkedinRegex);
const githubMatch = text.match(githubRegex);
// Extract Name (first line usually)
const lines = text.split('\n').map(l => l.trim()).filter(Boolean);
let fullName = "Hamza Ayed";
if (lines.length > 0) {
const firstLine = lines[0].replace(/—.*/, '').replace(/:.*/, '').trim();
fullName = firstLine;
}
const nameParts = fullName.split(' ');
const firstName = nameParts[0] || "";
const lastName = nameParts.slice(1).join(' ') || "";
// Extract Summary
let summary = "";
const summaryIdx = text.toLowerCase().indexOf("summary:");
if (summaryIdx !== -1) {
const skillsIdx = text.toLowerCase().indexOf("core skills:", summaryIdx);
if (skillsIdx !== -1) {
summary = text.substring(summaryIdx + 8, skillsIdx).trim();
} else {
summary = text.substring(summaryIdx + 8, summaryIdx + 500).trim();
}
}
return {
fullName,
firstName,
lastName,
email: emailMatch ? emailMatch[0] : "",
phone: phoneMatch ? phoneMatch[0] : "",
linkedin: linkedinMatch ? linkedinMatch[0] : "",
github: githubMatch ? githubMatch[0] : "",
summary,
cvText: text
};
}
document.getElementById('autofill-btn').addEventListener('click', async () => {
const userProfile = document.getElementById('profile-textarea').value.trim();
if (!userProfile) {
showToast('⚠️ Please enter profile info first', 'error');
return;
}
const profileData = parseProfileText(userProfile);
try {
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
if (!tab) {
showToast('❌ No active tab found', 'error');
return;
}
// Set the profile data on the tab window context
await chrome.scripting.executeScript({
target: { tabId: tab.id },
func: (data) => {
window.__autofillProfileData = data;
},
args: [profileData]
});
// Execute the filler script
const results = await chrome.scripting.executeScript({
target: { tabId: tab.id },
files: ['filler.js']
});
if (results && results[0]) {
showToast('✨ ' + results[0].result, 'success');
} else {
showToast('✨ Autofill completed!', 'success');
}
} catch (e) {
console.error(e);
showToast('❌ Error: ' + e.message, 'error');
}
});
// ─── Voice Dictation ───────────────────────────────────────────────────────────
function initDictation() {
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
const statusEl = document.getElementById('dictation-status');
const micBtn = document.getElementById('dictation-mic-btn');
const resultArea = document.getElementById('dictation-result');
const copyBtn = document.getElementById('copy-dictation-btn');
if (!SpeechRecognition) {
statusEl.textContent = '❌ المتصفح لا يدعم التعرف على الصوت';
micBtn.disabled = true;
micBtn.style.opacity = '0.5';
return;
}
let recognition = new SpeechRecognition();
recognition.continuous = true;
recognition.interimResults = true;
recognition.lang = 'ar-SA';
let isRecording = false;
let finalTranscript = '';
recognition.onstart = () => {
isRecording = true;
micBtn.style.background = 'linear-gradient(135deg, #ff4d6d, #c9184a)';
micBtn.innerHTML = '🔴';
micBtn.style.animation = 'claude-voice-pulse 1.5s infinite'; // We can add this via inline or just rely on color
statusEl.textContent = 'جارٍ الاستماع... اضغط للإيقاف';
finalTranscript = '';
resultArea.value = '';
copyBtn.style.display = 'none';
};
recognition.onresult = (event) => {
let interimTranscript = '';
let currentFinal = '';
for (let i = event.resultIndex; i < event.results.length; i++) {
const transcript = event.results[i][0].transcript;
if (event.results[i].isFinal) {
currentFinal += transcript + ' ';
} else {
interimTranscript += transcript;
}
}
finalTranscript += currentFinal;
resultArea.value = finalTranscript + interimTranscript;
};
recognition.onerror = (event) => {
console.error('Speech recognition error', event.error);
if (event.error === 'not-allowed') {
statusEl.textContent = '❌ يرجى السماح للميكروفون من إعدادات المتصفح';
} else if (event.error !== 'no-speech') {
statusEl.textContent = '❌ خطأ: ' + event.error;
}
stopRecording(false);
};
recognition.onend = () => {
if (isRecording) {
stopRecording(true);
}
};
function stopRecording(process = true) {
if (!isRecording) return;
isRecording = false;
recognition.stop();
micBtn.style.background = 'linear-gradient(135deg, var(--accent), #9b5de5)';
micBtn.innerHTML = '🎤';
const textToProcess = resultArea.value.trim();
if (textToProcess && process) {
statusEl.textContent = '✨ جارٍ التحسين بواسطة الذكاء الاصطناعي...';
refineWithGemini(textToProcess);
} else {
statusEl.textContent = 'اضغط للتحدث (بالعربية)';
}
}
micBtn.addEventListener('click', async () => {
if (isRecording) {
stopRecording(true);
} else {
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
stream.getTracks().forEach(t => t.stop());
recognition.start();
} catch (err) {
statusEl.textContent = '❌ يجب السماح باستخدام الميكروفون';
chrome.tabs.create({ url: chrome.runtime.getURL('permission.html') });
}
}
});
copyBtn.addEventListener('click', () => {
navigator.clipboard.writeText(resultArea.value);
const originalText = copyBtn.textContent;
copyBtn.textContent = '✅ تم النسخ!';
setTimeout(() => { copyBtn.textContent = originalText; }, 2000);
});
async function refineWithGemini(text) {
const apiKey = document.getElementById('api-key-input').value.trim();
if (!apiKey) {
statusEl.textContent = '⚠️ الرجاء إدخال مفتاح Gemini لتحسين النص';
copyBtn.style.display = 'inline-flex';
return;
}
try {
const prompt = `You are an expert Arabic text refinement assistant. Your task is to:
1. Correct any speech recognition errors in the following Arabic text
2. Fix punctuation, capitalization, and formatting
3. Keep the original meaning and content intact
4. Do NOT add any new information or commentary
5. Return ONLY the corrected text, nothing else
TEXT TO REFINE:
${text}
CORRECTED TEXT:`;
const response = await fetch('https://cv.intaleqapp.com/cv/server/generate_cv.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
action: 'generateText',
apiKey: apiKey,
prompt: prompt
})
});
if (response.ok) {
const rawText = await response.text();
let data;
try { data = JSON.parse(rawText); } catch(e) { data = rawText; }
let processedText = text;
if (data && data.candidates && data.candidates[0]) {
processedText = data.candidates[0].content?.parts?.[0]?.text;
} else if (typeof data === 'string') {
processedText = data;
}
resultArea.value = processedText.trim();
statusEl.textContent = '✅ تم التحسين بنجاح! يمكنك النسخ الآن.';
copyBtn.style.display = 'inline-flex';
} else {
throw new Error('Server error');
}
} catch (e) {
statusEl.textContent = '⚠️ فشل التحسين الذكي، نعرض النص الأصلي.';
copyBtn.style.display = 'inline-flex';
}
}
}
// ─── Init ────────────────────────────────────────────────────────────────────
loadSettings();
initDictation();