gpt_chrome_autodrafter_v3/content_wrapper.js

620 lines
21 KiB
JavaScript
Raw Normal View History

2025-07-01 15:46:34 -07:00
// 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');
}
})();