diff --git a/background.js b/background.js index 9c2658d..5c7aa4e 100644 --- a/background.js +++ b/background.js @@ -67,6 +67,15 @@ async function handleGeminiRequest({ apiKey, prompt, tab, action = 'generateText } await incrementUsage(); return { comment, fromCache: false }; + } else if (action === 'repurposePost') { + const result = data.result; + if (!result) { + console.error('[LJA-BG] Empty result. Full response:', JSON.stringify(data).substring(0, 300)); + lastError = 'Empty result from server.'; + continue; + } + await incrementUsage(); + return { result, fromCache: false }; } else { const text = data.candidates?.[0]?.content?.parts?.[0]?.text; if (!text) { diff --git a/post_feed.js b/post_feed.js index df1e600..25dc37b 100644 --- a/post_feed.js +++ b/post_feed.js @@ -218,6 +218,133 @@ } } + // ─── Core: call server and repurpose post ─────────────────────────────── + async function repurposePost(postEl) { + const settings = await getSettings(); + if (!settings || !settings.apiKey) { + alert('Please set your Gemini API key in the extension popup first.\n(Debug: Key not found in storage)'); + return; + } + + const postText = extractPostText(postEl); + if (!postText) { + alert('Could not read post text. The post might be an image or video.'); + return; + } + + const btn = postEl.querySelector('.lja-rewrite-btn'); + if (btn) { + btn.disabled = true; + btn.innerHTML = ' Rewriting...'; + } + + try { + const response = await chrome.runtime.sendMessage({ + type: 'GEMINI_REQUEST', + payload: { + apiKey: settings.apiKey, + action: 'repurposePost', + postText: postText + } + }); + + if (!response.success) { + throw new Error(response.error || 'Unknown error'); + } + + const resultText = response.data.result || response.data; + + // We reuse the comment box UI but style it differently, or just use the same box but change the title and actions. + createRepurposeBox(postEl, resultText); + + } catch (e) { + console.error('[LJA Feed]', e); + alert('Post rewriting failed: ' + e.message); + } finally { + if (btn) { + btn.disabled = false; + btn.innerHTML = '📝 Rewrite'; + } + } + } + + // ─── Create the repurpose result box ───────────────────────────────────── + function createRepurposeBox(postEl, resultText) { + const existing = postEl.querySelector('.' + BOX_CLASS); + if (existing) existing.remove(); + + const isRTL = /[\u0600-\u06FF]/.test(resultText); + + // Split text into Post and Image Prompt if the separator exists + let postContent = resultText; + let imagePrompt = ''; + const parts = resultText.split('--- IMAGE PROMPT ---'); + if (parts.length > 1) { + postContent = parts[0].trim(); + imagePrompt = parts[1].trim(); + } + + const box = document.createElement('div'); + box.className = BOX_CLASS; + box.innerHTML = ` +
+ ✍️ + Rewritten Post + +
+
+ Post Content: +
+ + ${imagePrompt ? ` +
+ Image Generation Prompt (Midjourney / DALL-E): +
+ + ` : ''} +
+ + ${imagePrompt ? '' : ''} +
+ `; + + box.querySelector('.lja-cb-close').addEventListener('click', () => box.remove()); + + box.querySelector('.lja-cb-copy').addEventListener('click', function () { + const text = box.querySelector('.lja-cb-text').value; + navigator.clipboard.writeText(text).then(() => { + this.textContent = '✅ Post Copied!'; + setTimeout(() => { this.textContent = '📋 Copy Post'; }, 1500); + }); + }); + + const copyImgBtn = box.querySelector('.lja-cb-copy-img'); + if (copyImgBtn) { + copyImgBtn.addEventListener('click', function () { + const text = box.querySelector('.lja-cb-image-prompt').value; + navigator.clipboard.writeText(text).then(() => { + this.textContent = '✅ Prompt Copied!'; + setTimeout(() => { this.textContent = '🎨 Copy Image Prompt'; }, 1500); + }); + }); + } + + const actionBar = findActionBar(postEl); + if (actionBar) { + actionBar.parentNode.insertBefore(box, actionBar.nextSibling); + } else { + postEl.appendChild(box); + } + } + // ─── Inject button into a single post ──────────────────────────────────── function injectButton(postEl) { if (postEl.querySelector('.' + BUTTON_CLASS)) return; // Already injected @@ -238,7 +365,33 @@ generateComment(postEl); }); + const rewriteBtn = document.createElement('button'); + rewriteBtn.className = 'lja-rewrite-btn'; + rewriteBtn.innerHTML = '📝 Rewrite'; + rewriteBtn.title = 'Rewrite this post in your own style and generate an image prompt'; + + // Add some inline styles for the rewrite button to differentiate it + rewriteBtn.style.background = 'linear-gradient(135deg, #FF8787 0%, #E03131 100%)'; + rewriteBtn.style.color = 'white'; + rewriteBtn.style.border = 'none'; + rewriteBtn.style.borderRadius = '16px'; + rewriteBtn.style.padding = '4px 12px'; + rewriteBtn.style.fontSize = '12px'; + rewriteBtn.style.fontWeight = '600'; + rewriteBtn.style.cursor = 'pointer'; + rewriteBtn.style.marginLeft = '8px'; + rewriteBtn.style.display = 'flex'; + rewriteBtn.style.alignItems = 'center'; + rewriteBtn.style.gap = '4px'; + + rewriteBtn.addEventListener('click', (e) => { + e.stopPropagation(); + e.preventDefault(); + repurposePost(postEl); + }); + actionBar.appendChild(btn); + actionBar.appendChild(rewriteBtn); } // ─── Process all posts on the page ─────────────────────────────────────── diff --git a/server/generate_cv.php b/server/generate_cv.php index 9a96130..0cfcf46 100644 --- a/server/generate_cv.php +++ b/server/generate_cv.php @@ -215,3 +215,58 @@ if ($action === 'generateComment') { echo json_encode(["success" => true, "comment" => $commentText]); exit; } + +// ========================================== +// ACTION 4: Repurpose Post Generator +// ========================================== +if ($action === 'repurposePost') { + $postText = substr($data['postText'] ?? '', 0, 3000); + + if (empty($postText)) { + http_response_code(400); + echo json_encode(["error" => "postText is required."]); + exit; + } + + $promptFile = __DIR__ . '/prompts/repurpose_prompt.txt'; + if (!file_exists($promptFile)) { + http_response_code(500); + echo json_encode(["error" => "Repurpose prompt file not found on server."]); + exit; + } + + $promptTemplate = file_get_contents($promptFile); + $prompt = str_replace('{{POST_TEXT}}', $postText, $promptTemplate); + + $payload = json_encode([ + "contents" => [["parts" => [["text" => $prompt]]]], + "generationConfig" => ["temperature" => 0.75, "maxOutputTokens" => 800] + ]); + + $ch = curl_init($geminiUrl); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); + $response = curl_exec($ch); + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + if ($httpCode !== 200) { + http_response_code(500); + echo json_encode(["error" => "Gemini API Error", "details" => json_decode($response)]); + exit; + } + + $responseData = json_decode($response, true); + $resultText = trim($responseData['candidates'][0]['content']['parts'][0]['text'] ?? ''); + + if (empty($resultText)) { + http_response_code(500); + echo json_encode(["error" => "Empty result from AI."]); + exit; + } + + echo json_encode(["success" => true, "result" => $resultText]); + exit; +} diff --git a/server/prompts/repurpose_prompt.txt b/server/prompts/repurpose_prompt.txt new file mode 100644 index 0000000..a28481b --- /dev/null +++ b/server/prompts/repurpose_prompt.txt @@ -0,0 +1,21 @@ +You are Hamza Ayed, a Senior Solutions Architect and technical leader. +Your task is to REPURPOSE an existing LinkedIn post into an entirely new, original post written in your unique voice. + +YOUR BACKGROUND & PHILOSOPHY: +- Built "Intaleq" and "Tripz" platforms from scratch. +- Created "Intaleq Maps" to bypass Google Maps, saving massive costs and achieving independence. +- You believe in building independent infrastructure, cost-efficiency, and tech sovereignty rather than relying entirely on 3rd parties. + +STRICT RULES: +1. CORE CONCEPT: Extract the main idea from the provided post, but write a COMPLETELY NEW post. Do not just summarize. Reframe it from your Architect/Leadership perspective. +2. LANGUAGE: Match the language of the original post. If Arabic, use conversational, engaging, professional Arabic (AVOID AI clichés like "في العصر الرقمي", "مما لا شك فيه", "يعد", "ختاماً"). Write naturally like a seasoned expert. +3. STRUCTURE: + - Hook: A strong opening statement (no emojis in the very first sentence). + - Body: Your perspective on the topic. Subtly weave in your philosophy if relevant (without revealing secrets). + - Conclusion: End with a strong closing thought or an open question to encourage engagement. +4. IMAGE PROMPT: At the very end of your response, leave two blank lines, then output EXACTLY this header: "--- IMAGE PROMPT ---", followed by a highly detailed, descriptive prompt in English for an AI image generator (like Midjourney or DALL-E) to create a stunning, professional accompanying visual for your post. +5. NO EMOJIS in the image prompt section. You may use a few appropriate emojis in the main post text. +6. OUTPUT FORMAT: Return the new post text, then the image prompt section. Nothing else. + +POST TO REPURPOSE: +{{POST_TEXT}}