AI-powered exam assistant for 富学宝典 - automatically analyzes questions and suggests answers
Size
13.7 KB
Version
1.1.1
Created
Nov 5, 2025
Updated
about 1 month ago
1// ==UserScript==
2// @name 富學寶典答題助手
3// @description AI-powered exam assistant for 富学宝典 - automatically analyzes questions and suggests answers
4// @version 1.1.1
5// @match https://*.iedu.foxconn.com/*
6// @icon https://iedu.foxconn.com/favicon.ico
7// @grant GM.getValue
8// @grant GM.setValue
9// @grant GM.xmlhttpRequest
10// @grant RM.aiCall
11// ==/UserScript==
12(function() {
13 'use strict';
14
15 console.log('富學寶典答題助手已啟動');
16
17 // Debounce function to prevent excessive calls
18 function debounce(func, wait) {
19 let timeout;
20 return function executedFunction(...args) {
21 const later = () => {
22 clearTimeout(timeout);
23 func(...args);
24 };
25 clearTimeout(timeout);
26 timeout = setTimeout(later, wait);
27 };
28 }
29
30 // Extract all questions from the exam page
31 function extractQuestions() {
32 const questions = [];
33 const questionElements = document.querySelectorAll('.question_warp');
34
35 console.log(`找到 ${questionElements.length} 個問題`);
36
37 questionElements.forEach((element, index) => {
38 const questionTitle = element.querySelector('h3');
39 const options = element.querySelectorAll('.radio label');
40 const radioInputs = element.querySelectorAll('input[type="radio"]');
41
42 if (questionTitle && options.length > 0) {
43 const questionText = questionTitle.textContent.trim();
44 const optionsList = [];
45 const radioName = radioInputs[0]?.name || '';
46
47 options.forEach((option) => {
48 const input = option.querySelector('input[type="radio"]');
49 const value = input?.value || '';
50 const text = option.textContent.trim();
51 optionsList.push({ value, text });
52 });
53
54 questions.push({
55 index: index + 1,
56 question: questionText,
57 options: optionsList,
58 radioName: radioName,
59 element: element
60 });
61 }
62 });
63
64 return questions;
65 }
66
67 // Analyze questions using AI
68 async function analyzeQuestionsWithAI(questions) {
69 console.log('開始使用 AI 分析問題...');
70
71 try {
72 // Prepare questions for AI analysis
73 const questionsText = questions.map(q => {
74 const optionsText = q.options.map(opt => opt.text).join('\n');
75 return `問題 ${q.index}:\n${q.question}\n選項:\n${optionsText}\n`;
76 }).join('\n---\n');
77
78 const prompt = `你是一個專業的考試助手。請分析以下關於靜電防護(ESD)的考試題目,並為每個問題選擇最正確的答案。
79
80${questionsText}
81
82請以JSON格式返回答案,包含每個問題的編號和建議的答案選項(A, B, C, D等)。同時提供簡短的解釋說明為什麼選擇該答案。`;
83
84 console.log('正在調用 AI API...');
85
86 const response = await RM.aiCall(prompt, {
87 type: "json_schema",
88 json_schema: {
89 name: "exam_answers",
90 schema: {
91 type: "object",
92 properties: {
93 answers: {
94 type: "array",
95 items: {
96 type: "object",
97 properties: {
98 questionNumber: { type: "number" },
99 selectedOption: { type: "string" },
100 explanation: { type: "string" }
101 },
102 required: ["questionNumber", "selectedOption", "explanation"]
103 }
104 }
105 },
106 required: ["answers"]
107 }
108 }
109 });
110
111 console.log('AI 分析完成:', response);
112 return response;
113
114 } catch (error) {
115 console.error('AI 分析失敗:', error);
116 throw error;
117 }
118 }
119
120 // Auto-fill answers based on AI suggestions
121 function autoFillAnswers(questions, aiResponse) {
122 console.log('開始自動填寫答案...');
123
124 if (!aiResponse || !aiResponse.answers) {
125 console.error('AI 回應格式錯誤');
126 return;
127 }
128
129 aiResponse.answers.forEach(answer => {
130 const question = questions.find(q => q.index === answer.questionNumber);
131 if (question) {
132 const radioInput = document.querySelector(`input[name="${question.radioName}"][value="${answer.selectedOption}"]`);
133 if (radioInput) {
134 radioInput.checked = true;
135 console.log(`問題 ${answer.questionNumber}: 選擇 ${answer.selectedOption} - ${answer.explanation}`);
136 }
137 }
138 });
139
140 console.log('答案填寫完成!');
141 }
142
143 // Show AI analysis results in a modal
144 function showAnalysisResults(aiResponse) {
145 // Remove existing modal if any
146 const existingModal = document.getElementById('ai-analysis-modal');
147 if (existingModal) {
148 existingModal.remove();
149 }
150
151 // Create modal
152 const modal = document.createElement('div');
153 modal.id = 'ai-analysis-modal';
154 modal.style.cssText = `
155 position: fixed;
156 top: 50%;
157 left: 50%;
158 transform: translate(-50%, -50%);
159 background: white;
160 border: 2px solid #007bff;
161 border-radius: 10px;
162 padding: 20px;
163 max-width: 600px;
164 max-height: 80vh;
165 overflow-y: auto;
166 z-index: 10000;
167 box-shadow: 0 4px 20px rgba(0,0,0,0.3);
168 `;
169
170 let resultsHTML = '<h2 style="color: #007bff; margin-top: 0;">AI 分析結果</h2>';
171
172 if (aiResponse && aiResponse.answers) {
173 aiResponse.answers.forEach(answer => {
174 resultsHTML += `
175 <div style="margin-bottom: 15px; padding: 10px; background: #f8f9fa; border-radius: 5px;">
176 <strong style="color: #28a745;">問題 ${answer.questionNumber}:</strong>
177 <span style="font-size: 18px; font-weight: bold; color: #dc3545;">選項 ${answer.selectedOption}</span>
178 <p style="margin: 5px 0 0 0; color: #666;">${answer.explanation}</p>
179 </div>
180 `;
181 });
182 }
183
184 resultsHTML += `
185 <button id="close-modal-btn" style="
186 background: #dc3545;
187 color: white;
188 border: none;
189 padding: 10px 20px;
190 border-radius: 5px;
191 cursor: pointer;
192 font-size: 16px;
193 margin-top: 10px;
194 ">關閉</button>
195 `;
196
197 modal.innerHTML = resultsHTML;
198
199 // Add overlay
200 const overlay = document.createElement('div');
201 overlay.id = 'ai-analysis-overlay';
202 overlay.style.cssText = `
203 position: fixed;
204 top: 0;
205 left: 0;
206 width: 100%;
207 height: 100%;
208 background: rgba(0,0,0,0.5);
209 z-index: 9999;
210 `;
211
212 document.body.appendChild(overlay);
213 document.body.appendChild(modal);
214
215 // Close modal handlers
216 document.getElementById('close-modal-btn').addEventListener('click', () => {
217 modal.remove();
218 overlay.remove();
219 });
220
221 overlay.addEventListener('click', () => {
222 modal.remove();
223 overlay.remove();
224 });
225 }
226
227 // Show loading indicator
228 function showLoadingIndicator() {
229 const loader = document.createElement('div');
230 loader.id = 'ai-loader';
231 loader.innerHTML = `
232 <div style="display: flex; align-items: center; gap: 10px;">
233 <div style="
234 border: 3px solid #f3f3f3;
235 border-top: 3px solid #007bff;
236 border-radius: 50%;
237 width: 20px;
238 height: 20px;
239 animation: spin 1s linear infinite;
240 "></div>
241 <span>AI 正在分析問題...</span>
242 </div>
243 `;
244 loader.style.cssText = `
245 position: fixed;
246 top: 20px;
247 right: 20px;
248 background: #007bff;
249 color: white;
250 padding: 15px 20px;
251 border-radius: 8px;
252 z-index: 10000;
253 box-shadow: 0 4px 12px rgba(0,0,0,0.2);
254 font-size: 14px;
255 `;
256
257 // Add animation
258 const style = document.createElement('style');
259 style.textContent = `
260 @keyframes spin {
261 0% { transform: rotate(0deg); }
262 100% { transform: rotate(360deg); }
263 }
264 `;
265 document.head.appendChild(style);
266
267 document.body.appendChild(loader);
268 }
269
270 function hideLoadingIndicator() {
271 const loader = document.getElementById('ai-loader');
272 if (loader) loader.remove();
273 }
274
275 // Main function to handle AI analysis
276 async function handleAIAnalysis() {
277 try {
278 showLoadingIndicator();
279
280 // Extract questions
281 const questions = extractQuestions();
282
283 if (questions.length === 0) {
284 alert('未找到考試題目!請確認您在考試頁面上。');
285 hideLoadingIndicator();
286 return;
287 }
288
289 // Analyze with AI
290 const aiResponse = await analyzeQuestionsWithAI(questions);
291
292 hideLoadingIndicator();
293
294 // Show results
295 showAnalysisResults(aiResponse);
296
297 // Auto-fill answers
298 autoFillAnswers(questions, aiResponse);
299
300 } catch (error) {
301 hideLoadingIndicator();
302 console.error('處理失敗:', error);
303 alert('AI 分析失敗,請稍後再試。錯誤: ' + error.message);
304 }
305 }
306
307 // Create AI assistant button
308 function createAIButton() {
309 // Check if we're on an exam page
310 const isExamPage = window.location.href.includes('/examUI');
311
312 if (!isExamPage) {
313 console.log('不在考試頁面,不顯示 AI 按鈕');
314 return;
315 }
316
317 // Remove existing button if any
318 const existingButton = document.getElementById('ai-assistant-btn');
319 if (existingButton) {
320 existingButton.remove();
321 }
322
323 const button = document.createElement('button');
324 button.id = 'ai-assistant-btn';
325 button.innerHTML = `
326 <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" style="margin-right: 8px; vertical-align: middle;">
327 <path d="M12 2L2 7l10 5 10-5-10-5z"></path>
328 <path d="M2 17l10 5 10-5"></path>
329 <path d="M2 12l10 5 10-5"></path>
330 </svg>
331 AI 智能答題
332 `;
333 button.style.cssText = `
334 position: fixed;
335 bottom: 30px;
336 right: 30px;
337 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
338 color: white;
339 border: none;
340 padding: 15px 25px;
341 border-radius: 50px;
342 cursor: pointer;
343 font-size: 16px;
344 font-weight: bold;
345 z-index: 9999;
346 box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
347 transition: all 0.3s ease;
348 display: flex;
349 align-items: center;
350 `;
351
352 // Hover effect
353 button.addEventListener('mouseenter', () => {
354 button.style.transform = 'translateY(-2px)';
355 button.style.boxShadow = '0 6px 20px rgba(102, 126, 234, 0.6)';
356 });
357
358 button.addEventListener('mouseleave', () => {
359 button.style.transform = 'translateY(0)';
360 button.style.boxShadow = '0 4px 15px rgba(102, 126, 234, 0.4)';
361 });
362
363 button.addEventListener('click', handleAIAnalysis);
364
365 document.body.appendChild(button);
366 console.log('AI 助手按鈕已創建');
367 }
368
369 // Initialize the extension
370 function init() {
371 console.log('初始化富學寶典答題助手...');
372
373 // Wait for page to load
374 if (document.readyState === 'loading') {
375 document.addEventListener('DOMContentLoaded', () => {
376 setTimeout(createAIButton, 1000);
377 });
378 } else {
379 setTimeout(createAIButton, 1000);
380 }
381
382 // Re-create button on navigation (for SPA-like behavior)
383 const debouncedCreateButton = debounce(createAIButton, 500);
384
385 // Watch for URL changes
386 let lastUrl = location.href;
387 new MutationObserver(() => {
388 const url = location.href;
389 if (url !== lastUrl) {
390 lastUrl = url;
391 console.log('URL 變更,重新檢查頁面...');
392 debouncedCreateButton();
393 }
394 }).observe(document.body, { childList: true, subtree: true });
395 }
396
397 // Start the extension
398 init();
399
400})();