cryptonaut commited on
Commit
2f9457e
·
verified ·
1 Parent(s): 14903d1

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +966 -18
index.html CHANGED
@@ -1,19 +1,967 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  </html>
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>AI Task Administration UI (Optional Auto-Score)</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <style>
9
+ /* Custom styles */
10
+ body {
11
+ font-family: 'Inter', sans-serif;
12
+ }
13
+ .transition-all {
14
+ transition: all 0.3s ease-in-out;
15
+ }
16
+ button:disabled {
17
+ opacity: 0.5;
18
+ cursor: not-allowed;
19
+ }
20
+ .center-content {
21
+ display: flex;
22
+ flex-direction: column;
23
+ align-items: center;
24
+ padding: 1rem;
25
+ margin-bottom: 3rem;
26
+ }
27
+ .main-container {
28
+ max-width: 800px;
29
+ width: 100%;
30
+ }
31
+ .error-message {
32
+ color: #dc2626; /* red-600 */
33
+ font-size: 0.875rem; /* text-sm */
34
+ margin-top: 0.25rem;
35
+ }
36
+ #aiResponse pre {
37
+ white-space: pre-wrap;
38
+ word-wrap: break-word;
39
+ font-family: inherit;
40
+ font-size: 0.95rem;
41
+ line-height: 1.5;
42
+ background-color: #f9fafb; /* gray-50 */
43
+ padding: 0.75rem;
44
+ border-radius: 0.375rem; /* rounded-md */
45
+ border: 1px solid #e5e7eb; /* gray-200 */
46
+ max-height: 300px;
47
+ overflow-y: auto;
48
+ }
49
+ .data-table {
50
+ width: 100%;
51
+ border-collapse: collapse;
52
+ margin-top: 1rem;
53
+ }
54
+ .data-table th, .data-table td {
55
+ border: 1px solid #e5e7eb;
56
+ padding: 0.5rem 0.75rem;
57
+ text-align: left;
58
+ vertical-align: top;
59
+ }
60
+ .data-table th {
61
+ background-color: #f3f4f6;
62
+ font-weight: 600;
63
+ white-space: nowrap;
64
+ }
65
+ .data-table tr:nth-child(even) {
66
+ background-color: #f9fafb;
67
+ }
68
+ .data-table td.prompt-cell {
69
+ max-width: 300px;
70
+ white-space: normal;
71
+ font-size: 0.8rem;
72
+ line-height: 1.2;
73
+ }
74
+ .modal {
75
+ display: none;
76
+ position: fixed;
77
+ z-index: 1000;
78
+ left: 0;
79
+ top: 0;
80
+ width: 100%;
81
+ height: 100%;
82
+ overflow: auto;
83
+ background-color: rgba(0,0,0,0.6);
84
+ }
85
+ .modal-content {
86
+ background-color: #fefefe;
87
+ margin: 5% auto;
88
+ padding: 25px;
89
+ border: 1px solid #888;
90
+ width: 90%;
91
+ max-width: 900px;
92
+ border-radius: 8px;
93
+ position: relative;
94
+ }
95
+ .modal-close {
96
+ color: #aaa;
97
+ position: absolute;
98
+ top: 10px;
99
+ right: 20px;
100
+ font-size: 28px;
101
+ font-weight: bold;
102
+ cursor: pointer;
103
+ }
104
+ .modal-close:hover,
105
+ .modal-close:focus {
106
+ color: black;
107
+ text-decoration: none;
108
+ }
109
+ .details-button {
110
+ font-size: 0.75rem;
111
+ padding: 0.25rem 0.5rem;
112
+ background-color: #4f46e5;
113
+ color: white;
114
+ border-radius: 0.375rem;
115
+ cursor: pointer;
116
+ transition: background-color 0.2s;
117
+ }
118
+ .details-button:hover {
119
+ background-color: #4338ca;
120
+ }
121
+ .suggested-score-badge {
122
+ display: inline-block;
123
+ padding: 0.2em 0.6em;
124
+ font-size: 0.75em;
125
+ font-weight: bold;
126
+ line-height: 1;
127
+ color: #fff;
128
+ text-align: center;
129
+ white-space: nowrap;
130
+ vertical-align: baseline;
131
+ border-radius: 0.25rem;
132
+ background-color: #10b981; /* green-500 */
133
+ margin-left: 8px;
134
+ }
135
+ #scoringInput .scoring-option-description {
136
+ display: block;
137
+ margin-left: 1.75rem;
138
+ font-size: 0.8rem;
139
+ color: #4b5563;
140
+ }
141
+ </style>
142
+ <link rel="preconnect" href="https://fonts.googleapis.com">
143
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
144
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
145
+ </head>
146
+ <body class="bg-gray-100 text-gray-800 center-content">
147
+
148
+ <div class="main-container bg-white p-6 md:p-8 rounded-lg shadow-lg mb-8">
149
+ <h1 class="text-2xl md:text-3xl font-bold mb-6 text-center text-blue-700">AI Task Administration UI</h1>
150
+
151
+ <div id="configArea" class="mb-6 p-4 bg-gray-50 rounded-md border border-gray-200 space-y-4">
152
+ <h2 class="text-lg font-semibold mb-2 text-gray-700">API Configuration</h2>
153
+ <p class="text-sm text-red-600 font-medium">⚠️ Security Warning: Never enter sensitive API keys on untrusted websites. Use this only for local testing.</p>
154
+ <div>
155
+ <label for="baseUrl" class="block text-sm font-medium text-gray-700">API Base URL:</label>
156
+ <input type="url" id="baseUrl" placeholder="e.g., https://api.openai.com/v1" value="https://api.openai.com/v1" class="mt-1 block w-full px-3 py-2 bg-white border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" required>
157
+ </div>
158
+ <div>
159
+ <label for="apiKey" class="block text-sm font-medium text-gray-700">API Key:</label>
160
+ <input type="password" id="apiKey" placeholder="Enter your API Key (sk-...)" class="mt-1 block w-full px-3 py-2 bg-white border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" required>
161
+ </div>
162
+ <div>
163
+ <label for="modelName" class="block text-sm font-medium text-gray-700">Model Name:</label>
164
+ <input type="text" id="modelName" placeholder="e.g., gpt-3.5-turbo" value="gpt-3.5-turbo" class="mt-1 block w-full px-3 py-2 bg-white border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" required>
165
+ </div>
166
+ <div>
167
+ <label for="enableAutoScoring" class="flex items-center text-sm font-medium text-gray-700 mt-3">
168
+ <input type="checkbox" id="enableAutoScoring" class="form-checkbox h-4 w-4 text-indigo-600 border-gray-300 rounded focus:ring-indigo-500 mr-2">
169
+ Enable Automatic Scoring & Auto-Advance
170
+ </label>
171
+ <p class="text-xs text-gray-500 mt-1 ml-6">If checked, suggested scores will be automatically submitted after a short delay.</p>
172
+ </div>
173
+ <div id="configError" class="error-message hidden"></div>
174
+ </div>
175
+
176
+ <div id="startControl" class="mb-6 text-center">
177
+ <button id="startButton" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-6 rounded-lg shadow transition-all">
178
+ Start Test
179
+ </button>
180
+ </div>
181
+
182
+ <div id="testArea" class="hidden space-y-6">
183
+ <div class="w-full bg-gray-200 rounded-full h-2.5 dark:bg-gray-700">
184
+ <div id="progressBar" class="bg-blue-600 h-2.5 rounded-full" style="width: 0%"></div>
185
+ </div>
186
+ <p id="progressText" class="text-center text-sm text-gray-600">Task 0 / 0</p>
187
+
188
+ <div class="p-4 bg-blue-50 rounded-md border border-blue-200">
189
+ <h2 class="text-lg font-semibold mb-2 text-blue-800">Task <span id="taskNumber"></span>: <span id="taskType"></span></h2>
190
+ <p id="taskPrompt" class="text-gray-700 text-base"></p>
191
+ </div>
192
+
193
+ <div class="p-4 bg-green-50 rounded-md border border-green-200">
194
+ <h2 class="text-lg font-semibold mb-2 text-green-800">AI Response</h2>
195
+ <div id="aiResponse" class="text-gray-700 p-2 rounded bg-white border border-gray-200 min-h-[80px] flex items-center justify-center">
196
+ <span class="text-gray-500 italic">Waiting for API response...</span>
197
+ </div>
198
+ <div id="loadingIndicator" class="hidden text-center mt-2 text-sm text-gray-500">
199
+ <svg class="animate-spin h-5 w-5 inline-block mr-2 text-blue-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
200
+ <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
201
+ <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
202
+ </svg>
203
+ Calling API...
204
+ </div>
205
+ <div id="apiError" class="error-message hidden mt-2"></div>
206
+ </div>
207
+
208
+ <div class="p-4 bg-yellow-50 rounded-md border border-yellow-200">
209
+ <div class="flex justify-between items-center mb-2">
210
+ <h2 class="text-lg font-semibold text-yellow-800">Scoring</h2>
211
+ <span id="suggestedScoreDisplay" class="hidden"></span>
212
+ </div>
213
+ <p id="scoringInstructions" class="text-sm text-gray-600 mb-2">Evaluate the AI's response. Timer starts after response.</p>
214
+ <div id="scoringInput" class="space-y-2">
215
+ <span class="text-gray-500 italic">Score after response is received.</span>
216
+ </div>
217
+ </div>
218
+
219
+ <div class="text-center">
220
+ <button id="nextButton" class="bg-green-600 hover:bg-green-700 text-white font-bold py-2 px-6 rounded-lg shadow transition-all" disabled>
221
+ Submit Score & Next Task
222
+ </button>
223
+ </div>
224
+ </div>
225
+
226
+ <div id="resultsArea" class="hidden mt-8 p-6 bg-purple-50 rounded-md border border-purple-200">
227
+ <h2 class="text-xl font-semibold mb-4 text-center text-purple-800">Test Completed!</h2>
228
+ <p class="text-center text-gray-700 mb-1">Final Score:</p>
229
+ <p id="finalScore" class="text-center text-3xl font-bold text-purple-700 mb-4"></p>
230
+
231
+ <div class="mt-4 mb-4 flex flex-col sm:flex-row items-center justify-center gap-2">
232
+ <label for="userName" class="block text-sm font-medium text-gray-700 mb-1 sm:mb-0">Save score as:</label>
233
+ <input type="text" id="userName" placeholder="Enter your name" class="block px-3 py-2 bg-white border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
234
+ <button id="saveScoreButton" class="bg-indigo-600 hover:bg-indigo-700 text-white font-bold py-2 px-4 rounded-lg shadow transition-all">
235
+ Save to Leaderboard
236
+ </button>
237
+ </div>
238
+ <div id="saveMessage" class="text-center text-sm text-green-600 mt-2"></div>
239
+
240
+
241
+ <p class="mt-6 text-center text-red-600 font-semibold text-sm">Disclaimer: Results are from adapted tasks and are NOT equivalent to a standardized IQ score. Auto-scoring is heuristic.</p>
242
+ <div class="mt-6 text-center">
243
+ <button onclick="window.location.reload();" class="bg-gray-500 hover:bg-gray-600 text-white font-bold py-2 px-4 rounded-lg shadow transition-all">
244
+ Start New Test
245
+ </button>
246
+ </div>
247
+ </div>
248
+
249
+ </div> <div id="leaderboardArea" class="main-container w-full bg-white p-6 md:p-8 rounded-lg shadow-lg mt-8">
250
+ <h2 class="text-xl md:text-2xl font-bold mb-4 text-center text-teal-700">Leaderboard</h2>
251
+ <div id="leaderboardContent" class="overflow-x-auto">
252
+ <p class="text-center text-gray-500 italic">No scores saved yet.</p>
253
+ </div>
254
+ <div class="mt-4 text-center">
255
+ <button id="clearLeaderboardButton" class="bg-red-600 hover:bg-red-700 text-white font-bold py-1 px-3 rounded-lg shadow text-xs transition-all">
256
+ Clear Leaderboard
257
+ </button>
258
+ </div>
259
+ </div>
260
+
261
+ <div id="detailsModal" class="modal">
262
+ <div class="modal-content">
263
+ <span id="modalCloseButton" class="modal-close">&times;</span>
264
+ <h3 id="detailsModalTitle" class="text-xl font-semibold mb-4 text-center text-gray-800">Task Details</h3>
265
+ <div id="detailsModalContent" class="overflow-x-auto">
266
+ </div>
267
+ </div>
268
+ </div>
269
+
270
+
271
+ <script>
272
+ // --- Adapted Task Data with Auto-Scoring Criteria ---
273
+ const adaptedTasks = [
274
+ // Vocabulary (Target: 20, Current: 15 + 5 new)
275
+ { type: "Vocabulary", prompt: "What does 'tranquil' mean?",
276
+ scoringOptions: {
277
+ "0": "Incorrect (e.g., definition is wrong, irrelevant, or no answer)",
278
+ "1": "Partially Correct (e.g., captures some aspect like 'quiet' but misses full scope like 'peaceful')",
279
+ "2": "Fully Correct (e.g., comprehensive and accurate, like 'calm, peaceful, serene')"
280
+ },
281
+ autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["calm", "peaceful", "serene", "quiet"], matchType: "any", countRequired: 2 }, { score: 1, keywords: ["calm", "peaceful", "serene", "quiet", "still"], matchType: "any", countRequired: 1 } ] }
282
+ },
283
+ { type: "Vocabulary", prompt: "Define 'ambiguous'.",
284
+ scoringOptions: {
285
+ "0": "Incorrect (e.g., definition is wrong or irrelevant)",
286
+ "1": "Partially Correct (e.g., mentions 'unclear' but not the idea of multiple meanings)",
287
+ "2": "Fully Correct (e.g., explains it means open to more than one interpretation or having a double meaning)"
288
+ },
289
+ autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["more than one interpretation", "double meaning", "unclear meaning", "not clear"], matchType: "any", countRequired: 1 }, { score: 1, keywords: ["unclear", "not specific", "vague"], matchType: "any", countRequired: 1 } ] }
290
+ },
291
+ { type: "Vocabulary", prompt: "What is 'empathy'?",
292
+ scoringOptions: {
293
+ "0": "Incorrect (e.g., confuses with sympathy or provides an unrelated definition)",
294
+ "1": "Partially Correct (e.g., mentions understanding feelings but not sharing them)",
295
+ "2": "Fully Correct (e.g., defines as the ability to understand AND share the feelings of another)"
296
+ },
297
+ autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["understand and share feelings", "feel what another feels"], matchType: "any", countRequired: 1 }, { score: 1, keywords: ["understand feelings", "share feelings", "feel for someone"], matchType: "any", countRequired: 1 } ] }
298
+ },
299
+ { type: "Vocabulary", prompt: "What does 'resilient' mean?", scoringOptions: { "0": "Incorrect (e.g., 'strong' without context of recovery)", "1": "Partially Correct (e.g., 'bounces back', 'tough')", "2": "Fully Correct (e.g., 'able to withstand or recover quickly from difficult conditions')" }, autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["recover quickly", "withstand difficult", "bounce back from adversity"], matchType: "any", countRequired: 1 }, { score: 1, keywords: ["strong", "tough", "recover", "bounce back"], matchType: "any", countRequired: 1 } ] } },
300
+ { type: "Vocabulary", prompt: "Define 'ubiquitous'.", scoringOptions: { "0": "Incorrect (e.g., 'rare')", "1": "Partially Correct (e.g., 'common', 'widespread')", "2": "Fully Correct (e.g., 'present, appearing, or found everywhere')" }, autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["everywhere", "present all places", "found all over"], matchType: "any", countRequired: 1 }, { score: 1, keywords: ["common", "widespread", "all over"], matchType: "any", countRequired: 1 } ] } },
301
+ { type: "Vocabulary", prompt: "What does 'ephemeral' mean?", scoringOptions: { "0": "Incorrect (e.g., 'long lasting')", "1": "Partially Correct (e.g., 'short', 'temporary')", "2": "Fully Correct (e.g., 'lasting for a very short time; fleeting')" }, autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["short time", "fleeting", "brief period", "transitory"], matchType: "any", countRequired: 1 }, { score: 1, keywords: ["short", "temporary", "not long"], matchType: "any", countRequired: 1 } ] } },
302
+ { type: "Vocabulary", prompt: "Define 'mitigate'.", scoringOptions: { "0": "Incorrect (e.g., 'to make worse')", "1": "Partially Correct (e.g., 'to lessen', 'to reduce')", "2": "Fully Correct (e.g., 'make less severe, serious, or painful; reduce the impact of')" }, autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["less severe", "less serious", "less painful", "reduce impact", "alleviate"], matchType: "any", countRequired: 1 }, { score: 1, keywords: ["lessen", "reduce", "ease", "soften"], matchType: "any", countRequired: 1 } ] } },
303
+ { type: "Vocabulary", prompt: "What is 'pragmatic'?", scoringOptions: { "0": "Incorrect (e.g., 'idealistic')", "1": "Partially Correct (e.g., 'practical', 'useful')", "2": "Fully Correct (e.g., 'dealing with things sensibly and realistically based on practical rather than theoretical considerations')" }, autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["practical", "sensible", "realistic", "results-oriented", "down-to-earth approach"], matchType: "any", countRequired: 1 }, { score: 1, keywords: ["useful", "sensible", "practical idea"], matchType: "any", countRequired: 1 } ] } },
304
+ { type: "Vocabulary", prompt: "Define 'anomaly'.", scoringOptions: { "0": "Incorrect (e.g., 'something normal')", "1": "Partially Correct (e.g., 'different', 'odd', 'strange')", "2": "Fully Correct (e.g., 'something that deviates from what is standard, normal, or expected; an irregularity')" }, autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["deviates standard", "not normal", "irregularity", "exception", "abnormality"], matchType: "any", countRequired: 1 }, { score: 1, keywords: ["different", "odd", "strange", "unusual"], matchType: "any", countRequired: 1 } ] } },
305
+ { type: "Vocabulary", prompt: "What does 'gregarious' mean?", scoringOptions: { "0": "Incorrect (e.g., 'shy', 'lonely')", "1": "Partially Correct (e.g., 'friendly', 'likes people')", "2": "Fully Correct (e.g., 'fond of company; sociable; outgoing')" }, autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["sociable", "fond of company", "outgoing", "enjoys groups"], matchType: "any", countRequired: 1 }, { score: 1, keywords: ["friendly", "likes people", "social"], matchType: "any", countRequired: 1 } ] } },
306
+ { type: "Vocabulary", prompt: "Define 'eloquent'.", scoringOptions: { "0": "Incorrect (e.g., 'quiet', 'hard to understand')", "1": "Partially Correct (e.g., 'well-spoken', 'good with words')", "2": "Fully Correct (e.g., 'fluent or persuasive in speaking or writing; clearly expressing or indicating something')" }, autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["fluent", "persuasive speaking", "expressive writing", "articulate", "vivid expression"], matchType: "any", countRequired: 1 }, { score: 1, keywords: ["well-spoken", "good with words", "clear speaker"], matchType: "any", countRequired: 1 } ] } },
307
+ { type: "Vocabulary", prompt: "What is 'lethargy'?", scoringOptions: { "0": "Incorrect (e.g., 'full of energy')", "1": "Partially Correct (e.g., 'tired', 'slow')", "2": "Fully Correct (e.g., 'a lack of energy and enthusiasm; sluggishness; apathy')" }, autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["lack of energy", "sluggishness", "tiredness", "apathy", "drowsiness", "listlessness"], matchType: "any", countRequired: 1 }, { score: 1, keywords: ["tired", "slow", "no energy", "sleepy"], matchType: "any", countRequired: 1 } ] } },
308
+ { type: "Vocabulary", prompt: "Define 'benevolent'.", scoringOptions: { "0": "Incorrect (e.g., 'mean', 'selfish')", "1": "Partially Correct (e.g., 'good', 'nice', 'helpful')", "2": "Fully Correct (e.g., 'well meaning and kindly; charitable')" }, autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["kind", "well meaning", "charitable", "generous", "goodwill"], matchType: "any", countRequired: 1 }, { score: 1, keywords: ["good", "nice", "helpful", "kindly"], matchType: "any", countRequired: 1 } ] } },
309
+ { type: "Vocabulary", prompt: "What does 'fortitude' mean?", scoringOptions: { "0": "Incorrect (e.g., 'weakness')", "1": "Partially Correct (e.g., 'strong', 'brave')", "2": "Fully Correct (e.g., 'courage in pain or adversity; mental strength and endurance')" }, autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["courage in pain", "strength adversity", "bravery facing difficulty", "endurance"], matchType: "any", countRequired: 1 }, { score: 1, keywords: ["strong", "brave", "tough", "courage"], matchType: "any", countRequired: 1 } ] } },
310
+ { type: "Vocabulary", prompt: "Define 'juxtaposition'.", scoringOptions: { "0": "Incorrect (e.g., 'separation')", "1": "Partially Correct (e.g., 'next to each other', 'compare')", "2": "Fully Correct (e.g., 'the fact of two things being seen or placed close together with contrasting effect')" }, autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["close together contrast", "side by side comparison", "placing different things together"], matchType: "any", countRequired: 1 }, { score: 1, keywords: ["next to each other", "compare", "contrast", "side by side"], matchType: "any", countRequired: 1 } ] } },
311
+ { type: "Vocabulary", prompt: "What does 'obfuscate' mean?", scoringOptions: { "0": "Incorrect (e.g., 'to clarify')", "1": "Partially Correct (e.g., 'to confuse', 'make unclear')", "2": "Fully Correct (e.g., 'to deliberately make something unclear or difficult to understand')" }, autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["make unclear", "confuse", "obscure", "difficult to understand", "bewilder"], matchType: "any", countRequired: 1 }, { score: 1, keywords: ["unclear", "confusing", "hide meaning"], matchType: "any", countRequired: 1 } ] } },
312
+ { type: "Vocabulary", prompt: "Define 'ephemeral'.", scoringOptions: { "0": "Incorrect (e.g., 'permanent')", "1": "Partially Correct (e.g., 'short-lived', 'temporary')", "2": "Fully Correct (e.g., 'lasting for a very short time; fleeting or transient')" }, autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["short time", "fleeting", "transient", "brief"], matchType: "any", countRequired: 1 }, { score: 1, keywords: ["short", "temporary", "not long"], matchType: "any", countRequired: 1 } ] } },
313
+ { type: "Vocabulary", prompt: "What is a 'panacea'?", scoringOptions: { "0": "Incorrect (e.g., 'a problem')", "1": "Partially Correct (e.g., 'a cure', 'a solution')", "2": "Fully Correct (e.g., 'a solution or remedy for all difficulties or diseases; a cure-all')" }, autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["remedy for all", "cure-all", "solution for everything"], matchType: "any", countRequired: 1 }, { score: 1, keywords: ["cure", "solution", "remedy"], matchType: "any", countRequired: 1 } ] } },
314
+ { type: "Vocabulary", prompt: "Define 'vicarious'.", scoringOptions: { "0": "Incorrect (e.g., 'direct experience')", "1": "Partially Correct (e.g., 'experienced through others')", "2": "Fully Correct (e.g., 'experienced in the imagination through the feelings or actions of another person')" }, autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["experienced through others", "indirect experience", "imagination of another"], matchType: "any", countRequired: 1 }, { score: 1, keywords: ["through others", "secondhand", "indirectly"], matchType: "any", countRequired: 1 } ] } },
315
+ { type: "Vocabulary", prompt: "What does 'cacophony' mean?", scoringOptions: { "0": "Incorrect (e.g., 'pleasant sound')", "1": "Partially Correct (e.g., 'loud noise', 'bad sound')", "2": "Fully Correct (e.g., 'a harsh, discordant mixture of sounds; jarring noise')" }, autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["harsh sounds", "discordant mixture", "jarring noise", "unpleasant noise"], matchType: "any", countRequired: 1 }, { score: 1, keywords: ["loud noise", "bad sound", "noise", "unpleasant"], matchType: "any", countRequired: 1 } ] } },
316
+
317
+ // Similarities (Target: 20, Current: 15 + 5 new)
318
+ { type: "Similarities", prompt: "In what way are 'apple' and 'banana' alike?",
319
+ scoringOptions: {
320
+ "0": "Incorrect (e.g., irrelevant comparison, or focuses on differences like 'one is red, one is yellow')",
321
+ "1": "Functional/Concrete (e.g., 'you eat them', 'they are food', 'they grow on trees', 'you buy them at a store')",
322
+ "2": "Categorical/Abstract (e.g., 'they are fruits', 'types of produce', 'both are plants')"
323
+ },
324
+ autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["fruit", "produce", "plant"], matchType: "any" }, { score: 1, keywords: ["eat", "food", "grow", "buy"], matchType: "any" } ] }
325
+ },
326
+ { type: "Similarities", prompt: "How are 'table' and 'chair' alike?",
327
+ scoringOptions: {
328
+ "0": "Incorrect (e.g., 'they are different shapes')",
329
+ "1": "Functional/Associative (e.g., 'you use them together', 'found in a dining room', 'made of wood')",
330
+ "2": "Categorical/Abstract (e.g., 'they are types of furniture', 'household items', 'man-made objects for a room')"
331
+ },
332
+ autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["furniture", "household items", "man-made objects for room"], matchType: "any" }, { score: 1, keywords: ["use together", "in a room", "made of wood", "sit at", "put things on"], matchType: "any" } ] }
333
+ },
334
+ { type: "Similarities", prompt: "In what way are 'anger' and 'joy' alike?", scoringOptions: { "0": "Incorrect (e.g., 'one is good, one is bad')", "1": "General (e.g., 'ways you feel', 'reactions')", "2": "Categorical/Abstract (e.g., 'they are emotions', 'types of feelings', 'psychological states')" }, autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["emotion", "feeling", "state of mind", "psychological state"], matchType: "any" }, { score: 1, keywords: ["how you feel", "reactions", "moods"], matchType: "any" } ] } },
335
+ { type: "Similarities", prompt: "How are 'north' and 'west' alike?", scoringOptions: { "0": "Incorrect (e.g., 'they are opposites')", "1": "Functional (e.g., 'on a compass', 'used for maps')", "2": "Categorical/Abstract (e.g., 'they are directions', 'cardinal points', 'points of reference')" }, autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["direction", "cardinal point", "navigation point", "reference point"], matchType: "any" }, { score: 1, keywords: ["compass", "map", "way to go", "location"], matchType: "any" } ] } },
336
+ { type: "Similarities", prompt: "In what way are 'poem' and 'statue' alike?", scoringOptions: { "0": "Incorrect (e.g., 'one is words, one is stone')", "1": "Functional/Less Abstract (e.g., 'things people make', 'you can look at them')", "2": "Categorical/Abstract (e.g., 'they are works of art', 'forms of expression', 'creative pieces')" }, autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["art", "expression", "creation", "creative work", "artistic piece"], matchType: "any" }, { score: 1, keywords: ["made by people", "display", "look at", "cultural object"], matchType: "any" } ] } },
337
+ { type: "Similarities", prompt: "How are 'liberty' and 'justice' alike?", scoringOptions: { "0": "Incorrect (e.g., 'they are not related')", "1": "General (e.g., 'good things', 'important concepts')", "2": "Categorical/Abstract (e.g., 'they are ideals', 'fundamental principles', 'social or political concepts', 'rights')" }, autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["ideal", "principle", "concept", "right", "societal value", "foundation of society"], matchType: "any" }, { score: 1, keywords: ["good thing", "important", "freedom related", "fairness related", "abstract idea"], matchType: "any" } ] } },
338
+ { type: "Similarities", prompt: "In what way are 'fly' (insect) and 'tree' alike?", scoringOptions: { "0": "Incorrect (e.g., 'one is small, one is big')", "1": "Superficial/Locational (e.g., 'found outside', 'in nature')", "2": "Categorical/Abstract (e.g., 'they are living things', 'organisms', 'part of the ecosystem')" }, autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["living thing", "organism", "part of nature", "biological entity", "ecosystem component"], matchType: "any" }, { score: 1, keywords: ["outside", "alive", "natural"], matchType: "any" } ] } },
339
+ { type: "Similarities", prompt: "How are 'newspaper' and 'television' alike?", scoringOptions: { "0": "Incorrect (e.g., 'one is paper, one is a screen')", "1": "Functional (e.g., 'they give news', 'you get information from them')", "2": "Categorical/Abstract (e.g., 'they are forms of media', 'sources of information', 'communication channels')" }, autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["media", "information source", "communication channel", "mass communication"], matchType: "any" }, { score: 1, keywords: ["news", "tell stories", "watch", "read", "inform"], matchType: "any" } ] } },
340
+ { type: "Similarities", prompt: "In what way are 'mountain' and 'lake' alike?", scoringOptions: { "0": "Incorrect (e.g., 'one is high, one is low')", "1": "Locational/Descriptive (e.g., 'found in nature', 'part of the landscape')", "2": "Categorical/Abstract (e.g., 'they are geographical features', 'natural landforms', 'parts of the Earth’s surface')" }, autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["geographical feature", "landform", "natural formation", "earth surface feature"], matchType: "any" }, { score: 1, keywords: ["nature", "outdoors", "scenery", "landscape element"], matchType: "any" } ] } },
341
+ { type: "Similarities", prompt: "How are 'work' and 'play' alike?", scoringOptions: { "0": "Incorrect (e.g., 'one is fun, one is not')", "1": "General (e.g., 'things you do', 'take up time')", "2": "Categorical/Abstract (e.g., 'they are activities', 'parts of life', 'ways of expending energy')" }, autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["activit", "part of life", "things people do", "expend energy", "human behavior"], matchType: "any" }, { score: 1, keywords: ["use energy", "time consuming", "actions"], matchType: "any" } ] } },
342
+ { type: "Similarities", prompt: "In what way are 'train' and 'bicycle' alike?", scoringOptions: { "0": "Incorrect (e.g., 'one is big, one is small')", "1": "Functional (e.g., 'they move people', 'they have wheels')", "2": "Categorical/Abstract (e.g., 'they are forms of transportation', 'types of vehicles', 'modes of travel')" }, autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["transportation", "vehicle", "mode of travel", "way to get places"], matchType: "any" }, { score: 1, keywords: ["move people", "wheels", "travel", "get around"], matchType: "any" } ] } },
343
+ { type: "Similarities", prompt: "How are 'optimist' and 'pessimist' alike?", scoringOptions: { "0": "Incorrect (e.g., 'they are opposites')", "1": "General (e.g., 'ways of thinking', 'types of people')", "2": "Categorical/Abstract (e.g., 'they are outlooks on life', 'types of perspectives', 'attitudinal types', 'personality traits')" }, autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["outlook on life", "perspective", "attitude type", "personality trait", "viewpoint on future"], matchType: "any" }, { score: 1, keywords: ["viewpoint", "way of thinking", "person type", "attitude"], matchType: "any" } ] } },
344
+ { type: "Similarities", prompt: "In what way are 'painting' and 'music' alike?", scoringOptions: { "0": "Incorrect (e.g., 'one you see, one you hear')", "1": "Functional/General (e.g., 'they are enjoyable', 'made by artists')", "2": "Categorical/Abstract (e.g., 'they are art forms', 'forms of creative expression', 'cultural products')" }, autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["art form", "expression", "creative", "cultural product", "aesthetic creation"], matchType: "any" }, { score: 1, keywords: ["enjoyable", "made by artists", "cultural", "creative output"], matchType: "any" } ] } },
345
+ { type: "Similarities", prompt: "How are 'inch' and 'mile' alike?", scoringOptions: { "0": "Incorrect (e.g., 'one is short, one is long' - focuses on difference)", "1": "General (e.g., 'they are measurements', 'related to length')", "2": "Categorical/Abstract (e.g., 'they are units of measurement for distance/length', 'standard measures')" }, autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["unit of measurement", "measure distance", "length unit", "standard measure"], matchType: "any" }, { score: 1, keywords: ["measurement", "length", "distance", "unit"], matchType: "any" } ] } },
346
+ { type: "Similarities", prompt: "In what way are 'seed' and 'egg' alike?", scoringOptions: { "0": "Incorrect (e.g., 'one is from plant, one from animal' - focuses on difference)", "1": "Functional (e.g., 'they grow into something', 'they produce life')", "2": "Categorical/Abstract (e.g., 'they are beginnings of life', 'contain potential for a new organism', 'reproductive units')" }, autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["beginning of life", "potential for growth", "reproductive unit", "start of new organism", "embryonic stage"], matchType: "any" }, { score: 1, keywords: ["grows into something", "produces life", "contains life", "start of something"], matchType: "any" } ] } },
347
+ { type: "Similarities", prompt: "How are 'river' and 'road' alike?", scoringOptions: { "0": "Incorrect", "1": "Functional/Descriptive (e.g., 'long and winding', 'things travel on them')", "2": "Categorical/Abstract (e.g., 'pathways', 'routes for travel/transport', 'conduits')" }, autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["pathway", "route", "conduit", "channel for movement"], matchType: "any" }, { score: 1, keywords: ["long", "travel on", "flow", "go places"], matchType: "any" } ] } },
348
+ { type: "Similarities", prompt: "In what way are 'telescope' and 'microscope' alike?", scoringOptions: { "0": "Incorrect", "1": "Functional (e.g., 'help you see things', 'look through them')", "2": "Categorical/Abstract (e.g., 'optical instruments', 'tools for viewing', 'magnification devices')" }, autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["optical instrument", "viewing tool", "magnification device", "scientific instrument"], matchType: "any" }, { score: 1, keywords: ["see things", "look through", "magnify", "instrument"], matchType: "any" } ] } },
349
+ { type: "Similarities", prompt: "How are 'steam' and 'ice' alike?", scoringOptions: { "0": "Incorrect", "1": "General (e.g., 'related to temperature', 'can change')", "2": "Categorical/Abstract (e.g., 'states of water', 'forms of H2O', 'phases of matter')" }, autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["state of water", "form of h2o", "phase of matter", "water"], matchType: "any" }, { score: 1, keywords: ["temperature", "water related", "can change form"], matchType: "any" } ] } },
350
+ { type: "Similarities", prompt: "In what way are 'sculptor' and 'author' alike?", scoringOptions: { "0": "Incorrect", "1": "General (e.g., 'they make things', 'people')", "2": "Categorical/Abstract (e.g., 'creators', 'artists', 'producers of creative works')" }, autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["creator", "artist", "producer of creative work", "artisan"], matchType: "any" }, { score: 1, keywords: ["make things", "people", "creative job", "express ideas"], matchType: "any" } ] } },
351
+ { type: "Similarities", prompt: "How are 'first' and 'last' alike?", scoringOptions: { "0": "Incorrect", "1": "General (e.g., 'related to order', 'positions')", "2": "Categorical/Abstract (e.g., 'ordinal positions', 'extremes of a sequence', 'boundary markers')" }, autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["ordinal position", "extreme of sequence", "boundary marker", "position in series"], matchType: "any" }, { score: 1, keywords: ["order", "position", "start and end", "sequence related"], matchType: "any" } ] } },
352
+
353
+ // Information (Target: 20, Current: 15 + 5 new)
354
+ { type: "Information", prompt: "How many days are in a normal year (not a leap year)?",
355
+ scoringOptions: {
356
+ "0": "Incorrect (e.g., wrong number like 356 or 30)",
357
+ "1": "Correct (states 365)"
358
+ },
359
+ autoScoringCriteria: { type: "exact", answer: "365" }
360
+ },
361
+ { type: "Information", prompt: "What is the boiling point of water in degrees Celsius?",
362
+ scoringOptions: {
363
+ "0": "Incorrect (e.g., wrong temperature like 0 or 212, or wrong units if specified)",
364
+ "1": "Correct (states 100, Celsius implied or stated)"
365
+ },
366
+ autoScoringCriteria: { type: "exact", answer: "100" }
367
+ },
368
+ { type: "Information", prompt: "Who wrote the play 'Hamlet'?", scoringOptions: { "0": "Incorrect (e.g., 'Dickens')", "1": "Correct (William Shakespeare)" }, autoScoringCriteria: { type: "exact", answer: "william shakespeare" } },
369
+ { type: "Information", prompt: "What is the largest ocean on Earth?", scoringOptions: { "0": "Incorrect (e.g., 'Atlantic')", "1": "Correct (Pacific Ocean or Pacific)" }, autoScoringCriteria: { type: "exact", answer: "pacific ocean" } },
370
+ { type: "Information", prompt: "What gas do plants primarily absorb from the atmosphere for photosynthesis?", scoringOptions: { "0": "Incorrect (e.g., 'Oxygen')", "1": "Correct (Carbon Dioxide or CO2)" }, autoScoringCriteria: { type: "exact", answer: "carbon dioxide" } },
371
+ { type: "Information", prompt: "What is the capital of Japan?", scoringOptions: { "0": "Incorrect (e.g., 'Kyoto')", "1": "Correct (Tokyo)" }, autoScoringCriteria: { type: "exact", answer: "tokyo" } },
372
+ { type: "Information", prompt: "Who is credited with the theory of general relativity?", scoringOptions: { "0": "Incorrect (e.g., 'Newton')", "1": "Correct (Albert Einstein or Einstein)" }, autoScoringCriteria: { type: "exact", answer: "albert einstein" } },
373
+ { type: "Information", prompt: "What continent is Brazil on?", scoringOptions: { "0": "Incorrect (e.g., 'Africa')", "1": "Correct (South America)" }, autoScoringCriteria: { type: "exact", answer: "south america" } },
374
+ { type: "Information", prompt: "What is the chemical symbol for water?", scoringOptions: { "0": "Incorrect (e.g., 'HO')", "1": "Correct (H2O or h2o)" }, autoScoringCriteria: { type: "exact", answer: "h2o" } },
375
+ { type: "Information", prompt: "How many planets are currently recognized in our Solar System by the IAU?", scoringOptions: { "0": "Incorrect (e.g., '9' or '7')", "1": "Correct (8)" }, autoScoringCriteria: { type: "exact", answer: "8" } },
376
+ { type: "Information", prompt: "What is the currency of the United Kingdom?", scoringOptions: { "0": "Incorrect (e.g., 'Euro', 'Dollar')", "1": "Correct (Pound Sterling or Pound)" }, autoScoringCriteria: { type: "exact", answer: "pound sterling" } }, // also accept "pound"
377
+ { type: "Information", prompt: "What is the tallest mountain in the world (above sea level)?", scoringOptions: { "0": "Incorrect (e.g., 'K2' without context or other mountain)", "1": "Correct (Mount Everest or Everest)" }, autoScoringCriteria: { type: "exact", answer: "mount everest" } },
378
+ { type: "Information", prompt: "In what year did World War II end?", scoringOptions: { "0": "Incorrect (e.g., '1944', '1918')", "1": "Correct (1945)" }, autoScoringCriteria: { type: "exact", answer: "1945" } },
379
+ { type: "Information", prompt: "What is the main function of the heart?", scoringOptions: { "0": "Incorrect (e.g., 'thinking', 'breathing')", "1": "Correct (Pumps or circulates blood)" }, autoScoringCriteria: { type: "keywords", rules: [{ score: 1, keywords: ["pump blood", "circulate blood", "distribute blood"], matchType: "any" }] } },
380
+ { type: "Information", prompt: "What is the primary language spoken in France?", scoringOptions: { "0": "Incorrect (e.g., 'Spanish', 'English')", "1": "Correct (French)" }, autoScoringCriteria: { type: "exact", answer: "french" } },
381
+ { type: "Information", prompt: "What is the largest desert in the world (by area, including polar deserts)?", scoringOptions: { "0": "Incorrect (e.g., 'Sahara' if not specifying hot desert)", "1": "Correct (Antarctic Polar Desert or Antarctica)" }, autoScoringCriteria: { type: "exact", answer: "antarctic polar desert" } }, // Also accept "antarctica"
382
+ { type: "Information", prompt: "Who painted the Mona Lisa?", scoringOptions: { "0": "Incorrect", "1": "Correct (Leonardo da Vinci or da Vinci)" }, autoScoringCriteria: { type: "exact", answer: "leonardo da vinci" } },
383
+ { type: "Information", prompt: "How many sides does a hexagon have?", scoringOptions: { "0": "Incorrect", "1": "Correct (6)" }, autoScoringCriteria: { type: "exact", answer: "6" } },
384
+ { type: "Information", prompt: "What is the speed of light in a vacuum (approximately, in km/s)?", scoringOptions: { "0": "Incorrect", "1": "Correct (around 300,000 km/s)" }, autoScoringCriteria: { type: "keywords", rules: [{ score: 1, keywords: ["300000", "299792"], matchType: "any" }] } }, // Accept variations
385
+ { type: "Information", prompt: "What force keeps planets in orbit around the Sun?", scoringOptions: { "0": "Incorrect", "1": "Correct (Gravity or Gravitational Force)" }, autoScoringCriteria: { type: "exact", answer: "gravity" } },
386
+
387
+
388
+ // Arithmetic (Target: 20, Current: 15 + 5 new)
389
+ { type: "Arithmetic", prompt: "If pencils cost $0.50 each, how many can you buy for $4.00? Respond with only the number.",
390
+ scoringOptions: {
391
+ "0": "Incorrect (wrong numerical answer e.g., 4, 2)",
392
+ "1": "Correct (provides the number 8)"
393
+ },
394
+ autoScoringCriteria: { type: "exact", answer: "8" }
395
+ },
396
+ { type: "Arithmetic", prompt: "A train travels 50 miles in 1 hour. How far will it travel in 3.5 hours? Respond with the number of miles.",
397
+ scoringOptions: {
398
+ "0": "Incorrect (wrong numerical answer e.g., 150, 53.5)",
399
+ "1": "Correct (provides the number 175)"
400
+ },
401
+ autoScoringCriteria: { type: "exact", answer: "175" }
402
+ },
403
+ { type: "Arithmetic", prompt: "If you buy 3 apples at $0.75 each, how much change do you get from $5.00? Respond with the amount, e.g., $X.XX or X.XX.", scoringOptions: { "0": "Incorrect (e.g., $2.25, $3.75)", "1": "Correct ($2.75 or 2.75)" }, autoScoringCriteria: { type: "exact", answer: "2.75" } },
404
+ { type: "Arithmetic", prompt: "What is 15% of 200? Respond with only the number.", scoringOptions: { "0": "Incorrect (e.g., 15, 20)", "1": "Correct (30)" }, autoScoringCriteria: { type: "exact", answer: "30" } },
405
+ { type: "Arithmetic", prompt: "A recipe needs 2 cups of flour. You only have a 1/4 cup measure. How many times do you need to fill it? Respond with only the number.", scoringOptions: { "0": "Incorrect (e.g., 2, 4)", "1": "Correct (8)" }, autoScoringCriteria: { type: "exact", answer: "8" } },
406
+ { type: "Arithmetic", prompt: "If a car travels 300 kilometers on 30 liters of fuel, how many kilometers per liter does it average? Respond with only the number.", scoringOptions: { "0": "Incorrect (e.g., 30, 330)", "1": "Correct (10)" }, autoScoringCriteria: { type: "exact", answer: "10" } },
407
+ { type: "Arithmetic", prompt: "A meeting starts at 9:15 AM and lasts for 2 hours and 30 minutes. What time does it end? Respond in HH:MM AM/PM format (e.g., 11:45 AM).", scoringOptions: { "0": "Incorrect (e.g., 11:30 AM, 12:45 PM)", "1": "Correct (11:45 AM)" }, autoScoringCriteria: { type: "exact", answer: "11:45 am" } },
408
+ { type: "Arithmetic", prompt: "If 4 people share a $56 dinner bill equally, how much does each person pay? Respond with the amount, e.g., $XX or XX.", scoringOptions: { "0": "Incorrect (e.g., $12, $20)", "1": "Correct ($14 or 14)" }, autoScoringCriteria: { type: "exact", answer: "14" } },
409
+ { type: "Arithmetic", prompt: "What number is halfway between 10 and 50? Respond with only the number.", scoringOptions: { "0": "Incorrect (e.g., 20, 25, 40)", "1": "Correct (30)" }, autoScoringCriteria: { type: "exact", answer: "30" } },
410
+ { type: "Arithmetic", prompt: "If an item costs $80 and is discounted by 25%, what is the final price? Respond with the amount, e.g., $XX or XX.", scoringOptions: { "0": "Incorrect (e.g., $20, $105)", "1": "Correct ($60 or 60)" }, autoScoringCriteria: { type: "exact", answer: "60" } },
411
+ { type: "Arithmetic", prompt: "What is 7 multiplied by 9? Respond with only the number.", scoringOptions: { "0": "Incorrect (e.g., 16, 79)", "1": "Correct (63)" }, autoScoringCriteria: { type: "exact", answer: "63" } },
412
+ { type: "Arithmetic", prompt: "If you have 12 cookies and eat 3, how many are left? Respond with only the number.", scoringOptions: { "0": "Incorrect (e.g., 3, 15)", "1": "Correct (9)" }, autoScoringCriteria: { type: "exact", answer: "9" } },
413
+ { type: "Arithmetic", prompt: "A farmer has 15 sheep and sells 7. How many sheep does he have left? Respond with only the number.", scoringOptions: { "0": "Incorrect (e.g., 7, 22)", "1": "Correct (8)" }, autoScoringCriteria: { type: "exact", answer: "8" } },
414
+ { type: "Arithmetic", prompt: "How many sides does a triangle have? Respond with only the number.", scoringOptions: { "0": "Incorrect (e.g., 4, 2)", "1": "Correct (3)" }, autoScoringCriteria: { type: "exact", answer: "3" } },
415
+ { type: "Arithmetic", prompt: "What is 50 divided by 5? Respond with only the number.", scoringOptions: { "0": "Incorrect (e.g., 5, 250)", "1": "Correct (10)" }, autoScoringCriteria: { type: "exact", answer: "10" } },
416
+ { type: "Arithmetic", prompt: "If a book has 200 pages and you read 25 pages a day, how many days will it take to finish the book? Respond with only the number.", scoringOptions: { "0": "Incorrect", "1": "Correct (8)" }, autoScoringCriteria: { type: "exact", answer: "8" } },
417
+ { type: "Arithmetic", prompt: "What is the sum of 15, 25, and 10? Respond with only the number.", scoringOptions: { "0": "Incorrect", "1": "Correct (50)" }, autoScoringCriteria: { type: "exact", answer: "50" } },
418
+ { type: "Arithmetic", prompt: "If a movie is 120 minutes long, how many hours is that? Respond with only the number.", scoringOptions: { "0": "Incorrect", "1": "Correct (2)" }, autoScoringCriteria: { type: "exact", answer: "2" } },
419
+ { type: "Arithmetic", prompt: "You buy an item for $7.50 and pay with a $10 bill. How much change do you receive? Respond with amount, e.g., $X.XX or X.XX.", scoringOptions: { "0": "Incorrect", "1": "Correct ($2.50 or 2.50)" }, autoScoringCriteria: { type: "exact", answer: "2.50" } },
420
+ { type: "Arithmetic", prompt: "What is one third of 90? Respond with only the number.", scoringOptions: { "0": "Incorrect", "1": "Correct (30)" }, autoScoringCriteria: { type: "exact", answer: "30" } },
421
+
422
+ // Comprehension (Target: 20, Current: 15 + 5 new)
423
+ { type: "Comprehension", prompt: "Why do people need licenses to drive cars?",
424
+ scoringOptions: {
425
+ "0": "Incorrect/Irrelevant (e.g., 'because they are cars', 'to show off')",
426
+ "1": "Partial/Simple (e.g., 'it's the law', 'so you don't crash', 'to drive')",
427
+ "2": "Comprehensive (e.g., 'to ensure drivers know traffic laws, can operate a vehicle safely, and to protect public safety and property')"
428
+ },
429
+ autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["safety", "rules", "operate vehicle", "protect", "ensure competence", "public safety", "traffic laws", "responsibility"], matchType: "any", countRequired: 2 }, { score: 1, keywords: ["law", "permission", "safe", "rules", "crash", "drive legally"], matchType: "any", countRequired: 1 } ] }
430
+ },
431
+ { type: "Comprehension", prompt: "Why should promises be kept?",
432
+ scoringOptions: {
433
+ "0": "Irrelevant/Incorrect (e.g., 'you don't have to if you don't want to')",
434
+ "1": "Simple/Superficial (e.g., 'it's nice', 'to be good', 'so people like you')",
435
+ "2": "Comprehensive (e.g., 'to build trust, maintain relationships, show reliability and integrity, because it's a commitment that affects others')"
436
+ },
437
+ autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["trust", "reliability", "integrity", "relationships", "respect", "commitment", "dependability", "honoring word"], matchType: "any", countRequired: 2 }, { score: 1, keywords: ["good", "nice", "don't lie", "important", "keep word"], matchType: "any", countRequired: 1 } ] }
438
+ },
439
+ { type: "Comprehension", prompt: "Why is it generally important to tell the truth?", scoringOptions: { "0": "Irrelevant (e.g., 'lies are fun')", "1": "Simple (e.g., 'so you don't get in trouble', 'it's good')", "2": "Comprehensive (e.g., 'builds trust, essential for communication and relationships, avoids complications and harm, reflects integrity')" }, autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["trust", "honesty", "communication", "relationships", "avoid complications", "integrity", "foundation of society"], matchType: "any", countRequired: 2 }, { score: 1, keywords: ["good", "right thing", "not get in trouble", "clear conscience"], matchType: "any", countRequired: 1 } ] } },
440
+ { type: "Comprehension", prompt: "Why do we cook food?", scoringOptions: { "0": "Irrelevant (e.g., 'because we are hungry' - doesn't explain cooking itself)", "1": "Partial (e.g., 'tastes better', 'kills germs')", "2": "Comprehensive (e.g., 'to make it safe by killing harmful bacteria, improve digestibility, enhance flavor and texture, make nutrients more available')" }, autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["safety", "kill bacteria", "digestibility", "flavor", "texture", "make edible", "nutrients available", "easier to chew"], matchType: "any", countRequired: 2 }, { score: 1, keywords: ["taste better", "kill germs", "easier to eat", "soften"], matchType: "any", countRequired: 1 } ] } },
441
+ { type: "Comprehension", prompt: "What is the advantage of using libraries?", scoringOptions: { "0": "Irrelevant (e.g., 'they are old buildings')", "1": "Partial (e.g., 'get books', 'read', 'quiet place')", "2": "Comprehensive (e.g., 'free access to a wide range of books, resources, internet, and information; promotes learning and literacy; provides a community space')" }, autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["free access", "resources", "books", "learning", "knowledge", "community", "information", "literacy"], matchType: "any", countRequired: 2 }, { score: 1, keywords: ["get books", "read", "study", "quiet place", "borrow books"], matchType: "any", countRequired: 1 } ] } },
442
+ { type: "Comprehension", prompt: "Why do buildings have foundations?", scoringOptions: { "0": "Irrelevant (e.g., 'to look nice from the bottom')", "1": "Simple (e.g., 'holds it up', 'strong base')", "2": "Comprehensive (e.g., 'to provide a stable and strong base, distribute the building’s weight evenly to the ground, and prevent settling or collapse')" }, autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["support", "stability", "distribute weight", "prevent collapse", "solid base", "anchor building", "prevent settling"], matchType: "any", countRequired: 2 }, { score: 1, keywords: ["hold up", "strong base", "keep from falling", "bottom part"], matchType: "any", countRequired: 1 } ] } },
443
+ { type: "Comprehension", prompt: "Why is it important to vote in democratic elections?", scoringOptions: { "0": "Irrelevant (e.g., 'it's a holiday')", "1": "Simple (e.g., 'choose leaders', 'your say')", "2": "Comprehensive (e.g., 'to participate in government, express preferences for policies and leaders, hold officials accountable, and uphold civic duty in a democracy')" }, autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["choose leaders", "participate government", "voice heard", "accountability", "civic duty", "representation", "democracy", "influence policy"], matchType: "any", countRequired: 2 }, { score: 1, keywords: ["pick president", "your say", "important", "select government"], matchType: "any", countRequired: 1 } ] } },
444
+ { type: "Comprehension", prompt: "Why do ships have anchors?", scoringOptions: { "0": "Irrelevant (e.g., 'to make them heavy')", "1": "Simple (e.g., 'to stop', 'stay still')", "2": "Comprehensive (e.g., 'to hold the vessel in a desired position against wind, current, or tide, preventing it from drifting')" }, autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["hold in place", "prevent drifting", "stay stationary", "secure vessel", "against current wind tide", "moor"], matchType: "any", countRequired: 2 }, { score: 1, keywords: ["stop", "stay still", "not move", "park ship"], matchType: "any", countRequired: 1 } ] } },
445
+ { type: "Comprehension", prompt: "Why should children go to school?", scoringOptions: { "0": "Irrelevant (e.g., 'to get away from home')", "1": "Simple (e.g., 'to learn stuff', 'make friends')", "2": "Comprehensive (e.g., 'to gain knowledge and skills, develop socially and emotionally, prepare for future education and careers, and become informed citizens')" }, autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["learn", "education", "knowledge", "skills", "socialization", "prepare future", "citizenship", "development"], matchType: "any", countRequired: 2 }, { score: 1, keywords: ["get smart", "read write", "make friends", "learn things"], matchType: "any", countRequired: 1 } ] } },
446
+ { type: "Comprehension", prompt: "Why do we pay taxes?", scoringOptions: { "0": "Irrelevant (e.g., 'because the government is greedy')", "1": "Simple (e.g., 'government needs money', 'pay for things')", "2": "Comprehensive (e.g., 'to fund public services and infrastructure like roads, schools, police, healthcare, defense, and other government operations essential for society')" }, autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["fund public services", "government spending", "roads", "schools", "police", "healthcare", "defense", "community needs", "infrastructure", "societal benefit"], matchType: "any", countRequired: 2 }, { score: 1, keywords: ["government money", "pay for things", "law", "public good"], matchType: "any", countRequired: 1 } ] } },
447
+ { type: "Comprehension", prompt: "Why is recycling beneficial?", scoringOptions: { "0": "Irrelevant (e.g., 'it's a lot of work')", "1": "Simple (e.g., 'helps the planet', 'less trash')", "2": "Comprehensive (e.g., 'conserves natural resources, reduces landfill waste, saves energy, reduces pollution, and can create jobs')" }, autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["conserve resources", "reduce waste", "landfill", "save energy", "pollution", "environment", "sustainability", "reuse materials"], matchType: "any", countRequired: 2 }, { score: 1, keywords: ["helps earth", "less trash", "reuse", "good for nature"], matchType: "any", countRequired: 1 } ] } },
448
+ { type: "Comprehension", prompt: "What does the proverb 'Look before you leap' mean?", scoringOptions: { "0": "Incorrect/Literal (e.g., 'check the ground before jumping')", "1": "Partial (e.g., 'be careful', 'think first')", "2": "Comprehensive (e.g., 'it means one should consider the possible consequences or dangers before taking an action or making a decision')" }, autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["consider consequences", "think before acting", "plan ahead", "avoid rash decisions", "assess risk"], matchType: "any", countRequired: 1 }, { score: 1, keywords: ["be careful", "don't rush", "think first", "check first"], matchType: "any", countRequired: 1 } ] } },
449
+ { type: "Comprehension", prompt: "Why do we have traffic lights?", scoringOptions: { "0": "Irrelevant (e.g., 'they look pretty')", "1": "Simple (e.g., 'stop cars', 'tell when to go')", "2": "Comprehensive (e.g., 'to control the flow of traffic at intersections, prevent accidents, and ensure orderly movement for vehicles and pedestrians')" }, autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["control traffic flow", "prevent accidents", "order intersections", "safety", "regulate traffic", "pedestrian safety"], matchType: "any", countRequired: 2 }, { score: 1, keywords: ["stop cars", "avoid crashes", "tell when to go", "direct traffic"], matchType: "any", countRequired: 1 } ] } },
450
+ { type: "Comprehension", prompt: "What is the purpose of a preface in a book?", scoringOptions: { "0": "Irrelevant (e.g., 'the last page')", "1": "Simple (e.g., 'before the book starts', 'intro')", "2": "Comprehensive (e.g., 'an introduction by the author or another person, explaining the book’s subject, purpose, scope, or how it was written')" }, autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["introduction", "author's note", "background information", "purpose of book", "guide reader", "scope", "context"], matchType: "any", countRequired: 1 }, { score: 1, keywords: ["before the book", "intro", "about the book", "start of book"], matchType: "any", countRequired: 1 } ] } },
451
+ { type: "Comprehension", prompt: "Why is exercise important for health?", scoringOptions: { "0": "Irrelevant (e.g., 'it makes you tired')", "1": "Simple (e.g., 'stay fit', 'be healthy', 'good for body')", "2": "Comprehensive (e.g., 'maintains physical fitness, strengthens muscles and bones, improves cardiovascular health, helps manage weight, boosts mental well-being, and reduces risk of chronic diseases')" }, autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["physical fitness", "strong muscles bones", "heart health", "weight management", "mental well-being", "prevent disease", "cardiovascular", "reduce risk"], matchType: "any", countRequired: 2 }, { score: 1, keywords: ["stay fit", "be healthy", "good for body", "strong", "lose weight"], matchType: "any", countRequired: 1 } ] } },
452
+ { type: "Comprehension", prompt: "Why do birds migrate?", scoringOptions: { "0": "Irrelevant", "1": "Partial (e.g., 'to go south', 'for warmth')", "2": "Comprehensive (e.g., 'to find better food resources, suitable breeding grounds, or more favorable climate conditions, often seasonally')" }, autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["food resources", "breeding grounds", "climate conditions", "seasonal movement", "survival"], matchType: "any", countRequired: 2 }, { score: 1, keywords: ["go south", "warmth", "food", "weather", "seasons"], matchType: "any", countRequired: 1 } ] } },
453
+ { type: "Comprehension", prompt: "What is the main purpose of a country's constitution?", scoringOptions: { "0": "Irrelevant", "1": "Partial (e.g., 'rules', 'laws')", "2": "Comprehensive (e.g., 'to establish the fundamental principles, laws, and structure of government, and to define the rights and duties of its citizens')" }, autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["fundamental principles", "structure of government", "define rights", "supreme law", "framework for governance"], matchType: "any", countRequired: 2 }, { score: 1, keywords: ["rules", "laws", "government guide", "rights"], matchType: "any", countRequired: 1 } ] } },
454
+ { type: "Comprehension", prompt: "Why is it important to get enough sleep?", scoringOptions: { "0": "Irrelevant", "1": "Partial (e.g., 'so you're not tired', 'to rest')", "2": "Comprehensive (e.g., 'for physical and mental restoration, cognitive functions like memory and concentration, immune system health, and overall well-being')" }, autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["physical restoration", "mental restoration", "cognitive functions", "immune system", "memory consolidation", "concentration"], matchType: "any", countRequired: 2 }, { score: 1, keywords: ["not tired", "rest", "energy", "feel good", "think better"], matchType: "any", countRequired: 1 } ] } },
455
+ { type: "Comprehension", prompt: "What does the saying 'A stitch in time saves nine' mean?", scoringOptions: { "0": "Incorrect/Literal", "1": "Partial (e.g., 'fix things quickly')", "2": "Comprehensive (e.g., 'addressing a small problem promptly can prevent it from becoming a much larger, more difficult problem later')" }, autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["address problem promptly", "prevent larger problem", "early action saves effort", "timely intervention"], matchType: "any", countRequired: 1 }, { score: 1, keywords: ["fix quickly", "don't wait", "solve early", "save trouble"], matchType: "any", countRequired: 1 } ] } },
456
+ { type: "Comprehension", prompt: "Why do we have different time zones around the world?", scoringOptions: { "0": "Irrelevant", "1": "Partial (e.g., 'sun is different', 'day and night')", "2": "Comprehensive (e.g., 'due to Earth's rotation, different parts of the world experience daylight at different times, so time zones standardize local times relative to the sun's position')" }, autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["earth rotation", "daylight different times", "standardize local time", "sun position", "longitude"], matchType: "any", countRequired: 2 }, { score: 1, keywords: ["sun different places", "day and night", "time difference", "earth spins"], matchType: "any", countRequired: 1 } ] } },
457
+ ]; // Total ~100 questions
458
+
459
+ // --- DOM Elements ---
460
+ const configArea = document.getElementById('configArea');
461
+ const baseUrlInput = document.getElementById('baseUrl');
462
+ const apiKeyInput = document.getElementById('apiKey');
463
+ const modelNameInput = document.getElementById('modelName');
464
+ const enableAutoScoringCheckbox = document.getElementById('enableAutoScoring');
465
+ const configErrorEl = document.getElementById('configError');
466
+ const startControl = document.getElementById('startControl');
467
+ const startButton = document.getElementById('startButton');
468
+ const testArea = document.getElementById('testArea');
469
+ const progressBar = document.getElementById('progressBar');
470
+ const progressText = document.getElementById('progressText');
471
+ const resultsArea = document.getElementById('resultsArea');
472
+ const taskNumberEl = document.getElementById('taskNumber');
473
+ const taskTypeEl = document.getElementById('taskType');
474
+ const taskPromptEl = document.getElementById('taskPrompt');
475
+ const aiResponseEl = document.getElementById('aiResponse');
476
+ const loadingIndicatorEl = document.getElementById('loadingIndicator');
477
+ const apiErrorEl = document.getElementById('apiError');
478
+ const scoringInputEl = document.getElementById('scoringInput');
479
+ const scoringInstructionsEl = document.getElementById('scoringInstructions');
480
+ const suggestedScoreDisplayEl = document.getElementById('suggestedScoreDisplay');
481
+ const nextButton = document.getElementById('nextButton');
482
+ const finalScoreEl = document.getElementById('finalScore');
483
+ const userNameInput = document.getElementById('userName');
484
+ const saveScoreButton = document.getElementById('saveScoreButton');
485
+ const saveMessageEl = document.getElementById('saveMessage');
486
+ const leaderboardArea = document.getElementById('leaderboardArea');
487
+ const leaderboardContentEl = document.getElementById('leaderboardContent');
488
+ const clearLeaderboardButton = document.getElementById('clearLeaderboardButton');
489
+ const detailsModal = document.getElementById('detailsModal');
490
+ const detailsModalTitle = document.getElementById('detailsModalTitle');
491
+ const detailsModalContent = document.getElementById('detailsModalContent');
492
+ const modalCloseButton = document.getElementById('modalCloseButton');
493
+
494
+
495
+ // --- State Variables ---
496
+ let currentTaskIndex = 0;
497
+ let detailedScores = [];
498
+ let currentTotalScore = 0;
499
+ let taskStartTime = 0;
500
+ let autoScoringEnabledGlobal = false;
501
+ let apiConfig = { baseUrl: '', apiKey: '', model: '' };
502
+ const LEADERBOARD_KEY = 'aiTestLeaderboard_v4';
503
+ const AUTO_ADVANCE_DELAY = 2500;
504
+
505
+ // --- Functions ---
506
+
507
+ function updateProgress() {
508
+ const percentage = adaptedTasks.length > 0 ? ((currentTaskIndex + 1) / adaptedTasks.length) * 100 : 0;
509
+ progressBar.style.width = `${percentage}%`;
510
+ progressText.textContent = `Task ${currentTaskIndex + 1} / ${adaptedTasks.length}`;
511
+ }
512
+
513
+ function displayTask(index) {
514
+ if (index >= adaptedTasks.length) {
515
+ showResults();
516
+ return;
517
+ }
518
+ const task = adaptedTasks[index];
519
+ taskNumberEl.textContent = index + 1;
520
+ taskTypeEl.textContent = task.type;
521
+ taskPromptEl.textContent = task.prompt;
522
+
523
+ aiResponseEl.innerHTML = `<span class="text-gray-500 italic">Waiting for API response...</span>`;
524
+ scoringInputEl.innerHTML = `<span class="text-gray-500 italic">Score after response is received.</span>`;
525
+ suggestedScoreDisplayEl.classList.add('hidden');
526
+ apiErrorEl.classList.add('hidden');
527
+ apiErrorEl.textContent = '';
528
+ nextButton.disabled = true;
529
+ loadingIndicatorEl.classList.add('hidden');
530
+ taskStartTime = 0;
531
+
532
+ updateProgress();
533
+ callActualApi(task);
534
+ }
535
+
536
+ function normalizeText(text) {
537
+ if (typeof text !== 'string') return '';
538
+ return text.trim().toLowerCase().replace(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g,"").replace(/\s{2,}/g," ");
539
+ }
540
+
541
+ function performAutoScoring(task, aiResponseText) {
542
+ const criteria = task.autoScoringCriteria;
543
+ if (!criteria) return null;
544
+
545
+ const normalizedResponse = normalizeText(aiResponseText);
546
+ let suggestedScore = 0;
547
+
548
+ if (criteria.type === "exact") {
549
+ const normalizedAnswer = normalizeText(criteria.answer);
550
+ // More robust check for exact answers that might have slight variations (e.g. "pound" vs "pound sterling")
551
+ const possibleAnswers = criteria.answer.split('|').map(s => normalizeText(s)); // Allow multiple exact answers separated by |
552
+ if (possibleAnswers.includes(normalizedResponse)) {
553
+ if (task.scoringOptions["1"] !== undefined && Object.keys(task.scoringOptions).length <= 2) {
554
+ suggestedScore = 1;
555
+ } else if (task.scoringOptions["2"] !== undefined) {
556
+ suggestedScore = 2;
557
+ } else {
558
+ suggestedScore = 0;
559
+ }
560
+ }
561
+ } else if (criteria.type === "keywords") {
562
+ for (const rule of criteria.rules.sort((a,b) => b.score - a.score)) {
563
+ let keywordsMet = 0;
564
+ for (const keyword of rule.keywords) {
565
+ if (normalizedResponse.includes(normalizeText(keyword))) {
566
+ keywordsMet++;
567
+ }
568
+ }
569
+ const countRequired = rule.countRequired || 1;
570
+ if (rule.matchType === "all" && keywordsMet === rule.keywords.length) {
571
+ suggestedScore = rule.score;
572
+ break;
573
+ } else if (rule.matchType === "any" && keywordsMet >= countRequired) {
574
+ suggestedScore = rule.score;
575
+ break;
576
+ }
577
+ }
578
+ }
579
+ return suggestedScore;
580
+ }
581
+
582
+
583
+ async function callActualApi(task) {
584
+ loadingIndicatorEl.classList.remove('hidden');
585
+ aiResponseEl.innerHTML = '';
586
+ apiErrorEl.classList.add('hidden');
587
+ suggestedScoreDisplayEl.classList.add('hidden');
588
+
589
+ const apiUrl = `${apiConfig.baseUrl.replace(/\/$/, '')}/chat/completions`;
590
+ const requestBody = {
591
+ model: apiConfig.model,
592
+ messages: [ { "role": "user", "content": task.prompt } ],
593
+ };
594
+
595
+ try {
596
+ const response = await fetch(apiUrl, {
597
+ method: 'POST',
598
+ headers: {
599
+ 'Content-Type': 'application/json',
600
+ 'Authorization': `Bearer ${apiConfig.apiKey}`
601
+ },
602
+ body: JSON.stringify(requestBody)
603
+ });
604
+
605
+ loadingIndicatorEl.classList.add('hidden');
606
+
607
+ if (!response.ok) {
608
+ const errorData = await response.json().catch(() => ({}));
609
+ const errorMessage = `API Error: ${response.status} ${response.statusText}. ${errorData.error?.message || 'No additional details.'}`;
610
+ throw new Error(errorMessage);
611
+ }
612
+
613
+ const data = await response.json();
614
+ let responseText = "No response content found.";
615
+ if (data.choices && data.choices.length > 0 && data.choices[0].message && data.choices[0].message.content) {
616
+ responseText = data.choices[0].message.content.trim();
617
+ }
618
+
619
+ const sanitizedText = responseText.replace(/</g, "&lt;").replace(/>/g, "&gt;");
620
+ aiResponseEl.innerHTML = `<pre>${sanitizedText}</pre>`;
621
+ taskStartTime = performance.now();
622
+
623
+ const autoSuggestedScore = performAutoScoring(task, responseText);
624
+ const scoreToPreselect = autoScoringEnabledGlobal ? autoSuggestedScore : null;
625
+ setupScoring(task.scoringOptions, scoreToPreselect);
626
+
627
+ if (autoScoringEnabledGlobal && autoSuggestedScore !== null) {
628
+ suggestedScoreDisplayEl.innerHTML = `Auto-Score: <span class="suggested-score-badge">${autoSuggestedScore}</span>`;
629
+ suggestedScoreDisplayEl.classList.remove('hidden');
630
+
631
+ nextButton.disabled = true;
632
+ setTimeout(() => {
633
+ // Ensure we are still on the same task before auto-advancing
634
+ // (e.g. user didn't manually click next very fast if button was briefly enabled)
635
+ if (task.prompt === adaptedTasks[currentTaskIndex].prompt) {
636
+ if (currentTaskIndex < adaptedTasks.length -1) {
637
+ nextButton.disabled = false;
638
+ }
639
+ nextTask();
640
+ }
641
+ }, AUTO_ADVANCE_DELAY);
642
+ } else {
643
+ nextButton.disabled = false;
644
+ }
645
+
646
+ } catch (error) {
647
+ loadingIndicatorEl.classList.add('hidden');
648
+ console.error('API Call Failed:', error);
649
+ apiErrorEl.textContent = `Failed to get response: ${error.message}`;
650
+ apiErrorEl.classList.remove('hidden');
651
+ aiResponseEl.innerHTML = `<span class="text-red-600 italic">Error fetching response.</span>`;
652
+ scoringInputEl.innerHTML = `<span class="text-gray-500 italic">Cannot score due to API error. Manual scoring required.</span>`;
653
+ setupScoring(task.scoringOptions, null);
654
+ nextButton.disabled = false;
655
+ taskStartTime = 0;
656
+ }
657
+ }
658
+
659
+ function setupScoring(options, preSelectedScoreValue = null) {
660
+ scoringInputEl.innerHTML = '';
661
+ const form = document.createElement('form');
662
+ form.id = 'scoringForm';
663
+
664
+ const sortedScoreValues = Object.keys(options).sort((a,b) => parseInt(a) - parseInt(b));
665
+
666
+ for (const scoreValue of sortedScoreValues) {
667
+ const description = options[scoreValue];
668
+ const labelDiv = document.createElement('div');
669
+ labelDiv.classList.add('p-2', 'rounded', 'hover:bg-yellow-100');
670
+
671
+ const radioLabel = document.createElement('label');
672
+ radioLabel.classList.add('flex', 'items-start', 'cursor-pointer');
673
+
674
+ const radio = document.createElement('input');
675
+ radio.type = 'radio';
676
+ radio.name = 'score';
677
+ radio.value = scoreValue;
678
+ radio.required = true;
679
+ radio.classList.add('form-radio', 'h-4', 'w-4', 'text-indigo-600', 'border-gray-300', 'focus:ring-indigo-500', 'mt-1');
680
+
681
+ if (autoScoringEnabledGlobal && preSelectedScoreValue !== null && parseInt(scoreValue) === preSelectedScoreValue) {
682
+ radio.checked = true;
683
+ }
684
+
685
+ const descriptionDiv = document.createElement('div');
686
+ descriptionDiv.classList.add('ml-2');
687
+
688
+ const scorePointText = document.createElement('span');
689
+ scorePointText.textContent = `${scoreValue} point(s): `;
690
+ scorePointText.classList.add('text-sm', 'font-medium', 'text-gray-800');
691
+
692
+ const detailedDescriptionText = document.createElement('span');
693
+ detailedDescriptionText.textContent = description;
694
+ detailedDescriptionText.classList.add('text-sm', 'text-gray-700');
695
+
696
+ descriptionDiv.appendChild(scorePointText);
697
+ descriptionDiv.appendChild(detailedDescriptionText);
698
+
699
+ radioLabel.appendChild(radio);
700
+ radioLabel.appendChild(descriptionDiv);
701
+ labelDiv.appendChild(radioLabel);
702
+ form.appendChild(labelDiv);
703
+ }
704
+ scoringInputEl.appendChild(form);
705
+ }
706
+
707
+ function getSelectedScore() {
708
+ const selectedRadio = scoringInputEl.querySelector('input[name="score"]:checked');
709
+ return selectedRadio ? parseInt(selectedRadio.value, 10) : null;
710
+ }
711
+
712
+ function nextTask() {
713
+ const score = getSelectedScore();
714
+ let duration = 0;
715
+
716
+ if (taskStartTime > 0) {
717
+ duration = performance.now() - taskStartTime;
718
+ }
719
+
720
+ if (score === null && !autoScoringEnabledGlobal && apiErrorEl.classList.contains('hidden')) {
721
+ alert("Please select a score before proceeding.");
722
+ return;
723
+ }
724
+
725
+ detailedScores.push({
726
+ taskIndex: currentTaskIndex,
727
+ score: score === null ? 0 : score,
728
+ duration: duration,
729
+ task: adaptedTasks[currentTaskIndex]
730
+ });
731
+
732
+ currentTaskIndex++;
733
+ displayTask(currentTaskIndex);
734
+ }
735
+
736
+ function showResults() {
737
+ testArea.classList.add('hidden');
738
+ resultsArea.classList.remove('hidden');
739
+ leaderboardArea.classList.remove('hidden');
740
+ saveMessageEl.textContent = '';
741
+ userNameInput.value = '';
742
+
743
+ currentTotalScore = detailedScores.reduce((sum, item) => sum + item.score, 0);
744
+ finalScoreEl.textContent = currentTotalScore;
745
+ displayLeaderboard();
746
+ }
747
+
748
+ function loadLeaderboard() {
749
+ try {
750
+ const storedData = localStorage.getItem(LEADERBOARD_KEY);
751
+ const parsedData = storedData ? JSON.parse(storedData) : [];
752
+ return Array.isArray(parsedData) ? parsedData : [];
753
+ } catch (e) {
754
+ console.error("Error loading or parsing leaderboard from localStorage:", e);
755
+ localStorage.removeItem(LEADERBOARD_KEY);
756
+ return [];
757
+ }
758
+ }
759
+
760
+ function saveLeaderboard(data) {
761
+ try {
762
+ data.sort((a, b) => b.score - a.score);
763
+ localStorage.setItem(LEADERBOARD_KEY, JSON.stringify(data));
764
+ return true;
765
+ } catch (e) {
766
+ console.error("Error saving leaderboard to localStorage:", e);
767
+ if (e.name === 'QuotaExceededError') {
768
+ alert("Could not save score: LocalStorage quota exceeded.");
769
+ } else {
770
+ alert("Could not save score due to a storage error.");
771
+ }
772
+ return false;
773
+ }
774
+ }
775
+
776
+ function displayLeaderboard() {
777
+ const leaderboardData = loadLeaderboard();
778
+ leaderboardContentEl.innerHTML = '';
779
+
780
+ if (leaderboardData.length === 0) {
781
+ leaderboardContentEl.innerHTML = '<p class="text-center text-gray-500 italic">No scores saved yet.</p>';
782
+ return;
783
+ }
784
+
785
+ leaderboardData.sort((a, b) => {
786
+ if (b.score !== a.score) return b.score - a.score;
787
+ return new Date(b.date) - new Date(a.date);
788
+ });
789
+
790
+ const table = document.createElement('table');
791
+ table.classList.add('data-table', 'min-w-full', 'divide-y', 'divide-gray-200');
792
+ const thead = table.createTHead();
793
+ const headerRow = thead.insertRow();
794
+ const headers = ['Rank', 'Name', 'Score', 'Date', 'Details'];
795
+ headers.forEach(text => {
796
+ const th = document.createElement('th');
797
+ th.textContent = text;
798
+ th.classList.add('px-4', 'py-2', 'text-left', 'text-xs', 'font-medium', 'text-gray-500', 'uppercase', 'tracking-wider');
799
+ if (text === 'Score') th.classList.add('text-right');
800
+ if (text === 'Rank' || text === 'Details') th.classList.add('text-center');
801
+ headerRow.appendChild(th);
802
+ });
803
+
804
+ const tbody = table.createTBody();
805
+ tbody.classList.add('bg-white', 'divide-y', 'divide-gray-200');
806
+ const maxEntriesToShow = 20;
807
+ leaderboardData.slice(0, maxEntriesToShow).forEach((entry, index) => {
808
+ const row = tbody.insertRow();
809
+ row.classList.add('hover:bg-gray-50');
810
+ row.insertCell().outerHTML = `<td class="px-4 py-2 whitespace-nowrap text-sm text-gray-500 text-center">${index + 1}</td>`;
811
+ row.insertCell().outerHTML = `<td class="px-4 py-2 whitespace-nowrap text-sm font-medium text-gray-900">${entry.name}</td>`;
812
+ row.insertCell().outerHTML = `<td class="px-4 py-2 whitespace-nowrap text-sm text-gray-500 text-right">${entry.score}</td>`;
813
+ row.insertCell().outerHTML = `<td class="px-4 py-2 whitespace-nowrap text-sm text-gray-500">${new Date(entry.date).toLocaleDateString()}</td>`;
814
+
815
+ const detailsCell = row.insertCell();
816
+ detailsCell.classList.add('px-4', 'py-2', 'whitespace-nowrap', 'text-sm', 'text-gray-500', 'text-center');
817
+ if (entry.details && entry.details.length > 0) {
818
+ const detailsButton = document.createElement('button');
819
+ detailsButton.textContent = 'View';
820
+ detailsButton.classList.add('details-button');
821
+ detailsButton.onclick = () => showDetails(entry);
822
+ detailsCell.appendChild(detailsButton);
823
+ } else {
824
+ detailsCell.textContent = 'N/A';
825
+ }
826
+ });
827
+ leaderboardContentEl.appendChild(table);
828
+ }
829
+
830
+ function saveCurrentScore() {
831
+ const userName = userNameInput.value.trim();
832
+ if (!userName) {
833
+ alert("Please enter a name to save the score.");
834
+ return;
835
+ }
836
+ const leaderboardData = loadLeaderboard();
837
+ const newEntry = {
838
+ id: Date.now().toString(),
839
+ name: userName,
840
+ score: currentTotalScore,
841
+ date: new Date().toISOString(),
842
+ details: detailedScores
843
+ };
844
+ leaderboardData.push(newEntry);
845
+ if(saveLeaderboard(leaderboardData)) {
846
+ saveMessageEl.textContent = `Score saved for ${userName}!`;
847
+ userNameInput.value = '';
848
+ displayLeaderboard();
849
+ } else {
850
+ saveMessageEl.textContent = `Failed to save score.`;
851
+ }
852
+ }
853
+
854
+ function clearLeaderboard() {
855
+ if (confirm("Are you sure you want to clear the entire leaderboard? This cannot be undone.")) {
856
+ try {
857
+ localStorage.removeItem(LEADERBOARD_KEY);
858
+ displayLeaderboard();
859
+ alert("Leaderboard cleared.");
860
+ } catch (e) {
861
+ console.error("Error clearing leaderboard:", e);
862
+ alert("Could not clear leaderboard.");
863
+ }
864
+ }
865
+ }
866
+
867
+ function showDetails(entry) {
868
+ if (!entry || !Array.isArray(entry.details) || entry.details.length === 0) {
869
+ alert("No details available for this entry.");
870
+ return;
871
+ }
872
+ detailsModalTitle.textContent = `Task Details for ${entry.name} (${new Date(entry.date).toLocaleDateString()})`;
873
+ detailsModalContent.innerHTML = '';
874
+
875
+ const table = document.createElement('table');
876
+ table.classList.add('data-table', 'min-w-full', 'divide-y', 'divide-gray-200');
877
+ const thead = table.createTHead();
878
+ const headerRow = thead.insertRow();
879
+ const headers = ['#', 'Type', 'Prompt', 'Score', 'Time (s)'];
880
+ headers.forEach(text => {
881
+ const th = document.createElement('th');
882
+ th.textContent = text;
883
+ th.classList.add('px-3', 'py-2', 'text-left', 'text-xs', 'font-medium', 'text-gray-500', 'uppercase', 'tracking-wider');
884
+ if (text === 'Score' || text === 'Time (s)') th.classList.add('text-right');
885
+ if (text === '#') th.classList.add('text-center');
886
+ headerRow.appendChild(th);
887
+ });
888
+ const tbody = table.createTBody();
889
+ tbody.classList.add('bg-white', 'divide-y', 'divide-gray-200');
890
+ entry.details.forEach(item => {
891
+ const row = tbody.insertRow();
892
+ row.classList.add('hover:bg-gray-50');
893
+ row.insertCell().outerHTML = `<td class="px-3 py-2 whitespace-nowrap text-sm text-gray-500 text-center">${item.taskIndex + 1}</td>`;
894
+ row.insertCell().outerHTML = `<td class="px-3 py-2 whitespace-nowrap text-sm text-gray-500">${item.task.type}</td>`;
895
+ row.insertCell().outerHTML = `<td class="px-3 py-2 text-sm text-gray-500 prompt-cell">${item.task.prompt}</td>`;
896
+ row.insertCell().outerHTML = `<td class="px-3 py-2 whitespace-nowrap text-sm text-gray-500 text-right">${item.score}</td>`;
897
+ const durationSeconds = item.duration > 0 ? (item.duration / 1000).toFixed(2) : 'N/A';
898
+ row.insertCell().outerHTML = `<td class="px-3 py-2 whitespace-nowrap text-sm text-gray-500 text-right">${durationSeconds}</td>`;
899
+ });
900
+ detailsModalContent.appendChild(table);
901
+ detailsModal.style.display = 'block';
902
+ }
903
+
904
+ function closeDetailsModal() {
905
+ detailsModal.style.display = 'none';
906
+ }
907
+
908
+ // --- Event Listeners ---
909
+ startButton.addEventListener('click', () => {
910
+ const baseUrl = baseUrlInput.value.trim();
911
+ const apiKey = apiKeyInput.value.trim();
912
+ const modelName = modelNameInput.value.trim();
913
+ autoScoringEnabledGlobal = enableAutoScoringCheckbox.checked;
914
+
915
+ let hasError = false;
916
+ configErrorEl.textContent = '';
917
+ configErrorEl.classList.add('hidden');
918
+
919
+ if (!baseUrl || !apiKey || !modelName) {
920
+ configErrorEl.textContent = 'API Base URL, API Key, and Model Name are required.';
921
+ hasError = true;
922
+ }
923
+ if (hasError) {
924
+ configErrorEl.classList.remove('hidden');
925
+ return;
926
+ }
927
+ apiConfig.baseUrl = baseUrl;
928
+ apiConfig.apiKey = apiKey;
929
+ apiConfig.model = modelName;
930
+
931
+ if (autoScoringEnabledGlobal) {
932
+ scoringInstructionsEl.textContent = "Auto-score will be suggested and submitted shortly. Timer starts after response.";
933
+ } else {
934
+ scoringInstructionsEl.textContent = "Evaluate the AI's response. Timer starts after response. Auto-score is NOT active.";
935
+ }
936
+
937
+
938
+ configArea.classList.add('hidden');
939
+ startControl.classList.add('hidden');
940
+ leaderboardArea.classList.add('hidden');
941
+ testArea.classList.remove('hidden');
942
+ resultsArea.classList.add('hidden');
943
+
944
+ currentTaskIndex = 0;
945
+ detailedScores = [];
946
+ currentTotalScore = 0;
947
+ displayTask(currentTaskIndex);
948
+ });
949
+
950
+ nextButton.addEventListener('click', nextTask);
951
+ saveScoreButton.addEventListener('click', saveCurrentScore);
952
+ clearLeaderboardButton.addEventListener('click', clearLeaderboard);
953
+ modalCloseButton.addEventListener('click', closeDetailsModal);
954
+ window.addEventListener('click', (event) => {
955
+ if (event.target == detailsModal) closeDetailsModal();
956
+ });
957
+
958
+ // --- Initial Load ---
959
+ document.addEventListener('DOMContentLoaded', () => {
960
+ displayLeaderboard();
961
+ scoringInstructionsEl.textContent = "Evaluate the AI's response. Timer starts after response. Auto-score is NOT active.";
962
+ });
963
+
964
+ </script>
965
+
966
+ </body>
967
  </html>