AI-Powered Quiz Assistant (Chrome Extension)
This commit is contained in:
9 files changed
+1101
No files matched your search
+633
@@ -0,0 +1,633 @@
|
||||
let extensionEnabled = false;
|
||||
let isProcessing = false;
|
||||
let processedQuestions = new Set();
|
||||
let openaiApiKey = null;
|
||||
let styleInjected = false;
|
||||
|
||||
// Inject custom CSS for highlighting
|
||||
function injectCustomStyles() {
|
||||
if (styleInjected) return;
|
||||
|
||||
const style = document.createElement('style');
|
||||
style.id = 'quiz-helper-styles';
|
||||
style.textContent = `
|
||||
.quiz-helper-correct {
|
||||
background-color: #90EE90 !important;
|
||||
border: 3px solid #28a745 !important;
|
||||
box-shadow: 0 0 10px rgba(40, 167, 69, 0.6) !important;
|
||||
padding: 8px !important;
|
||||
border-radius: 4px !important;
|
||||
transition: all 0.3s ease !important;
|
||||
}
|
||||
|
||||
.quiz-helper-correct * {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.quiz-helper-unknown {
|
||||
background-color: #FFB6C1 !important;
|
||||
border: 3px solid #dc3545 !important;
|
||||
box-shadow: 0 0 10px rgba(220, 53, 69, 0.6) !important;
|
||||
padding: 8px !important;
|
||||
border-radius: 4px !important;
|
||||
transition: all 0.3s ease !important;
|
||||
}
|
||||
|
||||
.quiz-helper-unknown * {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
styleInjected = true;
|
||||
console.log('✓ Custom CSS styles injected');
|
||||
}
|
||||
|
||||
// Initialize extension state
|
||||
chrome.storage.sync.get(['enabled', 'openaiApiKey'], function(result) {
|
||||
extensionEnabled = result.enabled || false;
|
||||
openaiApiKey = result.openaiApiKey || null;
|
||||
|
||||
if (openaiApiKey) {
|
||||
console.log('✓ OpenAI API key loaded');
|
||||
} else {
|
||||
console.log('⚠ No OpenAI API key found. Please add one in the extension popup.');
|
||||
}
|
||||
|
||||
if (extensionEnabled) {
|
||||
injectCustomStyles();
|
||||
waitForContentAndScan();
|
||||
}
|
||||
});
|
||||
|
||||
// Listen for toggle messages
|
||||
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
||||
if (request.action === 'toggleExtension') {
|
||||
extensionEnabled = request.enabled;
|
||||
|
||||
if (extensionEnabled) {
|
||||
if (!openaiApiKey) {
|
||||
console.error('❌ Cannot enable: No OpenAI API key set. Please add one in the extension popup.');
|
||||
} else {
|
||||
injectCustomStyles();
|
||||
waitForContentAndScan();
|
||||
}
|
||||
} else {
|
||||
removeHighlights();
|
||||
processedQuestions.clear();
|
||||
}
|
||||
} else if (request.action === 'apiKeyUpdated') {
|
||||
openaiApiKey = request.apiKey;
|
||||
console.log('✓ OpenAI API key updated');
|
||||
processedQuestions.clear();
|
||||
}
|
||||
});
|
||||
|
||||
// Wait for dynamic content to load before scanning
|
||||
function waitForContentAndScan() {
|
||||
console.log('Waiting for quiz content to load...');
|
||||
|
||||
let attempts = 0;
|
||||
const maxAttempts = 40;
|
||||
|
||||
const checkContent = setInterval(() => {
|
||||
attempts++;
|
||||
|
||||
const hasContent = document.body.innerText.length > 500;
|
||||
const hasInteractiveElements = document.querySelectorAll('button, input, label').length > 5;
|
||||
const mainContent = document.querySelector('[data-main-column]');
|
||||
const hasMainContent = mainContent && mainContent.innerText.length > 100;
|
||||
const hasQuizElements = document.querySelectorAll('input[type="radio"], input[type="checkbox"]').length > 0;
|
||||
|
||||
console.log(`Attempt ${attempts}: content=${hasContent}, interactive=${hasInteractiveElements}, main=${hasMainContent}, quiz=${hasQuizElements}`);
|
||||
|
||||
if ((hasContent && hasInteractiveElements && hasMainContent) || hasQuizElements) {
|
||||
clearInterval(checkContent);
|
||||
console.log('✓ Quiz content detected! Starting scan in 2 seconds...');
|
||||
setTimeout(() => scanAndHighlight(), 2000);
|
||||
} else if (attempts >= maxAttempts) {
|
||||
clearInterval(checkContent);
|
||||
console.log('✗ Timeout: Quiz content did not load. Try refreshing the page.');
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
|
||||
// Function to identify potential questions
|
||||
function findQuestions() {
|
||||
const questions = [];
|
||||
const seenTexts = new Set();
|
||||
|
||||
console.log('DEBUG: Total body text length:', document.body.innerText.length);
|
||||
console.log('DEBUG: Sample text:', document.body.innerText.substring(0, 500));
|
||||
|
||||
const selectors = [
|
||||
'[role="group"]',
|
||||
'[class*="question"]',
|
||||
'[class*="quiz"]',
|
||||
'[class*="assessment"]',
|
||||
'[data-test*="question"]',
|
||||
'fieldset',
|
||||
'h1, h2, h3, h4, h5, h6',
|
||||
'p',
|
||||
'div[class*="text"]',
|
||||
'div',
|
||||
'span',
|
||||
'label'
|
||||
];
|
||||
|
||||
const questionPatterns = [
|
||||
/\?$/,
|
||||
/\?/,
|
||||
/^(what|when|where|who|why|how|which|is|are|does|do|did|can|could|would|should|will|shall)/i,
|
||||
/\d+\.\s*[A-Z]/,
|
||||
/^(true|false)/i,
|
||||
/characteristics/i,
|
||||
/deployment model/i
|
||||
];
|
||||
|
||||
selectors.forEach(selector => {
|
||||
const elements = document.querySelectorAll(selector);
|
||||
console.log(`DEBUG: Found ${elements.length} elements for selector "${selector}"`);
|
||||
|
||||
elements.forEach(el => {
|
||||
const text = el.textContent.trim();
|
||||
|
||||
if (text.length < 10 || text.length > 1000 || seenTexts.has(text)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const pattern of questionPatterns) {
|
||||
if (pattern.test(text)) {
|
||||
seenTexts.add(text);
|
||||
questions.push({
|
||||
text: text,
|
||||
element: el
|
||||
});
|
||||
console.log('Found question:', text.substring(0, 100));
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (questions.length === 0) {
|
||||
console.log('DEBUG: No questions found. Logging first 10 p elements:');
|
||||
const pElements = document.querySelectorAll('p');
|
||||
Array.from(pElements).slice(0, 10).forEach((el, i) => {
|
||||
console.log(` p[${i}]:`, el.textContent.substring(0, 100));
|
||||
});
|
||||
|
||||
console.log('DEBUG: Logging first 10 divs with text:');
|
||||
const divs = document.querySelectorAll('div');
|
||||
let count = 0;
|
||||
for (const div of divs) {
|
||||
const text = div.textContent.trim();
|
||||
if (text.length > 20 && text.length < 200) {
|
||||
console.log(` div[${count}]:`, text.substring(0, 100));
|
||||
count++;
|
||||
if (count >= 10) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return questions;
|
||||
}
|
||||
|
||||
// Function to find potential answer choices
|
||||
function findAnswerChoices(questionElement) {
|
||||
const answers = [];
|
||||
const seenTexts = new Set();
|
||||
let inputType = null; // 'radio' or 'checkbox'
|
||||
|
||||
let container = questionElement.closest('[role="group"], fieldset, form, [class*="question"], div[class*="options"]');
|
||||
if (!container) {
|
||||
container = questionElement.parentElement;
|
||||
}
|
||||
|
||||
for (let i = 0; i < 3 && container && answers.length === 0; i++) {
|
||||
container = container.parentElement;
|
||||
if (!container) break;
|
||||
}
|
||||
|
||||
if (!container) container = document.body;
|
||||
|
||||
const answerSelectors = [
|
||||
'input[type="radio"]',
|
||||
'input[type="checkbox"]',
|
||||
'button[role="radio"]',
|
||||
'button[role="checkbox"]',
|
||||
'[role="option"]',
|
||||
'[class*="answer"]',
|
||||
'[class*="option"]',
|
||||
'[class*="choice"]',
|
||||
'[data-test*="answer"]',
|
||||
'[data-test*="option"]',
|
||||
'label',
|
||||
'li'
|
||||
];
|
||||
|
||||
const excludePatterns = [
|
||||
/^question\s+\d+\s+of\s+\d+$/i,
|
||||
/^page\s+\d+/i,
|
||||
/^next$/i,
|
||||
/^previous$/i,
|
||||
/^submit$/i,
|
||||
/^skip$/i,
|
||||
/^back$/i,
|
||||
/^continue$/i,
|
||||
/^finish$/i,
|
||||
/^review$/i,
|
||||
/^\d+\s*\/\s*\d+$/,
|
||||
/^score:/i,
|
||||
/^time:/i,
|
||||
/^remaining:/i
|
||||
];
|
||||
|
||||
answerSelectors.forEach(selector => {
|
||||
const elements = container.querySelectorAll(selector);
|
||||
|
||||
elements.forEach(el => {
|
||||
let text = el.textContent.trim();
|
||||
|
||||
// Detect input type from actual input elements
|
||||
if (el.tagName === 'INPUT') {
|
||||
if (el.type === 'radio') {
|
||||
inputType = 'radio';
|
||||
} else if (el.type === 'checkbox') {
|
||||
inputType = 'checkbox';
|
||||
}
|
||||
|
||||
const label = container.querySelector(`label[for="${el.id}"]`);
|
||||
if (label) {
|
||||
text = label.textContent.trim();
|
||||
} else {
|
||||
const parentLabel = el.closest('label');
|
||||
if (parentLabel) {
|
||||
text = parentLabel.textContent.trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!text || text.length < 2 || text.length > 300 || seenTexts.has(text)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (excludePatterns.some(pattern => pattern.test(text))) {
|
||||
console.log(`Excluding UI element: "${text}"`);
|
||||
return;
|
||||
}
|
||||
|
||||
const wordCount = text.split(/\s+/).length;
|
||||
const numberCount = (text.match(/\d+/g) || []).length;
|
||||
if (numberCount > wordCount / 2) {
|
||||
console.log(`Excluding numeric element: "${text}"`);
|
||||
return;
|
||||
}
|
||||
|
||||
seenTexts.add(text);
|
||||
answers.push({
|
||||
text: text,
|
||||
element: el,
|
||||
parentLabel: el.closest('label') || el
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
console.log(`Found ${answers.length} answer choices (type: ${inputType || 'unknown'})`);
|
||||
answers.forEach((a, i) => {
|
||||
console.log(` [${i}] "${a.text.substring(0, 50)}" - Element: ${a.element.tagName}.${a.element.className || 'no-class'}, ID: ${a.element.id || 'no-id'}`);
|
||||
});
|
||||
|
||||
return { answers, inputType };
|
||||
}
|
||||
|
||||
// Function to determine expected number of answers from question text
|
||||
function getExpectedAnswerCount(questionText, inputType) {
|
||||
if (inputType === 'radio') {
|
||||
return 1; // Radio buttons always mean single answer
|
||||
}
|
||||
|
||||
// For checkboxes, try to find the expected count in the question
|
||||
const lowerQuestion = questionText.toLowerCase();
|
||||
|
||||
// Look for explicit numbers
|
||||
const numberWords = {
|
||||
'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5,
|
||||
'six': 6, 'seven': 7, 'eight': 8, 'nine': 9, 'ten': 10
|
||||
};
|
||||
|
||||
// Check for patterns like "select two", "choose three", "two factors", etc.
|
||||
for (const [word, num] of Object.entries(numberWords)) {
|
||||
const patterns = [
|
||||
new RegExp(`\\b${word}\\s+(answers?|options?|choices?|factors?|characteristics?|items?|reasons?|ways?|methods?|types?)\\b`, 'i'),
|
||||
new RegExp(`\\bselect\\s+${word}\\b`, 'i'),
|
||||
new RegExp(`\\bchoose\\s+${word}\\b`, 'i'),
|
||||
new RegExp(`\\bpick\\s+${word}\\b`, 'i'),
|
||||
new RegExp(`\\bwhich\\s+${word}\\b`, 'i')
|
||||
];
|
||||
|
||||
for (const pattern of patterns) {
|
||||
if (pattern.test(lowerQuestion)) {
|
||||
console.log(`📊 Detected ${num} expected answers from question text`);
|
||||
return num;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for digit numbers (e.g., "2 answers", "3 characteristics")
|
||||
const digitMatch = lowerQuestion.match(/\b(\d+)\s+(answers?|options?|choices?|factors?|characteristics?|items?|reasons?|ways?|methods?|types?)\b/i);
|
||||
if (digitMatch) {
|
||||
const count = parseInt(digitMatch[1]);
|
||||
console.log(`📊 Detected ${count} expected answers from question text`);
|
||||
return count;
|
||||
}
|
||||
|
||||
// Check for "Each correct answer" or "select all that apply" - means multiple but unknown count
|
||||
if (/each correct answer|select all that apply|all that apply|select all|choose all/i.test(lowerQuestion)) {
|
||||
console.log(`📊 Detected "select all" question - expecting multiple answers`);
|
||||
return -1; // Special value meaning "multiple, unknown count"
|
||||
}
|
||||
|
||||
// Default for checkboxes: assume multiple but we don't know how many
|
||||
console.log(`📊 Checkbox question with no specific count mentioned`);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Function to get answer using OpenAI
|
||||
async function getAnswerFromOpenAI(question, answerChoices, inputType, expectedCount) {
|
||||
if (!openaiApiKey) {
|
||||
console.error('❌ No API key available');
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('🤖 Asking ChatGPT...');
|
||||
|
||||
const choicesText = answerChoices.map((c, i) => `${String.fromCharCode(65 + i)}. ${c.text}`).join('\n');
|
||||
|
||||
let instructions = '';
|
||||
if (inputType === 'radio') {
|
||||
instructions = '- This is a SINGLE-ANSWER question (radio buttons). Provide exactly ONE letter.';
|
||||
} else if (expectedCount > 0) {
|
||||
instructions = `- This is a MULTIPLE-ANSWER question (checkboxes). Provide exactly ${expectedCount} letter(s), separated by commas.`;
|
||||
} else if (expectedCount === -1) {
|
||||
instructions = '- This is a MULTIPLE-ANSWER question (checkboxes). Provide ALL correct answers, separated by commas.';
|
||||
} else {
|
||||
instructions = '- Determine if this requires one or multiple answers based on the question wording.';
|
||||
}
|
||||
|
||||
const prompt = `You are helping with a Microsoft Azure certification exam. Answer the following multiple-choice question by selecting the correct answer(s).
|
||||
|
||||
Question: ${question}
|
||||
|
||||
Answer choices:
|
||||
${choicesText}
|
||||
|
||||
Instructions:
|
||||
${instructions}
|
||||
- Respond ONLY with the letter(s) of the correct answer(s) (e.g., "A" or "A,C,D")
|
||||
- Do not include any explanation, reasoning, or additional text
|
||||
- Do not include spaces after commas
|
||||
|
||||
Correct answer(s):`;
|
||||
|
||||
const response = await fetch('https://api.openai.com/v1/chat/completions', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${openaiApiKey}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: 'gpt-3.5-turbo',
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
content: 'You are an expert on Microsoft Azure and cloud computing. You provide only the letter(s) of correct answers to multiple-choice questions, without any explanation.'
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: prompt
|
||||
}
|
||||
],
|
||||
temperature: 0.1,
|
||||
max_tokens: 20
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
console.error('❌ OpenAI API error:', error);
|
||||
if (error.error && error.error.message) {
|
||||
console.error('Error message:', error.error.message);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const answer = data.choices[0].message.content.trim();
|
||||
|
||||
console.log('🤖 ChatGPT says:', answer);
|
||||
|
||||
const correctLetters = answer.replace(/\s/g, '').split(',').map(l => l.trim().toUpperCase());
|
||||
const correctIndices = correctLetters.map(l => l.charCodeAt(0) - 65).filter(i => i >= 0 && i < answerChoices.length);
|
||||
|
||||
// Validate answer count for radio buttons
|
||||
if (inputType === 'radio' && correctIndices.length > 1) {
|
||||
console.log(`⚠️ Warning: Radio button question but got ${correctIndices.length} answers. Using only the first one.`);
|
||||
return [correctIndices[0]];
|
||||
}
|
||||
|
||||
// Validate answer count for checkboxes with specific count
|
||||
if (expectedCount > 0 && correctIndices.length !== expectedCount) {
|
||||
console.log(`⚠️ Warning: Expected ${expectedCount} answers but got ${correctIndices.length}`);
|
||||
}
|
||||
|
||||
return correctIndices;
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Error calling OpenAI:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Function to highlight matching answers
|
||||
function highlightAnswer(element, isCorrect = true) {
|
||||
const colorType = isCorrect ? 'correct' : 'unknown';
|
||||
console.log(` 🎨 Attempting to highlight as ${colorType}`);
|
||||
console.log(` Original element:`, element);
|
||||
|
||||
let targetElement = element;
|
||||
|
||||
if (element.tagName === 'INPUT') {
|
||||
console.log(` Element is INPUT, looking for label...`);
|
||||
const label = document.querySelector(`label[for="${element.id}"]`) || element.closest('label');
|
||||
if (label) {
|
||||
targetElement = label;
|
||||
console.log(` Found label:`, label);
|
||||
} else {
|
||||
targetElement = element.parentElement;
|
||||
console.log(` No label found, using parent:`, targetElement);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove any existing quiz helper classes
|
||||
targetElement.classList.remove('quiz-helper-correct', 'quiz-helper-unknown');
|
||||
targetElement.removeAttribute('data-quiz-helper-highlight');
|
||||
|
||||
// Add the appropriate class
|
||||
const className = isCorrect ? 'quiz-helper-correct' : 'quiz-helper-unknown';
|
||||
targetElement.classList.add(className);
|
||||
targetElement.setAttribute('data-quiz-helper-highlight', 'true');
|
||||
targetElement.setAttribute('data-quiz-helper-type', colorType);
|
||||
|
||||
console.log(` ✓ Applied ${colorType} class to:`, targetElement.textContent.substring(0, 50));
|
||||
console.log(` Classes:`, targetElement.className);
|
||||
}
|
||||
|
||||
// Function to remove all highlights
|
||||
function removeHighlights() {
|
||||
const highlighted = document.querySelectorAll('[data-quiz-helper-highlight="true"]');
|
||||
highlighted.forEach(el => {
|
||||
el.classList.remove('quiz-helper-correct', 'quiz-helper-unknown');
|
||||
el.removeAttribute('data-quiz-helper-highlight');
|
||||
el.removeAttribute('data-quiz-helper-type');
|
||||
});
|
||||
console.log(`Removed ${highlighted.length} highlights`);
|
||||
}
|
||||
|
||||
// Main scanning function
|
||||
async function scanAndHighlight() {
|
||||
if (isProcessing || !extensionEnabled) return;
|
||||
|
||||
if (!openaiApiKey) {
|
||||
console.error('❌ Cannot scan: No OpenAI API key. Please add one in the extension popup.');
|
||||
return;
|
||||
}
|
||||
|
||||
isProcessing = true;
|
||||
console.log('Starting scan...');
|
||||
|
||||
try {
|
||||
const questions = findQuestions();
|
||||
console.log(`Found ${questions.length} potential questions`);
|
||||
|
||||
if (questions.length === 0) {
|
||||
console.log('No questions found. Page might still be loading or uses unsupported format.');
|
||||
isProcessing = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const questionsToProcess = questions.slice(0, 10);
|
||||
|
||||
for (const question of questionsToProcess) {
|
||||
if (processedQuestions.has(question.text)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
processedQuestions.add(question.text);
|
||||
|
||||
const { answers: answerChoices, inputType } = findAnswerChoices(question.element);
|
||||
|
||||
if (answerChoices.length === 0) {
|
||||
console.log('No answer choices found for question');
|
||||
continue;
|
||||
}
|
||||
|
||||
const expectedCount = getExpectedAnswerCount(question.text, inputType);
|
||||
|
||||
console.log(`\n📝 Question: ${question.text.substring(0, 100)}...`);
|
||||
console.log(` Input type: ${inputType || 'unknown'}`);
|
||||
console.log(` Expected answers: ${expectedCount === -1 ? 'multiple (unknown count)' : expectedCount}`);
|
||||
console.log(` Answer choices (${answerChoices.length}):`);
|
||||
answerChoices.forEach((c, i) => {
|
||||
console.log(` ${String.fromCharCode(65 + i)}. ${c.text.substring(0, 50)}`);
|
||||
});
|
||||
|
||||
const correctIndices = await getAnswerFromOpenAI(question.text, answerChoices, inputType, expectedCount);
|
||||
|
||||
if (correctIndices && correctIndices.length > 0) {
|
||||
console.log(`✅ ChatGPT identified ${correctIndices.length} correct answer(s):`);
|
||||
|
||||
let highlightedCount = 0;
|
||||
correctIndices.forEach(index => {
|
||||
if (index >= 0 && index < answerChoices.length) {
|
||||
const choice = answerChoices[index];
|
||||
console.log(` ✓ ${String.fromCharCode(65 + index)}. ${choice.text}`);
|
||||
console.log(` Element type: ${choice.element.tagName}, class: ${choice.element.className}`);
|
||||
|
||||
try {
|
||||
highlightAnswer(choice.element, true);
|
||||
highlightedCount++;
|
||||
console.log(` ✓ Successfully highlighted`);
|
||||
} catch (e) {
|
||||
console.error(` ❌ Failed to highlight:`, e);
|
||||
}
|
||||
} else {
|
||||
console.error(` ❌ Invalid index ${index} (max: ${answerChoices.length - 1})`);
|
||||
}
|
||||
});
|
||||
|
||||
if (highlightedCount === 0) {
|
||||
console.error('⚠️ WARNING: ChatGPT provided answers but NONE were highlighted!');
|
||||
console.log('⚠️ Falling back to RED highlighting (user must guess)');
|
||||
answerChoices.forEach((choice, index) => {
|
||||
console.log(` ⚠️ ${String.fromCharCode(65 + index)}. ${choice.text} (HIGHLIGHT FAILED)`);
|
||||
try {
|
||||
highlightAnswer(choice.element, false);
|
||||
} catch (e) {
|
||||
console.error(` ❌ Failed to highlight in red:`, e);
|
||||
}
|
||||
});
|
||||
} else if (highlightedCount < correctIndices.length) {
|
||||
console.warn(`⚠️ WARNING: Only ${highlightedCount} of ${correctIndices.length} answers were highlighted`);
|
||||
}
|
||||
|
||||
} else {
|
||||
console.log('❌ ChatGPT failed to provide an answer - highlighting all choices in RED');
|
||||
console.log('⚠️ USER MUST GUESS - ChatGPT could not determine the correct answer');
|
||||
// Highlight all answers in red to indicate user needs to make a guess
|
||||
answerChoices.forEach((choice, index) => {
|
||||
console.log(` ⚠️ ${String.fromCharCode(65 + index)}. ${choice.text} (UNKNOWN)`);
|
||||
try {
|
||||
highlightAnswer(choice.element, false);
|
||||
} catch (e) {
|
||||
console.error(` ❌ Failed to highlight:`, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
}
|
||||
|
||||
console.log('\n✅ Scan complete');
|
||||
} catch (error) {
|
||||
console.error('Error in scanAndHighlight:', error);
|
||||
} finally {
|
||||
isProcessing = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Watch for dynamic content changes
|
||||
let mutationTimeout;
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
if (!extensionEnabled || isProcessing) return;
|
||||
|
||||
const hasNewContent = mutations.some(mutation => {
|
||||
return mutation.addedNodes.length > 0 &&
|
||||
Array.from(mutation.addedNodes).some(node =>
|
||||
node.nodeType === 1 && node.textContent.length > 50
|
||||
);
|
||||
});
|
||||
|
||||
if (hasNewContent) {
|
||||
clearTimeout(mutationTimeout);
|
||||
mutationTimeout = setTimeout(() => {
|
||||
console.log('Content changed, rescanning...');
|
||||
scanAndHighlight();
|
||||
}, 2000);
|
||||
}
|
||||
});
|
||||
|
||||
observer.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true
|
||||
});
|
||||
Reference in new issue
Block a user