620 lines
No EOL
21 KiB
JavaScript
620 lines
No EOL
21 KiB
JavaScript
// 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');
|
||
}
|
||
})();
|