652 lines
23 KiB
JavaScript
652 lines
23 KiB
JavaScript
// background.js
|
|
// Import email parser functions
|
|
importScripts('email_parser.js');
|
|
|
|
// Enhanced logging function
|
|
function logWithStyle(message, type = 'info') {
|
|
const styles = {
|
|
info: 'color: #1a4d2e; font-weight: bold;',
|
|
success: 'color: #1a4d2e; font-weight: bold; background: #e6ffe6; padding: 2px 5px; border-radius: 3px;',
|
|
error: 'color: #cc0000; font-weight: bold; background: #ffe6e6; padding: 2px 5px; border-radius: 3px;',
|
|
warning: 'color: #e6b800; font-weight: bold; background: #fff9e6; padding: 2px 5px; border-radius: 3px;'
|
|
};
|
|
console.log(`%c${message}`, styles[type]);
|
|
}
|
|
|
|
// Initialize with enhanced logging
|
|
logWithStyle('=== GPT Auto-Draft Background Script Initializing ===', 'info');
|
|
logWithStyle('Background script loaded successfully', 'success');
|
|
logWithStyle('Email parser imported successfully', 'success');
|
|
|
|
console.log('Background script loaded, email_parser.js imported');
|
|
|
|
// Add a cached token so we don't hit the OAuth flow on every request
|
|
let cachedOAuthToken = null;
|
|
let tokenExpiryTime = null;
|
|
|
|
// Enhanced token handling
|
|
let tokenRefreshInProgress = false;
|
|
let tokenRefreshPromise = null;
|
|
|
|
// Rate limiting and API usage tracking
|
|
const API_RATE_LIMITS = {
|
|
gmail: {
|
|
maxRequestsPerMinute: 60,
|
|
maxRequestsPerHour: 1000
|
|
},
|
|
openai: {
|
|
maxRequestsPerMinute: 20,
|
|
maxRequestsPerHour: 200
|
|
}
|
|
};
|
|
|
|
const apiUsage = {
|
|
gmail: {
|
|
requests: [],
|
|
lastReset: Date.now()
|
|
},
|
|
openai: {
|
|
requests: [],
|
|
lastReset: Date.now()
|
|
}
|
|
};
|
|
|
|
// Rate limiting helper
|
|
function checkRateLimit(api) {
|
|
const now = Date.now();
|
|
const limits = API_RATE_LIMITS[api];
|
|
const usage = apiUsage[api];
|
|
|
|
// Clean old requests
|
|
usage.requests = usage.requests.filter(time => now - time < 3600000); // Keep last hour
|
|
|
|
// Check minute limit
|
|
const minuteRequests = usage.requests.filter(time => now - time < 60000);
|
|
if (minuteRequests.length >= limits.maxRequestsPerMinute) {
|
|
throw new Error(`Rate limit exceeded: ${limits.maxRequestsPerMinute} requests per minute for ${api}`);
|
|
}
|
|
|
|
// Check hour limit
|
|
if (usage.requests.length >= limits.maxRequestsPerHour) {
|
|
throw new Error(`Rate limit exceeded: ${limits.maxRequestsPerHour} requests per hour for ${api}`);
|
|
}
|
|
|
|
// Add new request
|
|
usage.requests.push(now);
|
|
}
|
|
|
|
async function getValidToken(forceRefresh = false) {
|
|
// Check if we need to refresh based on expiry time
|
|
const now = Date.now();
|
|
const shouldRefresh = forceRefresh ||
|
|
!cachedOAuthToken ||
|
|
(tokenExpiryTime && now >= tokenExpiryTime - 60000); // Refresh 1 minute before expiry
|
|
|
|
if (!shouldRefresh && cachedOAuthToken) {
|
|
return cachedOAuthToken;
|
|
}
|
|
|
|
// If a refresh is already in progress, wait for it
|
|
if (tokenRefreshInProgress && tokenRefreshPromise) {
|
|
return tokenRefreshPromise;
|
|
}
|
|
|
|
// Start a new refresh
|
|
tokenRefreshInProgress = true;
|
|
tokenRefreshPromise = new Promise((resolve, reject) => {
|
|
chrome.identity.getAuthToken({ interactive: false }, async (token) => {
|
|
if (chrome.runtime.lastError || !token) {
|
|
console.warn('Silent token refresh failed, trying interactive...');
|
|
|
|
// Clear cached token on failure - FIX: Check if token exists before trying to remove
|
|
if (cachedOAuthToken) {
|
|
chrome.identity.removeCachedAuthToken({ token: cachedOAuthToken }, () => {
|
|
chrome.identity.getAuthToken({ interactive: true }, (interactiveToken) => {
|
|
if (chrome.runtime.lastError || !interactiveToken) {
|
|
console.error('Token refresh failed:', chrome.runtime.lastError);
|
|
cachedOAuthToken = null;
|
|
tokenExpiryTime = null;
|
|
tokenRefreshInProgress = false;
|
|
tokenRefreshPromise = null;
|
|
reject(chrome.runtime.lastError || new Error('Failed to get token'));
|
|
} else {
|
|
cachedOAuthToken = interactiveToken;
|
|
// Set expiry to 50 minutes from now (tokens typically last 1 hour)
|
|
tokenExpiryTime = Date.now() + (50 * 60 * 1000);
|
|
tokenRefreshInProgress = false;
|
|
tokenRefreshPromise = null;
|
|
logWithStyle('Token refreshed successfully (interactive)', 'success');
|
|
resolve(interactiveToken);
|
|
}
|
|
});
|
|
});
|
|
} else {
|
|
// No cached token to remove, go straight to interactive auth
|
|
chrome.identity.getAuthToken({ interactive: true }, (interactiveToken) => {
|
|
if (chrome.runtime.lastError || !interactiveToken) {
|
|
console.error('Token refresh failed:', chrome.runtime.lastError);
|
|
cachedOAuthToken = null;
|
|
tokenExpiryTime = null;
|
|
tokenRefreshInProgress = false;
|
|
tokenRefreshPromise = null;
|
|
reject(chrome.runtime.lastError || new Error('Failed to get token'));
|
|
} else {
|
|
cachedOAuthToken = interactiveToken;
|
|
// Set expiry to 50 minutes from now
|
|
tokenExpiryTime = Date.now() + (50 * 60 * 1000);
|
|
tokenRefreshInProgress = false;
|
|
tokenRefreshPromise = null;
|
|
logWithStyle('Token refreshed successfully (interactive)', 'success');
|
|
resolve(interactiveToken);
|
|
}
|
|
});
|
|
}
|
|
} else {
|
|
cachedOAuthToken = token;
|
|
// Set expiry to 50 minutes from now
|
|
tokenExpiryTime = Date.now() + (50 * 60 * 1000);
|
|
tokenRefreshInProgress = false;
|
|
tokenRefreshPromise = null;
|
|
logWithStyle('Token refreshed successfully (silent)', 'success');
|
|
resolve(token);
|
|
}
|
|
});
|
|
});
|
|
|
|
return tokenRefreshPromise;
|
|
}
|
|
|
|
// Enhanced API request wrapper with rate limiting
|
|
async function makeGmailApiRequest(url, options = {}) {
|
|
try {
|
|
checkRateLimit('gmail');
|
|
let token = await getValidToken();
|
|
let response = await fetch(url, {
|
|
...options,
|
|
headers: {
|
|
...options.headers,
|
|
'Authorization': `Bearer ${token}`
|
|
}
|
|
});
|
|
|
|
// If unauthorized, try refreshing token once
|
|
if (response.status === 401) {
|
|
console.log('Token expired (401), refreshing...');
|
|
|
|
// Remove the expired token from cache - FIX: Check if token exists first
|
|
if (token) {
|
|
chrome.identity.removeCachedAuthToken({ token }, async () => {
|
|
cachedOAuthToken = null;
|
|
tokenExpiryTime = null;
|
|
|
|
// Get a fresh token
|
|
token = await getValidToken(true);
|
|
|
|
// Retry the request
|
|
response = await fetch(url, {
|
|
...options,
|
|
headers: {
|
|
...options.headers,
|
|
'Authorization': `Bearer ${token}`
|
|
}
|
|
});
|
|
});
|
|
} else {
|
|
// No token to remove, just get a fresh one
|
|
cachedOAuthToken = null;
|
|
tokenExpiryTime = null;
|
|
token = await getValidToken(true);
|
|
|
|
// Retry the request
|
|
response = await fetch(url, {
|
|
...options,
|
|
headers: {
|
|
...options.headers,
|
|
'Authorization': `Bearer ${token}`
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
if (!response.ok) {
|
|
const errorText = await response.text();
|
|
console.error(`Gmail API request failed: ${response.status} ${response.statusText}`, errorText);
|
|
throw new Error(`Gmail API request failed: ${response.status} ${response.statusText}`);
|
|
}
|
|
|
|
return response;
|
|
} catch (error) {
|
|
console.error('Gmail API request failed:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// Data validation helpers
|
|
function validateThreadData(threadData) {
|
|
if (!threadData) throw new Error('Thread data is required');
|
|
if (!Array.isArray(threadData.messages)) throw new Error('Thread messages must be an array');
|
|
if (threadData.messages.length === 0) throw new Error('Thread must contain at least one message');
|
|
// No sender validation at all
|
|
}
|
|
|
|
function validateDraftData(draftData) {
|
|
if (!draftData) throw new Error('Draft data is required');
|
|
if (!draftData.recipient) throw new Error('Draft missing recipient');
|
|
if (!draftData.subject) throw new Error('Draft missing subject');
|
|
if (!draftData.content) throw new Error('Draft missing content');
|
|
}
|
|
|
|
// Enhanced message listener with validation and feedback
|
|
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|
console.log('Received message:', message);
|
|
|
|
if (message.action === 'generateDraft') {
|
|
try {
|
|
// Validate input
|
|
if (!message.threadMessages) {
|
|
throw new Error('Thread messages are required');
|
|
}
|
|
|
|
// Send progress update
|
|
chrome.tabs.sendMessage(sender.tab.id, {
|
|
action: 'updateProgress',
|
|
status: 'Generating draft...'
|
|
});
|
|
|
|
handleGenerateDraft(message.threadMessages, message.contextPrompt)
|
|
.then(draft => {
|
|
console.log('Draft generated successfully:', draft);
|
|
chrome.tabs.sendMessage(sender.tab.id, {
|
|
action: 'updateProgress',
|
|
status: 'Draft generated successfully'
|
|
});
|
|
sendResponse({ draft });
|
|
})
|
|
.catch(error => {
|
|
console.error('Drafting failed:', error);
|
|
chrome.tabs.sendMessage(sender.tab.id, {
|
|
action: 'updateProgress',
|
|
status: 'Error: ' + error.message,
|
|
error: true
|
|
});
|
|
sendResponse({ error: error.message || 'Drafting failed' });
|
|
});
|
|
return true;
|
|
} catch (error) {
|
|
console.error('Validation failed:', error);
|
|
sendResponse({ error: error.message });
|
|
return false;
|
|
}
|
|
} else if (message.action === 'parseMimeEmail') {
|
|
try {
|
|
console.log('Parsing MIME email from content script request');
|
|
console.log('MIME content length:', message.mimeContent.length);
|
|
console.log('MIME content preview:', message.mimeContent.substring(0, 100) + '...');
|
|
|
|
const emailData = extractEmailThread(message.mimeContent);
|
|
console.log('Email parsed successfully:', emailData);
|
|
console.log('Subject:', emailData.subject);
|
|
console.log('From:', emailData.sender.name, '<' + emailData.sender.email + '>');
|
|
console.log('To:', emailData.recipient.name, '<' + emailData.recipient.email + '>');
|
|
console.log('Content length:', emailData.content.length);
|
|
|
|
sendResponse({ success: true, emailData });
|
|
} catch (error) {
|
|
console.error('Email parsing failed:', error);
|
|
sendResponse({ success: false, error: error.message || 'Email parsing failed' });
|
|
}
|
|
return true; // async
|
|
} else if (message.action === 'getOAuthToken') {
|
|
getValidToken()
|
|
.then(token => sendResponse({ token }))
|
|
.catch(error => sendResponse({ error: error.message }));
|
|
return true;
|
|
}
|
|
});
|
|
|
|
// Utility: Format the entire thread as a single string
|
|
function formatThreadAsString(messages) {
|
|
console.log('Formatting thread as string');
|
|
console.log('Thread messages type:', typeof messages);
|
|
|
|
if (!Array.isArray(messages)) {
|
|
console.log('Thread messages is not an array, converting to string');
|
|
return messages ? String(messages).trim() : '';
|
|
}
|
|
|
|
console.log('Thread contains', messages.length, 'messages');
|
|
// Format each message with sender, timestamp, and content
|
|
return messages.map(msg =>
|
|
`${msg.senderName} <${msg.sender}>\n${msg.timestamp}\n\n${msg.content}`
|
|
).join('\n---\n');
|
|
}
|
|
|
|
// Helper: Summarize and truncate thread for OpenAI API
|
|
function prepareThreadForOpenAI(messages, maxChars = 250000) {
|
|
console.log('\n=== PREPARE THREAD FOR OPENAI ===');
|
|
console.log('Input messages:', Array.isArray(messages) ? messages.length : 'Not an array');
|
|
console.log('Max characters allowed:', maxChars);
|
|
|
|
if (!Array.isArray(messages)) {
|
|
console.log('Messages is not an array, returning as string');
|
|
return String(messages);
|
|
}
|
|
|
|
// Log structure of first message for debugging
|
|
if (messages.length > 0) {
|
|
console.log('First message structure:');
|
|
console.log(JSON.stringify(messages[0], null, 2));
|
|
}
|
|
|
|
// Format each message with clear structure
|
|
const formatMessage = (msg, index) => {
|
|
const sender = msg.senderName || msg.from || 'Unknown';
|
|
const email = msg.sender || msg.from || 'unknown@email.com';
|
|
const timestamp = msg.timestamp || msg.date || 'No timestamp';
|
|
let content = msg.content || '';
|
|
|
|
// Clean up content - remove excessive whitespace but preserve code blocks
|
|
content = content
|
|
.replace(/\[CODE BLOCK\]/g, '\n```')
|
|
.replace(/\[\/CODE BLOCK\]/g, '```\n')
|
|
.trim();
|
|
|
|
// Format the message with clear boundaries
|
|
const formatted = [
|
|
`=== Message ${index + 1} ===`,
|
|
`From: ${sender} <${email}>`,
|
|
`Date: ${timestamp}`,
|
|
'',
|
|
content,
|
|
''
|
|
].join('\n');
|
|
|
|
console.log(`Message ${index + 1} formatted length:`, formatted.length);
|
|
return formatted;
|
|
};
|
|
|
|
// If the thread is short enough, return as is with nice formatting
|
|
let fullText = messages.map(formatMessage).join('\n---\n');
|
|
|
|
console.log('Full formatted text length:', fullText.length);
|
|
|
|
if (fullText.length <= maxChars) {
|
|
console.log('Thread is within max chars, returning full text');
|
|
return fullText;
|
|
}
|
|
|
|
console.log('Thread exceeds max chars, summarizing...');
|
|
// Otherwise, include recent messages in full and summarize older ones
|
|
let result = '';
|
|
let recentMessages = [];
|
|
let totalLength = 0;
|
|
|
|
// Start from the most recent message and work backwards
|
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
const formatted = formatMessage(messages[i], i);
|
|
if (totalLength + formatted.length + 100 > maxChars * 0.8) {
|
|
// We've hit our limit
|
|
break;
|
|
}
|
|
recentMessages.unshift(formatted);
|
|
totalLength += formatted.length + 10; // Account for separator
|
|
}
|
|
|
|
// Add summary header if we didn't include all messages
|
|
if (recentMessages.length < messages.length) {
|
|
const omittedCount = messages.length - recentMessages.length;
|
|
result = `[Note: ${omittedCount} earlier messages omitted for length. Showing ${recentMessages.length} most recent messages.]\n\n`;
|
|
console.log(`Summarized ${omittedCount} earlier messages`);
|
|
}
|
|
|
|
result += recentMessages.join('\n---\n');
|
|
|
|
console.log('Final thread length:', result.length);
|
|
console.log('=== END PREPARE THREAD FOR OPENAI ===\n');
|
|
|
|
return result;
|
|
}
|
|
|
|
async function handleGenerateDraft(threadMessages, contextPrompt = null) {
|
|
console.log('=== HANDLE GENERATE DRAFT ===');
|
|
console.log('handleGenerateDraft called with:', { threadMessages, contextPrompt });
|
|
|
|
try {
|
|
checkRateLimit('openai');
|
|
|
|
const settings = await getFromStorage(['openAIApiKey', 'customPrompt', 'customGptEndpoint']);
|
|
if (!settings.openAIApiKey) throw new Error('OpenAI API key not set');
|
|
if (!settings.customGptEndpoint) throw new Error('Assistant ID not set');
|
|
|
|
// Validate thread data
|
|
if (Array.isArray(threadMessages)) {
|
|
validateThreadData({ messages: threadMessages });
|
|
}
|
|
|
|
const apiKey = settings.openAIApiKey;
|
|
const assistantId = settings.customGptEndpoint || '';
|
|
const basePrompt = settings.customPrompt || '';
|
|
|
|
console.log('\n=== SETTINGS ===');
|
|
console.log('Assistant ID:', assistantId);
|
|
console.log('Base Prompt:', basePrompt);
|
|
console.log('Context Prompt:', contextPrompt);
|
|
|
|
// Prepare thread for OpenAI API (truncate/summarize if needed)
|
|
let formattedThread = '';
|
|
if (typeof threadMessages === 'string' && threadMessages.startsWith('MIME-Version:')) {
|
|
console.log('Detected MIME email format, parsing...');
|
|
try {
|
|
const emailData = extractEmailThread(threadMessages);
|
|
console.log('MIME email parsed successfully');
|
|
console.log('Email data:', {
|
|
subject: emailData.subject,
|
|
sender: emailData.sender,
|
|
recipient: emailData.recipient,
|
|
contentLength: emailData.content.length
|
|
});
|
|
|
|
formattedThread = `From: ${emailData.sender.name} <${emailData.sender.email}>\n` +
|
|
`To: ${emailData.recipient.name} <${emailData.recipient.email}>\n` +
|
|
`Subject: ${emailData.subject}\n` +
|
|
`Date: ${emailData.date}\n\n` +
|
|
`${emailData.content}`;
|
|
console.log('Formatted thread length:', formattedThread.length);
|
|
} catch (error) {
|
|
console.error('Failed to parse MIME email:', error);
|
|
console.log('Falling back to raw content');
|
|
formattedThread = threadMessages; // Fallback to raw content
|
|
}
|
|
} else {
|
|
// Use the new truncation/summarization helper
|
|
formattedThread = prepareThreadForOpenAI(threadMessages);
|
|
console.log('\n=== FORMATTED THREAD ===');
|
|
console.log('Formatted thread length:', formattedThread.length);
|
|
console.log('Formatted thread preview:');
|
|
console.log(formattedThread.substring(0, 1000));
|
|
if (formattedThread.length > 1000) {
|
|
console.log('... [truncated for console] ...');
|
|
}
|
|
}
|
|
|
|
if (!apiKey) {
|
|
console.error('API key not set.');
|
|
throw new Error('API key not set.');
|
|
}
|
|
|
|
if (!assistantId) {
|
|
console.error('Assistant ID not set.');
|
|
throw new Error('Assistant ID not set.');
|
|
}
|
|
|
|
// Always include the base prompt, and append the context prompt if present
|
|
let promptToUse = basePrompt;
|
|
if (c
|
|
30 June 2025 at 05:00 ontextPrompt && contextPrompt.trim()) {
|
|
console.log('Adding context-specific prompt:', contextPrompt.trim());
|
|
promptToUse += '\n\nAdditional instructions: ' + contextPrompt.trim();
|
|
}
|
|
const messageContent = `Instructions: ${promptToUse}\n\nEmail Thread:\n${formattedThread}`;
|
|
|
|
console.log('\n=== FINAL PAYLOAD TO OPENAI ===');
|
|
console.log('Using combined prompt:', promptToUse);
|
|
console.log('Total message content length:', messageContent.length);
|
|
console.log('\nFull message content being sent to OpenAI:');
|
|
console.log('------------------------');
|
|
console.log(messageContent);
|
|
console.log('------------------------\n');
|
|
|
|
// 1. Create a thread
|
|
console.log('Creating OpenAI thread...');
|
|
const threadResponse = await fetch('https://api.openai.com/v1/threads', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${apiKey}`,
|
|
'OpenAI-Beta': 'assistants=v2'
|
|
}
|
|
});
|
|
if (!threadResponse.ok) {
|
|
const errorText = await threadResponse.text();
|
|
console.error('Failed to create thread:', threadResponse.status, errorText);
|
|
throw new Error('Failed to create thread');
|
|
}
|
|
const thread = await threadResponse.json();
|
|
console.log('Thread created with ID:', thread.id);
|
|
|
|
// 2. Add a message to the thread with the prompt and thread content
|
|
console.log('\nAdding message to thread...');
|
|
console.log('Message payload:');
|
|
console.log(JSON.stringify({
|
|
role: 'user',
|
|
content: messageContent
|
|
}, null, 2));
|
|
|
|
const messageResponse = await fetch(`https://api.openai.com/v1/threads/${thread.id}/messages`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${apiKey}`,
|
|
'OpenAI-Beta': 'assistants=v2'
|
|
},
|
|
body: JSON.stringify({
|
|
role: 'user',
|
|
content: messageContent
|
|
})
|
|
});
|
|
if (!messageResponse.ok) {
|
|
const errorText = await messageResponse.text();
|
|
console.error('Failed to add message to thread:', messageResponse.status, errorText);
|
|
throw new Error('Failed to add message to thread');
|
|
}
|
|
const message = await messageResponse.json();
|
|
console.log('Message added to thread with ID:', message.id);
|
|
|
|
// 3. Run the assistant
|
|
console.log('\nRunning assistant with ID:', assistantId);
|
|
const runResponse = await fetch(`https://api.openai.com/v1/threads/${thread.id}/runs`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${apiKey}`,
|
|
'OpenAI-Beta': 'assistants=v2'
|
|
},
|
|
body: JSON.stringify({
|
|
assistant_id: assistantId
|
|
})
|
|
});
|
|
if (!runResponse.ok) {
|
|
const errorText = await runResponse.text();
|
|
console.error('Failed to start run:', runResponse.status, errorText);
|
|
throw new Error('Failed to start run');
|
|
}
|
|
const run = await runResponse.json();
|
|
console.log('Run started with ID:', run.id);
|
|
|
|
// 4. Poll for completion
|
|
console.log('\nPolling for run completion...');
|
|
let completed = false;
|
|
let response = null;
|
|
let pollCount = 0;
|
|
while (!completed) {
|
|
pollCount++;
|
|
console.log(`Polling attempt ${pollCount}...`);
|
|
|
|
const statusResponse = await fetch(`https://api.openai.com/v1/threads/${thread.id}/runs/${run.id}`, {
|
|
headers: {
|
|
'Authorization': `Bearer ${apiKey}`,
|
|
'OpenAI-Beta': 'assistants=v2'
|
|
}
|
|
});
|
|
if (!statusResponse.ok) {
|
|
const errorText = await statusResponse.text();
|
|
console.error('Failed to check run status:', statusResponse.status, errorText);
|
|
throw new Error('Failed to check run status');
|
|
}
|
|
const status = await statusResponse.json();
|
|
console.log('Run status:', status.status);
|
|
|
|
if (status.status === 'completed') {
|
|
console.log('Run completed successfully');
|
|
completed = true;
|
|
// 5. Get the messages
|
|
console.log('Retrieving messages...');
|
|
const messagesResponse = await fetch(`https://api.openai.com/v1/threads/${thread.id}/messages`, {
|
|
headers: {
|
|
'Authorization': `Bearer ${apiKey}`,
|
|
'OpenAI-Beta': 'assistants=v2'
|
|
}
|
|
});
|
|
if (!messagesResponse.ok) {
|
|
const errorText = await messagesResponse.text();
|
|
console.error('Failed to get messages:', messagesResponse.status, errorText);
|
|
throw new Error('Failed to get messages');
|
|
}
|
|
const messages = await messagesResponse.json();
|
|
console.log('Retrieved', messages.data.length, 'messages');
|
|
console.log('\n=== OPENAI RESPONSE ===');
|
|
console.log('Full messages response:', JSON.stringify(messages, null, 2));
|
|
response = messages.data[0].content[0].text.value;
|
|
console.log('\nExtracted response:');
|
|
console.log(response);
|
|
console.log('Response length:', response.length);
|
|
console.log('=== END OPENAI RESPONSE ===\n');
|
|
} else if (status.status === 'failed') {
|
|
console.error('Run failed:', status.last_error);
|
|
throw new Error('Run failed: ' + (status.last_error?.message || 'Unknown error'));
|
|
} else {
|
|
// Wait before checking again
|
|
console.log('Run still in progress, waiting before next poll...');
|
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
}
|
|
}
|
|
console.log('=== END HANDLE GENERATE DRAFT ===\n');
|
|
return response;
|
|
} catch (error) {
|
|
console.error('Draft generation failed:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
function getFromStorage(keys) {
|
|
return new Promise((resolve) => {
|
|
chrome.storage.local.get(keys, resolve);
|
|
});
|
|
}
|
|
|
|
// Handle extension icon click
|
|
chrome.action.onClicked.addListener(async (tab) => {
|
|
// Check if we're on Gmail, Instantly, or Plusvibe/Pipl
|
|
if (!tab.url.includes('mail.google.com') &&
|
|
!tab.url.includes('app.instantly.ai') &&
|
|
!tab.url.includes('app.pipl.ai')) {
|
|
console.log('Extension clicked on unsupported tab, ignoring');
|
|
return;
|
|
}
|
|
|
|
console.log('Extension icon clicked, toggling UI on:', tab.url);
|
|
// Send message to content script to toggle UI visibility
|
|
chrome.tabs.sendMessage(tab.id, {
|
|
action: 'toggleUI'
|
|
});
|
|
});
|