// 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 = ' Valid'; } else if (state === 'error') { badge.className = 'status-badge error'; badge.innerHTML = ' Invalid'; } else if (state === 'checking') { badge.className = 'status-badge checking'; badge.innerHTML = ' 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 ─────────────────────────────────────────────────────────── // ─── Voice Dictation ─────────────────────────────────────────────────────────── function initDictation() { 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'); let isRecording = false; let finalTranscript = ''; let interimTranscript = ''; function stopRecording(process = true) { if (!isRecording) return; isRecording = false; chrome.runtime.sendMessage({ type: 'STOP_RECORDING_FROM_POPUP' }); 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 = 'اضغط للتحدث (بالعربية)'; } } // Listen to messages from the offscreen document via the background chrome.runtime.onMessage.addListener((message) => { if (message.type === 'OFFSCREEN_RECORDING_RESULT') { interimTranscript = message.payload.interimText || ''; finalTranscript = message.payload.finalText || ''; resultArea.value = (finalTranscript + interimTranscript).trim(); } else if (message.type === 'OFFSCREEN_RECORDING_ERROR') { console.error('Speech recognition error', message.payload.error); if (message.payload.error === 'not-allowed') { statusEl.textContent = '❌ يرجى السماح للميكروفون من إعدادات المتصفح'; chrome.tabs.create({ url: chrome.runtime.getURL('permission.html') }); } else if (message.payload.error !== 'no-speech') { statusEl.textContent = '❌ خطأ: ' + message.payload.error; } stopRecording(false); } else if (message.type === 'OFFSCREEN_RECORDING_END') { if (isRecording) { stopRecording(true); } } }); micBtn.addEventListener('click', async () => { if (isRecording) { stopRecording(true); } else { isRecording = true; micBtn.style.background = 'linear-gradient(135deg, #ff4d6d, #c9184a)'; micBtn.innerHTML = '🔴'; statusEl.textContent = 'جارٍ الاستماع... اضغط للإيقاف'; finalTranscript = ''; interimTranscript = ''; resultArea.value = ''; copyBtn.style.display = 'none'; chrome.runtime.sendMessage({ type: 'START_RECORDING_FROM_POPUP', payload: { language: 'ar-SA' } }, (response) => { if (!response || !response.success) { console.error('Failed to start recording', response); statusEl.textContent = '❌ فشل بدء التسجيل'; stopRecording(false); } }); } }); 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();