gpt_chrome_autodrafter_v3/content_wrapper.js
2025-07-01 15:46:34 -07:00

620 lines
No EOL
21 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Content Script Wrapper
// This wrapper loads platform-specific handlers without modifying existing code
(function() {
'use strict';
console.log('Auto-Draft Extension: Content wrapper initializing...');
// Request draft from background (shared helper)
function requestDraftFromBackground(threadMessages, contextPrompt) {
return new Promise((resolve, reject) => {
chrome.runtime.sendMessage({
action: 'generateDraft',
threadMessages,
contextPrompt
}, (response) => {
if (response && response.draft) {
resolve(response.draft);
} else {
reject(new Error(response?.error || 'No draft generated'));
}
});
});
}
// Helper to extract first name from a full name
function getFirstName(fullName) {
if (!fullName || fullName === 'there') return fullName;
// Remove any email addresses if present
const nameOnly = fullName.split('<')[0].trim();
// Handle common name formats
const parts = nameOnly.split(/\s+/);
// If it's a single word, return it
if (parts.length === 1) return parts[0];
// Check if first part is a title (Mr., Mrs., Dr., etc.)
const titles = ['mr', 'mrs', 'ms', 'miss', 'dr', 'prof', 'professor'];
if (titles.includes(parts[0].toLowerCase().replace('.', ''))) {
// Return the next part as first name
return parts[1] || parts[0];
}
// Otherwise, return the first part as the first name
return parts[0];
}
// Format helpers
function hasGreeting(draft) {
// Check for common greetings
if (/^(hi|hello|hey|dear|good\s*(morning|afternoon|evening))[^\n,]*[,\n]/i.test(draft.trim())) {
return true;
}
// Check if it starts with a name followed by comma or colon
if (/^[A-Z][a-zA-Z\s\-']+[,:]\s*$/m.test(draft.trim().split('\n')[0])) {
return true;
}
// Check if it starts with a title and name
if (/^(Mr\.|Mrs\.|Ms\.|Dr\.|Prof\.)\s+[A-Za-z\s\-']+[,:]/i.test(draft.trim())) {
return true;
}
return false;
}
function hasSignature(draft) {
return /(best|thanks|thank you|sincerely|regards|cheers|cordially|warmly|respectfully|yours)[^\n]*[\n\r]+[\w\s]+$/i.test(draft.trim());
}
// Format the reply as a proper email
function formatEmailReplySmart(draft, recipientName = 'there', senderName = 'Curtis') {
let result = draft.trim();
// Use only first name for greeting to be more natural
const greetingName = getFirstName(recipientName);
// Add greeting if needed
if (!hasGreeting(result)) {
result = `Hi ${greetingName},\n\n${result}`;
}
// Add signature if needed
if (!hasSignature(result)) {
result = `${result}\n\nBest,\n${senderName}`;
}
return result;
}
// Extract recipient name helper
function extractRecipientName(messages) {
// Get the last message that's not from Curtis
for (let i = messages.length - 1; i >= 0; i--) {
const msg = messages[i];
if (msg.sender && !msg.sender.includes('curtis@newfrontierinc.com')) {
return msg.senderName || msg.sender.split('@')[0] || 'there';
}
}
return 'there';
}
// Platform detector and instantly extractor are now loaded directly via manifest
// so we can use them directly
// Detect current platform
const platform = PlatformDetector.getCurrentPlatform();
console.log('Detected platform:', platform);
if (!PlatformDetector.shouldActivate()) {
console.log('Extension not active on this page');
return;
}
// Load platform-specific handlers
if (platform === 'gmail') {
// Gmail is already handled by content.js
console.log('Gmail platform detected - existing handlers will be used');
} else if (platform === 'instantly') {
console.log('Instantly platform detected - initializing Instantly handlers');
// Initialize Instantly-specific UI and handlers
initializeInstantly();
} else if (platform === 'plusvibe') {
console.log('Plusvibe platform detected - initializing Plusvibe handlers');
// Initialize Plusvibe-specific UI and handlers
initializePlusvibe();
}
// Initialize Instantly-specific functionality
function initializeInstantly() {
console.log('Initializing Instantly support...');
// Create a modified version of the UI injection for Instantly
let uiContainer = null;
let isUIVisible = false;
// Function to inject UI for Instantly
function injectInstantlyUI() {
const replyBox = InstantlyExtractor.findReplyBox();
if (!replyBox || document.querySelector('.gpt-autodraft-ui')) {
return;
}
console.log('Injecting Auto-Draft UI for Instantly');
// Create container with same styling as Gmail version
const container = document.createElement('div');
container.className = 'gpt-autodraft-ui';
container.style.cssText = `
background: #ffffff;
border: 2px solid #1a4d2e;
padding: 8px;
margin: 8px 0;
border-radius: 8px;
position: fixed;
bottom: 24px;
right: 24px;
z-index: 99999;
box-shadow: 0 2px 12px rgba(0,0,0,0.15);
min-width: 320px;
max-width: 400px;
`;
// Add the same UI elements
container.innerHTML = `
<button class="toggle-btn" style="
position: absolute;
top: 8px;
right: 8px;
background: #e6b800;
color: #1a4d2e;
border: none;
border-radius: 50%;
width: 28px;
height: 28px;
cursor: pointer;
font-weight: bold;
font-size: 18px;
"></button>
<label style="color: #1a4d2e; font-weight: bold; display: block; margin-bottom: 4px;">
Context-Specific Prompt:
</label>
<textarea class="prompt-input" style="
width: 100%;
min-height: 60px;
margin-bottom: 8px;
border: 1px solid #e6b800;
border-radius: 4px;
padding: 4px;
resize: vertical;
" placeholder="Add any specific context for this reply..."></textarea>
<button class="draft-btn" style="
background: #1a4d2e;
color: #ffffff;
border: none;
padding: 6px 12px;
border-radius: 4px;
cursor: pointer;
width: 100%;
margin-bottom: 8px;
">Auto-Draft with GPT</button>
<div class="error-msg" style="
color: red;
margin-top: 4px;
display: none;
"></div>
`;
document.body.appendChild(container);
uiContainer = container;
isUIVisible = true;
// Set up event handlers
setupInstantlyEventHandlers(container);
}
// Set up event handlers for Instantly UI
function setupInstantlyEventHandlers(container) {
const toggleBtn = container.querySelector('.toggle-btn');
const promptInput = container.querySelector('.prompt-input');
const draftBtn = container.querySelector('.draft-btn');
const errorMsg = container.querySelector('.error-msg');
const promptLabel = container.querySelector('label');
let minimized = false;
// Toggle button handler
toggleBtn.onclick = () => {
minimized = !minimized;
if (minimized) {
promptLabel.style.display = 'none';
promptInput.style.display = 'none';
errorMsg.style.display = 'none';
container.style.width = '340px';
container.style.minWidth = '340px';
container.style.padding = '0 12px 0 16px';
container.style.display = 'flex';
container.style.alignItems = 'center';
container.style.justifyContent = 'flex-end';
toggleBtn.textContent = '+';
} else {
promptLabel.style.display = 'block';
promptInput.style.display = 'block';
container.style.width = '';
container.style.minWidth = '320px';
container.style.padding = '8px';
container.style.display = 'block';
container.style.alignItems = '';
container.style.justifyContent = '';
toggleBtn.textContent = '';
}
};
// Draft button handler
draftBtn.onclick = async () => {
console.log('Draft button clicked (Instantly)');
draftBtn.disabled = true;
errorMsg.style.display = 'none';
errorMsg.textContent = '';
try {
// First, let's diagnose the DOM structure
console.log('=== INSTANTLY DOM DIAGNOSTIC ===');
// Check what's in the modal/dialog
const modal = document.querySelector('[role="dialog"], .modal-content, .reply-modal');
if (modal) {
console.log('Modal found, inspecting structure...');
console.log('Modal classes:', modal.className);
console.log('Modal ID:', modal.id);
// Log all text content that looks like email headers
const allText = modal.innerText;
const lines = allText.split('\n').filter(line => line.trim());
console.log('Modal text lines:');
lines.forEach((line, i) => {
if (line.includes('@') || line.includes('wrote:') || line.includes('On ') || line.includes('From:')) {
console.log(`Line ${i}: ${line}`);
}
});
// Check for specific elements
const possibleMessageContainers = modal.querySelectorAll('div, p, section, article');
console.log(`Found ${possibleMessageContainers.length} possible containers`);
// Look for containers with email content
possibleMessageContainers.forEach((container, i) => {
const text = container.textContent;
if (text && text.includes('@') && text.length > 50) {
console.log(`Container ${i}:`, {
tagName: container.tagName,
className: container.className,
textLength: text.length,
hasEmail: text.includes('@'),
hasWrote: text.includes('wrote:'),
preview: text.substring(0, 100) + '...'
});
}
});
} else {
console.log('No modal found, checking entire page...');
// Log the general page structure
const bodyText = document.body.innerText;
console.log('Page contains curtis@newfrontierinc.com:', bodyText.includes('curtis@newfrontierinc.com'));
console.log('Page contains drferrara@atlantaurgentcare.com:', bodyText.includes('drferrara@atlantaurgentcare.com'));
}
console.log('=== END DOM DIAGNOSTIC ===');
// Extract thread messages from Instantly
const threadMessages = InstantlyExtractor.extractThreadMessages();
if (!threadMessages || threadMessages.length === 0) {
throw new Error('No messages found in conversation');
}
console.log('Extracted thread messages:', threadMessages);
// Get context prompt
const contextPrompt = promptInput.value.trim();
// Request draft from background script (reuse existing logic)
const draft = await requestDraftFromBackground(threadMessages, contextPrompt);
if (draft) {
// Format the draft properly
const recipientName = extractRecipientName(threadMessages);
const formattedDraft = formatEmailReplySmart(draft, recipientName);
// Insert draft into Instantly's reply box
const inserted = InstantlyExtractor.insertDraft(formattedDraft);
if (!inserted) {
throw new Error('Failed to insert draft into reply box');
}
}
} catch (error) {
console.error('Draft generation failed:', error);
errorMsg.textContent = error.message || 'Failed to generate draft';
errorMsg.style.display = 'block';
} finally {
draftBtn.disabled = false;
}
};
// Auto-resize textarea
promptInput.addEventListener('input', function() {
this.style.height = 'auto';
this.style.height = (this.scrollHeight) + 'px';
});
}
// Monitor for reply box appearance
function monitorForReplyBox() {
const observer = new MutationObserver(() => {
if (!uiContainer && InstantlyExtractor.findReplyBox()) {
injectInstantlyUI();
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
// Initial check
if (InstantlyExtractor.findReplyBox()) {
injectInstantlyUI();
}
}
// Handle extension icon clicks for Instantly
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.action === 'toggleUI') {
if (isUIVisible && uiContainer) {
uiContainer.remove();
uiContainer = null;
isUIVisible = false;
} else {
injectInstantlyUI();
}
}
});
// Start monitoring
monitorForReplyBox();
console.log('Instantly support initialized successfully');
}
// Initialize Plusvibe-specific functionality
function initializePlusvibe() {
console.log('Initializing Plusvibe support...');
let uiContainer = null;
let isUIVisible = false;
// Function to inject UI for Plusvibe
function injectPlusvibeUI() {
const replyBox = PlusvibeExtractor.findReplyBox();
if (!replyBox || document.querySelector('.gpt-autodraft-ui')) {
return;
}
console.log('Injecting Auto-Draft UI for Plusvibe');
// Create container with same styling
const container = document.createElement('div');
container.className = 'gpt-autodraft-ui';
container.style.cssText = `
background: #ffffff;
border: 2px solid #1a4d2e;
padding: 8px;
margin: 8px 0;
border-radius: 8px;
position: fixed;
bottom: 24px;
right: 24px;
z-index: 99999;
box-shadow: 0 2px 12px rgba(0,0,0,0.15);
min-width: 320px;
max-width: 400px;
`;
// Add the same UI elements
container.innerHTML = `
<button class="toggle-btn" style="
position: absolute;
top: 8px;
right: 8px;
background: #e6b800;
color: #1a4d2e;
border: none;
border-radius: 50%;
width: 28px;
height: 28px;
cursor: pointer;
font-weight: bold;
font-size: 18px;
"></button>
<label style="color: #1a4d2e; font-weight: bold; display: block; margin-bottom: 4px;">
Context-Specific Prompt:
</label>
<textarea class="prompt-input" style="
width: 100%;
min-height: 60px;
margin-bottom: 8px;
border: 1px solid #e6b800;
border-radius: 4px;
padding: 4px;
resize: vertical;
" placeholder="Add any specific context for this reply..."></textarea>
<button class="draft-btn" style="
background: #1a4d2e;
color: #ffffff;
border: none;
padding: 6px 12px;
border-radius: 4px;
cursor: pointer;
width: 100%;
margin-bottom: 8px;
">Auto-Draft with GPT</button>
<div class="error-msg" style="
color: red;
margin-top: 4px;
display: none;
"></div>
`;
document.body.appendChild(container);
uiContainer = container;
isUIVisible = true;
// Set up event handlers
setupPlusvibeEventHandlers(container);
}
// Set up event handlers for Plusvibe UI
function setupPlusvibeEventHandlers(container) {
const toggleBtn = container.querySelector('.toggle-btn');
const promptInput = container.querySelector('.prompt-input');
const draftBtn = container.querySelector('.draft-btn');
const errorMsg = container.querySelector('.error-msg');
const promptLabel = container.querySelector('label');
let minimized = false;
// Toggle button handler
toggleBtn.onclick = () => {
minimized = !minimized;
if (minimized) {
promptLabel.style.display = 'none';
promptInput.style.display = 'none';
errorMsg.style.display = 'none';
container.style.width = '340px';
container.style.minWidth = '340px';
container.style.padding = '0 12px 0 16px';
container.style.display = 'flex';
container.style.alignItems = 'center';
container.style.justifyContent = 'flex-end';
toggleBtn.textContent = '+';
} else {
promptLabel.style.display = 'block';
promptInput.style.display = 'block';
container.style.width = '';
container.style.minWidth = '320px';
container.style.padding = '8px';
container.style.display = 'block';
container.style.alignItems = '';
container.style.justifyContent = '';
toggleBtn.textContent = '';
}
};
// Draft button handler
draftBtn.onclick = async () => {
console.log('Draft button clicked (Plusvibe)');
draftBtn.disabled = true;
errorMsg.style.display = 'none';
errorMsg.textContent = '';
try {
// Extract thread messages from Plusvibe
console.log('Extracting thread messages...');
const threadMessages = await PlusvibeExtractor.extractThreadMessages();
console.log('Thread messages extracted:', threadMessages);
if (!threadMessages || threadMessages.length === 0) {
throw new Error('No messages found in conversation');
}
console.log('Extracted thread messages:', threadMessages);
// Get context prompt
const contextPrompt = promptInput.value.trim();
console.log('Context prompt:', contextPrompt);
// Request draft from background script
console.log('Requesting draft from background...');
const draft = await requestDraftFromBackground(threadMessages, contextPrompt);
console.log('Draft received:', draft);
if (draft) {
// Format the draft properly
console.log('Formatting draft...');
const recipientName = extractRecipientName(threadMessages);
const formattedDraft = formatEmailReplySmart(draft, recipientName);
console.log('Formatted draft:', formattedDraft);
// Insert draft into Plusvibe's reply box
console.log('Inserting draft into reply box...');
const inserted = PlusvibeExtractor.insertDraft(formattedDraft);
if (!inserted) {
throw new Error('Failed to insert draft into reply box');
}
console.log('Draft inserted successfully');
}
} catch (error) {
console.error('Draft generation failed:', error);
console.error('Error stack:', error.stack);
errorMsg.textContent = error.message || 'Failed to generate draft';
errorMsg.style.display = 'block';
} finally {
draftBtn.disabled = false;
}
};
// Auto-resize textarea
promptInput.addEventListener('input', function() {
this.style.height = 'auto';
this.style.height = (this.scrollHeight) + 'px';
});
}
// Monitor for reply box appearance
function monitorForReplyBox() {
const observer = new MutationObserver(() => {
if (!uiContainer && PlusvibeExtractor.findReplyBox()) {
injectPlusvibeUI();
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
// Initial check
if (PlusvibeExtractor.findReplyBox()) {
injectPlusvibeUI();
}
}
// Handle extension icon clicks for Plusvibe
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.action === 'toggleUI') {
if (isUIVisible && uiContainer) {
uiContainer.remove();
uiContainer = null;
isUIVisible = false;
} else {
injectPlusvibeUI();
}
}
});
// Start monitoring
monitorForReplyBox();
console.log('Plusvibe support initialized successfully');
}
})();