Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>AI Task Administration UI (Optional Auto-Score)</title> | |
<script src="https://cdn.tailwindcss.com"></script> | |
<style> | |
/* Custom styles */ | |
body { | |
font-family: 'Inter', sans-serif; | |
} | |
.transition-all { | |
transition: all 0.3s ease-in-out; | |
} | |
button:disabled { | |
opacity: 0.5; | |
cursor: not-allowed; | |
} | |
.center-content { | |
display: flex; | |
flex-direction: column; | |
align-items: center; | |
padding: 1rem; | |
margin-bottom: 3rem; | |
} | |
.main-container { | |
max-width: 800px; | |
width: 100%; | |
} | |
.error-message { | |
color: #dc2626; /* red-600 */ | |
font-size: 0.875rem; /* text-sm */ | |
margin-top: 0.25rem; | |
} | |
#aiResponse pre { | |
white-space: pre-wrap; | |
word-wrap: break-word; | |
font-family: inherit; | |
font-size: 0.95rem; | |
line-height: 1.5; | |
background-color: #f9fafb; /* gray-50 */ | |
padding: 0.75rem; | |
border-radius: 0.375rem; /* rounded-md */ | |
border: 1px solid #e5e7eb; /* gray-200 */ | |
max-height: 300px; | |
overflow-y: auto; | |
} | |
.data-table { | |
width: 100%; | |
border-collapse: collapse; | |
margin-top: 1rem; | |
} | |
.data-table th, .data-table td { | |
border: 1px solid #e5e7eb; | |
padding: 0.5rem 0.75rem; | |
text-align: left; | |
vertical-align: top; | |
} | |
.data-table th { | |
background-color: #f3f4f6; | |
font-weight: 600; | |
white-space: nowrap; | |
} | |
.data-table tr:nth-child(even) { | |
background-color: #f9fafb; | |
} | |
.data-table td.prompt-cell { | |
max-width: 300px; | |
white-space: normal; | |
font-size: 0.8rem; | |
line-height: 1.2; | |
} | |
.modal { | |
display: none; | |
position: fixed; | |
z-index: 1000; | |
left: 0; | |
top: 0; | |
width: 100%; | |
height: 100%; | |
overflow: auto; | |
background-color: rgba(0,0,0,0.6); | |
} | |
.modal-content { | |
background-color: #fefefe; | |
margin: 5% auto; | |
padding: 25px; | |
border: 1px solid #888; | |
width: 90%; | |
max-width: 900px; | |
border-radius: 8px; | |
position: relative; | |
} | |
.modal-close { | |
color: #aaa; | |
position: absolute; | |
top: 10px; | |
right: 20px; | |
font-size: 28px; | |
font-weight: bold; | |
cursor: pointer; | |
} | |
.modal-close:hover, | |
.modal-close:focus { | |
color: black; | |
text-decoration: none; | |
} | |
.details-button { | |
font-size: 0.75rem; | |
padding: 0.25rem 0.5rem; | |
background-color: #4f46e5; | |
color: white; | |
border-radius: 0.375rem; | |
cursor: pointer; | |
transition: background-color 0.2s; | |
} | |
.details-button:hover { | |
background-color: #4338ca; | |
} | |
.suggested-score-badge { | |
display: inline-block; | |
padding: 0.2em 0.6em; | |
font-size: 0.75em; | |
font-weight: bold; | |
line-height: 1; | |
color: #fff; | |
text-align: center; | |
white-space: nowrap; | |
vertical-align: baseline; | |
border-radius: 0.25rem; | |
background-color: #10b981; /* green-500 */ | |
margin-left: 8px; | |
} | |
#scoringInput .scoring-option-description { | |
display: block; | |
margin-left: 1.75rem; | |
font-size: 0.8rem; | |
color: #4b5563; | |
} | |
</style> | |
<link rel="preconnect" href="https://fonts.googleapis.com"> | |
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> | |
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet"> | |
</head> | |
<body class="bg-gray-100 text-gray-800 center-content"> | |
<div class="main-container bg-white p-6 md:p-8 rounded-lg shadow-lg mb-8"> | |
<h1 class="text-2xl md:text-3xl font-bold mb-6 text-center text-blue-700">AI Task Administration UI</h1> | |
<div id="configArea" class="mb-6 p-4 bg-gray-50 rounded-md border border-gray-200 space-y-4"> | |
<h2 class="text-lg font-semibold mb-2 text-gray-700">API Configuration</h2> | |
<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> | |
<div> | |
<label for="baseUrl" class="block text-sm font-medium text-gray-700">API Base URL:</label> | |
<input type="url" id="baseUrl" placeholder="e.g., https://api.openai.com/v1" value="" 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> | |
</div> | |
<div> | |
<label for="apiKey" class="block text-sm font-medium text-gray-700">API Key:</label> | |
<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> | |
</div> | |
<div> | |
<label for="modelName" class="block text-sm font-medium text-gray-700">Model Name:</label> | |
<input type="text" id="modelName" placeholder="e.g., gpt-3.5-turbo" value="" 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> | |
</div> | |
<div> | |
<label for="enableAutoScoring" class="flex items-center text-sm font-medium text-gray-700 mt-3"> | |
<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"> | |
Enable Automatic Scoring & Auto-Advance | |
</label> | |
<p class="text-xs text-gray-500 mt-1 ml-6">If checked, suggested scores will be automatically submitted after a short delay.</p> | |
</div> | |
<div id="configError" class="error-message hidden"></div> | |
</div> | |
<div id="startControl" class="mb-6 text-center"> | |
<button id="startButton" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-6 rounded-lg shadow transition-all"> | |
Start Test | |
</button> | |
</div> | |
<div id="testArea" class="hidden space-y-6"> | |
<div class="w-full bg-gray-200 rounded-full h-2.5 dark:bg-gray-700"> | |
<div id="progressBar" class="bg-blue-600 h-2.5 rounded-full" style="width: 0%"></div> | |
</div> | |
<p id="progressText" class="text-center text-sm text-gray-600">Task 0 / 0</p> | |
<div class="p-4 bg-blue-50 rounded-md border border-blue-200"> | |
<h2 class="text-lg font-semibold mb-2 text-blue-800">Task <span id="taskNumber"></span>: <span id="taskType"></span></h2> | |
<p id="taskPrompt" class="text-gray-700 text-base"></p> | |
</div> | |
<div class="p-4 bg-green-50 rounded-md border border-green-200"> | |
<h2 class="text-lg font-semibold mb-2 text-green-800">AI Response</h2> | |
<div id="aiResponse" class="text-gray-700 p-2 rounded bg-white border border-gray-200 min-h-[80px] flex items-center justify-center"> | |
<span class="text-gray-500 italic">Waiting for API response...</span> | |
</div> | |
<div id="loadingIndicator" class="hidden text-center mt-2 text-sm text-gray-500"> | |
<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"> | |
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle> | |
<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> | |
</svg> | |
Calling API... | |
</div> | |
<div id="apiError" class="error-message hidden mt-2"></div> | |
</div> | |
<div class="p-4 bg-yellow-50 rounded-md border border-yellow-200"> | |
<div class="flex justify-between items-center mb-2"> | |
<h2 class="text-lg font-semibold text-yellow-800">Scoring</h2> | |
<span id="suggestedScoreDisplay" class="hidden"></span> | |
</div> | |
<p id="scoringInstructions" class="text-sm text-gray-600 mb-2">Evaluate the AI's response. Timer starts after response.</p> | |
<div id="scoringInput" class="space-y-2"> | |
<span class="text-gray-500 italic">Score after response is received.</span> | |
</div> | |
</div> | |
<div class="text-center"> | |
<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> | |
Submit Score & Next Task | |
</button> | |
</div> | |
</div> | |
<div id="resultsArea" class="hidden mt-8 p-6 bg-purple-50 rounded-md border border-purple-200"> | |
<h2 class="text-xl font-semibold mb-4 text-center text-purple-800">Test Completed!</h2> | |
<p class="text-center text-gray-700 mb-1">Final Score:</p> | |
<p id="finalScore" class="text-center text-3xl font-bold text-purple-700 mb-4"></p> | |
<div class="mt-4 mb-4 flex flex-col sm:flex-row items-center justify-center gap-2"> | |
<label for="userName" class="block text-sm font-medium text-gray-700 mb-1 sm:mb-0">Save score as:</label> | |
<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"> | |
<button id="saveScoreButton" class="bg-indigo-600 hover:bg-indigo-700 text-white font-bold py-2 px-4 rounded-lg shadow transition-all"> | |
Save to Leaderboard | |
</button> | |
</div> | |
<div id="saveMessage" class="text-center text-sm text-green-600 mt-2"></div> | |
<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> | |
<div class="mt-6 text-center"> | |
<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"> | |
Start New Test | |
</button> | |
</div> | |
</div> | |
</div> <div id="leaderboardArea" class="main-container w-full bg-white p-6 md:p-8 rounded-lg shadow-lg mt-8"> | |
<h2 class="text-xl md:text-2xl font-bold mb-4 text-center text-teal-700">Leaderboard</h2> | |
<div id="leaderboardContent" class="overflow-x-auto"> | |
<p class="text-center text-gray-500 italic">No scores saved yet.</p> | |
</div> | |
<div class="mt-4 text-center"> | |
<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"> | |
Clear Leaderboard | |
</button> | |
</div> | |
</div> | |
<div id="detailsModal" class="modal"> | |
<div class="modal-content"> | |
<span id="modalCloseButton" class="modal-close">×</span> | |
<h3 id="detailsModalTitle" class="text-xl font-semibold mb-4 text-center text-gray-800">Task Details</h3> | |
<div id="detailsModalContent" class="overflow-x-auto"> | |
</div> | |
</div> | |
</div> | |
<script> | |
// --- Adapted Task Data with Auto-Scoring Criteria --- | |
const adaptedTasks = [ | |
// Vocabulary (Target: 20, Current: 15 + 5 new) | |
{ type: "Vocabulary", prompt: "What does 'tranquil' mean?", | |
scoringOptions: { | |
"0": "Incorrect (e.g., definition is wrong, irrelevant, or no answer)", | |
"1": "Partially Correct (e.g., captures some aspect like 'quiet' but misses full scope like 'peaceful')", | |
"2": "Fully Correct (e.g., comprehensive and accurate, like 'calm, peaceful, serene')" | |
}, | |
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 } ] } | |
}, | |
{ type: "Vocabulary", prompt: "Define 'ambiguous'.", | |
scoringOptions: { | |
"0": "Incorrect (e.g., definition is wrong or irrelevant)", | |
"1": "Partially Correct (e.g., mentions 'unclear' but not the idea of multiple meanings)", | |
"2": "Fully Correct (e.g., explains it means open to more than one interpretation or having a double meaning)" | |
}, | |
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 } ] } | |
}, | |
{ type: "Vocabulary", prompt: "What is 'empathy'?", | |
scoringOptions: { | |
"0": "Incorrect (e.g., confuses with sympathy or provides an unrelated definition)", | |
"1": "Partially Correct (e.g., mentions understanding feelings but not sharing them)", | |
"2": "Fully Correct (e.g., defines as the ability to understand AND share the feelings of another)" | |
}, | |
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 } ] } | |
}, | |
{ 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 } ] } }, | |
{ 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 } ] } }, | |
{ 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 } ] } }, | |
{ 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 } ] } }, | |
{ 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 } ] } }, | |
{ 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 } ] } }, | |
{ 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 } ] } }, | |
{ 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 } ] } }, | |
{ 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 } ] } }, | |
{ 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 } ] } }, | |
{ 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 } ] } }, | |
{ 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 } ] } }, | |
{ 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 } ] } }, | |
{ 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 } ] } }, | |
{ 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 } ] } }, | |
{ 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 } ] } }, | |
{ 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 } ] } }, | |
// Similarities (Target: 20, Current: 15 + 5 new) | |
{ type: "Similarities", prompt: "In what way are 'apple' and 'banana' alike?", | |
scoringOptions: { | |
"0": "Incorrect (e.g., irrelevant comparison, or focuses on differences like 'one is red, one is yellow')", | |
"1": "Functional/Concrete (e.g., 'you eat them', 'they are food', 'they grow on trees', 'you buy them at a store')", | |
"2": "Categorical/Abstract (e.g., 'they are fruits', 'types of produce', 'both are plants')" | |
}, | |
autoScoringCriteria: { type: "keywords", rules: [ { score: 2, keywords: ["fruit", "produce", "plant"], matchType: "any" }, { score: 1, keywords: ["eat", "food", "grow", "buy"], matchType: "any" } ] } | |
}, | |
{ type: "Similarities", prompt: "How are 'table' and 'chair' alike?", | |
scoringOptions: { | |
"0": "Incorrect (e.g., 'they are different shapes')", | |
"1": "Functional/Associative (e.g., 'you use them together', 'found in a dining room', 'made of wood')", | |
"2": "Categorical/Abstract (e.g., 'they are types of furniture', 'household items', 'man-made objects for a room')" | |
}, | |
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" } ] } | |
}, | |
{ 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" } ] } }, | |
{ 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" } ] } }, | |
{ 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" } ] } }, | |
{ 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" } ] } }, | |
{ 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" } ] } }, | |
{ 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" } ] } }, | |
{ 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" } ] } }, | |
{ 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" } ] } }, | |
{ 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" } ] } }, | |
{ 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" } ] } }, | |
{ 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" } ] } }, | |
{ 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" } ] } }, | |
{ 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" } ] } }, | |
{ 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" } ] } }, | |
{ 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" } ] } }, | |
{ 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" } ] } }, | |
{ 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" } ] } }, | |
{ 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" } ] } }, | |
// Information (Target: 20, Current: 15 + 5 new) | |
{ type: "Information", prompt: "How many days are in a normal year (not a leap year)?", | |
scoringOptions: { | |
"0": "Incorrect (e.g., wrong number like 356 or 30)", | |
"1": "Correct (states 365)" | |
}, | |
autoScoringCriteria: { type: "exact", answer: "365" } | |
}, | |
{ type: "Information", prompt: "What is the boiling point of water in degrees Celsius?", | |
scoringOptions: { | |
"0": "Incorrect (e.g., wrong temperature like 0 or 212, or wrong units if specified)", | |
"1": "Correct (states 100, Celsius implied or stated)" | |
}, | |
autoScoringCriteria: { type: "exact", answer: "100" } | |
}, | |
{ type: "Information", prompt: "Who wrote the play 'Hamlet'?", scoringOptions: { "0": "Incorrect (e.g., 'Dickens')", "1": "Correct (William Shakespeare)" }, autoScoringCriteria: { type: "exact", answer: "william shakespeare" } }, | |
{ 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" } }, | |
{ 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" } }, | |
{ type: "Information", prompt: "What is the capital of Japan?", scoringOptions: { "0": "Incorrect (e.g., 'Kyoto')", "1": "Correct (Tokyo)" }, autoScoringCriteria: { type: "exact", answer: "tokyo" } }, | |
{ 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" } }, | |
{ type: "Information", prompt: "What continent is Brazil on?", scoringOptions: { "0": "Incorrect (e.g., 'Africa')", "1": "Correct (South America)" }, autoScoringCriteria: { type: "exact", answer: "south america" } }, | |
{ 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" } }, | |
{ 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" } }, | |
{ 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" | |
{ 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" } }, | |
{ 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" } }, | |
{ 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" }] } }, | |
{ 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" } }, | |
{ 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" | |
{ 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" } }, | |
{ type: "Information", prompt: "How many sides does a hexagon have?", scoringOptions: { "0": "Incorrect", "1": "Correct (6)" }, autoScoringCriteria: { type: "exact", answer: "6" } }, | |
{ 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 | |
{ 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" } }, | |
// Arithmetic (Target: 20, Current: 15 + 5 new) | |
{ type: "Arithmetic", prompt: "If pencils cost $0.50 each, how many can you buy for $4.00? Respond with only the number.", | |
scoringOptions: { | |
"0": "Incorrect (wrong numerical answer e.g., 4, 2)", | |
"1": "Correct (provides the number 8)" | |
}, | |
autoScoringCriteria: { type: "exact", answer: "8" } | |
}, | |
{ 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.", | |
scoringOptions: { | |
"0": "Incorrect (wrong numerical answer e.g., 150, 53.5)", | |
"1": "Correct (provides the number 175)" | |
}, | |
autoScoringCriteria: { type: "exact", answer: "175" } | |
}, | |
{ 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" } }, | |
{ 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" } }, | |
{ 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" } }, | |
{ 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" } }, | |
{ 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" } }, | |
{ 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" } }, | |
{ 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" } }, | |
{ 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" } }, | |
{ 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" } }, | |
{ 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" } }, | |
{ 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" } }, | |
{ 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" } }, | |
{ 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" } }, | |
{ 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" } }, | |
{ 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" } }, | |
{ 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" } }, | |
{ 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" } }, | |
{ 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" } }, | |
// Comprehension (Target: 20, Current: 15 + 5 new) | |
{ type: "Comprehension", prompt: "Why do people need licenses to drive cars?", | |
scoringOptions: { | |
"0": "Incorrect/Irrelevant (e.g., 'because they are cars', 'to show off')", | |
"1": "Partial/Simple (e.g., 'it's the law', 'so you don't crash', 'to drive')", | |
"2": "Comprehensive (e.g., 'to ensure drivers know traffic laws, can operate a vehicle safely, and to protect public safety and property')" | |
}, | |
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 } ] } | |
}, | |
{ type: "Comprehension", prompt: "Why should promises be kept?", | |
scoringOptions: { | |
"0": "Irrelevant/Incorrect (e.g., 'you don't have to if you don't want to')", | |
"1": "Simple/Superficial (e.g., 'it's nice', 'to be good', 'so people like you')", | |
"2": "Comprehensive (e.g., 'to build trust, maintain relationships, show reliability and integrity, because it's a commitment that affects others')" | |
}, | |
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 } ] } | |
}, | |
{ 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 } ] } }, | |
{ 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 } ] } }, | |
{ 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 } ] } }, | |
{ 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 } ] } }, | |
{ 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 } ] } }, | |
{ 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 } ] } }, | |
{ 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 } ] } }, | |
{ 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 } ] } }, | |
{ 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 } ] } }, | |
{ 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 } ] } }, | |
{ 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 } ] } }, | |
{ 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 } ] } }, | |
{ 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 } ] } }, | |
{ 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 } ] } }, | |
{ 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 } ] } }, | |
{ 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 } ] } }, | |
{ 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 } ] } }, | |
{ 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 } ] } }, | |
]; // Total ~100 questions | |
// --- DOM Elements --- | |
const configArea = document.getElementById('configArea'); | |
const baseUrlInput = document.getElementById('baseUrl'); | |
const apiKeyInput = document.getElementById('apiKey'); | |
const modelNameInput = document.getElementById('modelName'); | |
const enableAutoScoringCheckbox = document.getElementById('enableAutoScoring'); | |
const configErrorEl = document.getElementById('configError'); | |
const startControl = document.getElementById('startControl'); | |
const startButton = document.getElementById('startButton'); | |
const testArea = document.getElementById('testArea'); | |
const progressBar = document.getElementById('progressBar'); | |
const progressText = document.getElementById('progressText'); | |
const resultsArea = document.getElementById('resultsArea'); | |
const taskNumberEl = document.getElementById('taskNumber'); | |
const taskTypeEl = document.getElementById('taskType'); | |
const taskPromptEl = document.getElementById('taskPrompt'); | |
const aiResponseEl = document.getElementById('aiResponse'); | |
const loadingIndicatorEl = document.getElementById('loadingIndicator'); | |
const apiErrorEl = document.getElementById('apiError'); | |
const scoringInputEl = document.getElementById('scoringInput'); | |
const scoringInstructionsEl = document.getElementById('scoringInstructions'); | |
const suggestedScoreDisplayEl = document.getElementById('suggestedScoreDisplay'); | |
const nextButton = document.getElementById('nextButton'); | |
const finalScoreEl = document.getElementById('finalScore'); | |
const userNameInput = document.getElementById('userName'); | |
const saveScoreButton = document.getElementById('saveScoreButton'); | |
const saveMessageEl = document.getElementById('saveMessage'); | |
const leaderboardArea = document.getElementById('leaderboardArea'); | |
const leaderboardContentEl = document.getElementById('leaderboardContent'); | |
const clearLeaderboardButton = document.getElementById('clearLeaderboardButton'); | |
const detailsModal = document.getElementById('detailsModal'); | |
const detailsModalTitle = document.getElementById('detailsModalTitle'); | |
const detailsModalContent = document.getElementById('detailsModalContent'); | |
const modalCloseButton = document.getElementById('modalCloseButton'); | |
// --- State Variables --- | |
let currentTaskIndex = 0; | |
let detailedScores = []; | |
let currentTotalScore = 0; | |
let taskStartTime = 0; | |
let autoScoringEnabledGlobal = false; | |
let apiConfig = { baseUrl: '', apiKey: '', model: '' }; | |
const LEADERBOARD_KEY = 'aiTestLeaderboard_v4'; | |
const AUTO_ADVANCE_DELAY = 2500; | |
// --- Functions --- | |
function updateProgress() { | |
const percentage = adaptedTasks.length > 0 ? ((currentTaskIndex + 1) / adaptedTasks.length) * 100 : 0; | |
progressBar.style.width = `${percentage}%`; | |
progressText.textContent = `Task ${currentTaskIndex + 1} / ${adaptedTasks.length}`; | |
} | |
function displayTask(index) { | |
if (index >= adaptedTasks.length) { | |
showResults(); | |
return; | |
} | |
const task = adaptedTasks[index]; | |
taskNumberEl.textContent = index + 1; | |
taskTypeEl.textContent = task.type; | |
taskPromptEl.textContent = task.prompt; | |
aiResponseEl.innerHTML = `<span class="text-gray-500 italic">Waiting for API response...</span>`; | |
scoringInputEl.innerHTML = `<span class="text-gray-500 italic">Score after response is received.</span>`; | |
suggestedScoreDisplayEl.classList.add('hidden'); | |
apiErrorEl.classList.add('hidden'); | |
apiErrorEl.textContent = ''; | |
nextButton.disabled = true; | |
loadingIndicatorEl.classList.add('hidden'); | |
taskStartTime = 0; | |
updateProgress(); | |
callActualApi(task); | |
} | |
function normalizeText(text) { | |
if (typeof text !== 'string') return ''; | |
return text.trim().toLowerCase().replace(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g,"").replace(/\s{2,}/g," "); | |
} | |
function performAutoScoring(task, aiResponseText) { | |
const criteria = task.autoScoringCriteria; | |
if (!criteria) return null; | |
const normalizedResponse = normalizeText(aiResponseText); | |
let suggestedScore = 0; | |
if (criteria.type === "exact") { | |
const normalizedAnswer = normalizeText(criteria.answer); | |
// More robust check for exact answers that might have slight variations (e.g. "pound" vs "pound sterling") | |
const possibleAnswers = criteria.answer.split('|').map(s => normalizeText(s)); // Allow multiple exact answers separated by | | |
if (possibleAnswers.includes(normalizedResponse)) { | |
if (task.scoringOptions["1"] !== undefined && Object.keys(task.scoringOptions).length <= 2) { | |
suggestedScore = 1; | |
} else if (task.scoringOptions["2"] !== undefined) { | |
suggestedScore = 2; | |
} else { | |
suggestedScore = 0; | |
} | |
} | |
} else if (criteria.type === "keywords") { | |
for (const rule of criteria.rules.sort((a,b) => b.score - a.score)) { | |
let keywordsMet = 0; | |
for (const keyword of rule.keywords) { | |
if (normalizedResponse.includes(normalizeText(keyword))) { | |
keywordsMet++; | |
} | |
} | |
const countRequired = rule.countRequired || 1; | |
if (rule.matchType === "all" && keywordsMet === rule.keywords.length) { | |
suggestedScore = rule.score; | |
break; | |
} else if (rule.matchType === "any" && keywordsMet >= countRequired) { | |
suggestedScore = rule.score; | |
break; | |
} | |
} | |
} | |
return suggestedScore; | |
} | |
async function callActualApi(task) { | |
loadingIndicatorEl.classList.remove('hidden'); | |
aiResponseEl.innerHTML = ''; | |
apiErrorEl.classList.add('hidden'); | |
suggestedScoreDisplayEl.classList.add('hidden'); | |
const apiUrl = `${apiConfig.baseUrl.replace(/\/$/, '')}/chat/completions`; | |
const requestBody = { | |
model: apiConfig.model, | |
messages: [ { "role": "user", "content": task.prompt } ], | |
}; | |
try { | |
const response = await fetch(apiUrl, { | |
method: 'POST', | |
headers: { | |
'Content-Type': 'application/json', | |
'Authorization': `Bearer ${apiConfig.apiKey}` | |
}, | |
body: JSON.stringify(requestBody) | |
}); | |
loadingIndicatorEl.classList.add('hidden'); | |
if (!response.ok) { | |
const errorData = await response.json().catch(() => ({})); | |
const errorMessage = `API Error: ${response.status} ${response.statusText}. ${errorData.error?.message || 'No additional details.'}`; | |
throw new Error(errorMessage); | |
} | |
const data = await response.json(); | |
let responseText = "No response content found."; | |
if (data.choices && data.choices.length > 0 && data.choices[0].message && data.choices[0].message.content) { | |
responseText = data.choices[0].message.content.trim(); | |
} | |
const sanitizedText = responseText.replace(/</g, "<").replace(/>/g, ">"); | |
aiResponseEl.innerHTML = `<pre>${sanitizedText}</pre>`; | |
taskStartTime = performance.now(); | |
const autoSuggestedScore = performAutoScoring(task, responseText); | |
const scoreToPreselect = autoScoringEnabledGlobal ? autoSuggestedScore : null; | |
setupScoring(task.scoringOptions, scoreToPreselect); | |
if (autoScoringEnabledGlobal && autoSuggestedScore !== null) { | |
suggestedScoreDisplayEl.innerHTML = `Auto-Score: <span class="suggested-score-badge">${autoSuggestedScore}</span>`; | |
suggestedScoreDisplayEl.classList.remove('hidden'); | |
nextButton.disabled = true; | |
setTimeout(() => { | |
// Ensure we are still on the same task before auto-advancing | |
// (e.g. user didn't manually click next very fast if button was briefly enabled) | |
if (task.prompt === adaptedTasks[currentTaskIndex].prompt) { | |
if (currentTaskIndex < adaptedTasks.length -1) { | |
nextButton.disabled = false; | |
} | |
nextTask(); | |
} | |
}, AUTO_ADVANCE_DELAY); | |
} else { | |
nextButton.disabled = false; | |
} | |
} catch (error) { | |
loadingIndicatorEl.classList.add('hidden'); | |
console.error('API Call Failed:', error); | |
apiErrorEl.textContent = `Failed to get response: ${error.message}`; | |
apiErrorEl.classList.remove('hidden'); | |
aiResponseEl.innerHTML = `<span class="text-red-600 italic">Error fetching response.</span>`; | |
scoringInputEl.innerHTML = `<span class="text-gray-500 italic">Cannot score due to API error. Manual scoring required.</span>`; | |
setupScoring(task.scoringOptions, null); | |
nextButton.disabled = false; | |
taskStartTime = 0; | |
} | |
} | |
function setupScoring(options, preSelectedScoreValue = null) { | |
scoringInputEl.innerHTML = ''; | |
const form = document.createElement('form'); | |
form.id = 'scoringForm'; | |
const sortedScoreValues = Object.keys(options).sort((a,b) => parseInt(a) - parseInt(b)); | |
for (const scoreValue of sortedScoreValues) { | |
const description = options[scoreValue]; | |
const labelDiv = document.createElement('div'); | |
labelDiv.classList.add('p-2', 'rounded', 'hover:bg-yellow-100'); | |
const radioLabel = document.createElement('label'); | |
radioLabel.classList.add('flex', 'items-start', 'cursor-pointer'); | |
const radio = document.createElement('input'); | |
radio.type = 'radio'; | |
radio.name = 'score'; | |
radio.value = scoreValue; | |
radio.required = true; | |
radio.classList.add('form-radio', 'h-4', 'w-4', 'text-indigo-600', 'border-gray-300', 'focus:ring-indigo-500', 'mt-1'); | |
if (autoScoringEnabledGlobal && preSelectedScoreValue !== null && parseInt(scoreValue) === preSelectedScoreValue) { | |
radio.checked = true; | |
} | |
const descriptionDiv = document.createElement('div'); | |
descriptionDiv.classList.add('ml-2'); | |
const scorePointText = document.createElement('span'); | |
scorePointText.textContent = `${scoreValue} point(s): `; | |
scorePointText.classList.add('text-sm', 'font-medium', 'text-gray-800'); | |
const detailedDescriptionText = document.createElement('span'); | |
detailedDescriptionText.textContent = description; | |
detailedDescriptionText.classList.add('text-sm', 'text-gray-700'); | |
descriptionDiv.appendChild(scorePointText); | |
descriptionDiv.appendChild(detailedDescriptionText); | |
radioLabel.appendChild(radio); | |
radioLabel.appendChild(descriptionDiv); | |
labelDiv.appendChild(radioLabel); | |
form.appendChild(labelDiv); | |
} | |
scoringInputEl.appendChild(form); | |
} | |
function getSelectedScore() { | |
const selectedRadio = scoringInputEl.querySelector('input[name="score"]:checked'); | |
return selectedRadio ? parseInt(selectedRadio.value, 10) : null; | |
} | |
function nextTask() { | |
const score = getSelectedScore(); | |
let duration = 0; | |
if (taskStartTime > 0) { | |
duration = performance.now() - taskStartTime; | |
} | |
if (score === null && !autoScoringEnabledGlobal && apiErrorEl.classList.contains('hidden')) { | |
alert("Please select a score before proceeding."); | |
return; | |
} | |
detailedScores.push({ | |
taskIndex: currentTaskIndex, | |
score: score === null ? 0 : score, | |
duration: duration, | |
task: adaptedTasks[currentTaskIndex] | |
}); | |
currentTaskIndex++; | |
displayTask(currentTaskIndex); | |
} | |
function showResults() { | |
testArea.classList.add('hidden'); | |
resultsArea.classList.remove('hidden'); | |
leaderboardArea.classList.remove('hidden'); | |
saveMessageEl.textContent = ''; | |
userNameInput.value = ''; | |
currentTotalScore = detailedScores.reduce((sum, item) => sum + item.score, 0); | |
finalScoreEl.textContent = currentTotalScore; | |
displayLeaderboard(); | |
} | |
function loadLeaderboard() { | |
try { | |
const storedData = localStorage.getItem(LEADERBOARD_KEY); | |
const parsedData = storedData ? JSON.parse(storedData) : []; | |
return Array.isArray(parsedData) ? parsedData : []; | |
} catch (e) { | |
console.error("Error loading or parsing leaderboard from localStorage:", e); | |
localStorage.removeItem(LEADERBOARD_KEY); | |
return []; | |
} | |
} | |
function saveLeaderboard(data) { | |
try { | |
data.sort((a, b) => b.score - a.score); | |
localStorage.setItem(LEADERBOARD_KEY, JSON.stringify(data)); | |
return true; | |
} catch (e) { | |
console.error("Error saving leaderboard to localStorage:", e); | |
if (e.name === 'QuotaExceededError') { | |
alert("Could not save score: LocalStorage quota exceeded."); | |
} else { | |
alert("Could not save score due to a storage error."); | |
} | |
return false; | |
} | |
} | |
function displayLeaderboard() { | |
const leaderboardData = loadLeaderboard(); | |
leaderboardContentEl.innerHTML = ''; | |
if (leaderboardData.length === 0) { | |
leaderboardContentEl.innerHTML = '<p class="text-center text-gray-500 italic">No scores saved yet.</p>'; | |
return; | |
} | |
leaderboardData.sort((a, b) => { | |
if (b.score !== a.score) return b.score - a.score; | |
return new Date(b.date) - new Date(a.date); | |
}); | |
const table = document.createElement('table'); | |
table.classList.add('data-table', 'min-w-full', 'divide-y', 'divide-gray-200'); | |
const thead = table.createTHead(); | |
const headerRow = thead.insertRow(); | |
const headers = ['Rank', 'Name', 'Score', 'Date', 'Details']; | |
headers.forEach(text => { | |
const th = document.createElement('th'); | |
th.textContent = text; | |
th.classList.add('px-4', 'py-2', 'text-left', 'text-xs', 'font-medium', 'text-gray-500', 'uppercase', 'tracking-wider'); | |
if (text === 'Score') th.classList.add('text-right'); | |
if (text === 'Rank' || text === 'Details') th.classList.add('text-center'); | |
headerRow.appendChild(th); | |
}); | |
const tbody = table.createTBody(); | |
tbody.classList.add('bg-white', 'divide-y', 'divide-gray-200'); | |
const maxEntriesToShow = 20; | |
leaderboardData.slice(0, maxEntriesToShow).forEach((entry, index) => { | |
const row = tbody.insertRow(); | |
row.classList.add('hover:bg-gray-50'); | |
row.insertCell().outerHTML = `<td class="px-4 py-2 whitespace-nowrap text-sm text-gray-500 text-center">${index + 1}</td>`; | |
row.insertCell().outerHTML = `<td class="px-4 py-2 whitespace-nowrap text-sm font-medium text-gray-900">${entry.name}</td>`; | |
row.insertCell().outerHTML = `<td class="px-4 py-2 whitespace-nowrap text-sm text-gray-500 text-right">${entry.score}</td>`; | |
row.insertCell().outerHTML = `<td class="px-4 py-2 whitespace-nowrap text-sm text-gray-500">${new Date(entry.date).toLocaleDateString()}</td>`; | |
const detailsCell = row.insertCell(); | |
detailsCell.classList.add('px-4', 'py-2', 'whitespace-nowrap', 'text-sm', 'text-gray-500', 'text-center'); | |
if (entry.details && entry.details.length > 0) { | |
const detailsButton = document.createElement('button'); | |
detailsButton.textContent = 'View'; | |
detailsButton.classList.add('details-button'); | |
detailsButton.onclick = () => showDetails(entry); | |
detailsCell.appendChild(detailsButton); | |
} else { | |
detailsCell.textContent = 'N/A'; | |
} | |
}); | |
leaderboardContentEl.appendChild(table); | |
} | |
function saveCurrentScore() { | |
const userName = userNameInput.value.trim(); | |
if (!userName) { | |
alert("Please enter a name to save the score."); | |
return; | |
} | |
const leaderboardData = loadLeaderboard(); | |
const newEntry = { | |
id: Date.now().toString(), | |
name: userName, | |
score: currentTotalScore, | |
date: new Date().toISOString(), | |
details: detailedScores | |
}; | |
leaderboardData.push(newEntry); | |
if(saveLeaderboard(leaderboardData)) { | |
saveMessageEl.textContent = `Score saved for ${userName}!`; | |
userNameInput.value = ''; | |
displayLeaderboard(); | |
} else { | |
saveMessageEl.textContent = `Failed to save score.`; | |
} | |
} | |
function clearLeaderboard() { | |
if (confirm("Are you sure you want to clear the entire leaderboard? This cannot be undone.")) { | |
try { | |
localStorage.removeItem(LEADERBOARD_KEY); | |
displayLeaderboard(); | |
alert("Leaderboard cleared."); | |
} catch (e) { | |
console.error("Error clearing leaderboard:", e); | |
alert("Could not clear leaderboard."); | |
} | |
} | |
} | |
function showDetails(entry) { | |
if (!entry || !Array.isArray(entry.details) || entry.details.length === 0) { | |
alert("No details available for this entry."); | |
return; | |
} | |
detailsModalTitle.textContent = `Task Details for ${entry.name} (${new Date(entry.date).toLocaleDateString()})`; | |
detailsModalContent.innerHTML = ''; | |
const table = document.createElement('table'); | |
table.classList.add('data-table', 'min-w-full', 'divide-y', 'divide-gray-200'); | |
const thead = table.createTHead(); | |
const headerRow = thead.insertRow(); | |
const headers = ['#', 'Type', 'Prompt', 'Score', 'Time (s)']; | |
headers.forEach(text => { | |
const th = document.createElement('th'); | |
th.textContent = text; | |
th.classList.add('px-3', 'py-2', 'text-left', 'text-xs', 'font-medium', 'text-gray-500', 'uppercase', 'tracking-wider'); | |
if (text === 'Score' || text === 'Time (s)') th.classList.add('text-right'); | |
if (text === '#') th.classList.add('text-center'); | |
headerRow.appendChild(th); | |
}); | |
const tbody = table.createTBody(); | |
tbody.classList.add('bg-white', 'divide-y', 'divide-gray-200'); | |
entry.details.forEach(item => { | |
const row = tbody.insertRow(); | |
row.classList.add('hover:bg-gray-50'); | |
row.insertCell().outerHTML = `<td class="px-3 py-2 whitespace-nowrap text-sm text-gray-500 text-center">${item.taskIndex + 1}</td>`; | |
row.insertCell().outerHTML = `<td class="px-3 py-2 whitespace-nowrap text-sm text-gray-500">${item.task.type}</td>`; | |
row.insertCell().outerHTML = `<td class="px-3 py-2 text-sm text-gray-500 prompt-cell">${item.task.prompt}</td>`; | |
row.insertCell().outerHTML = `<td class="px-3 py-2 whitespace-nowrap text-sm text-gray-500 text-right">${item.score}</td>`; | |
const durationSeconds = item.duration > 0 ? (item.duration / 1000).toFixed(2) : 'N/A'; | |
row.insertCell().outerHTML = `<td class="px-3 py-2 whitespace-nowrap text-sm text-gray-500 text-right">${durationSeconds}</td>`; | |
}); | |
detailsModalContent.appendChild(table); | |
detailsModal.style.display = 'block'; | |
} | |
function closeDetailsModal() { | |
detailsModal.style.display = 'none'; | |
} | |
// --- Event Listeners --- | |
startButton.addEventListener('click', () => { | |
const baseUrl = baseUrlInput.value.trim(); | |
const apiKey = apiKeyInput.value.trim(); | |
const modelName = modelNameInput.value.trim(); | |
autoScoringEnabledGlobal = enableAutoScoringCheckbox.checked; | |
let hasError = false; | |
configErrorEl.textContent = ''; | |
configErrorEl.classList.add('hidden'); | |
if (!baseUrl || !apiKey || !modelName) { | |
configErrorEl.textContent = 'API Base URL, API Key, and Model Name are required.'; | |
hasError = true; | |
} | |
if (hasError) { | |
configErrorEl.classList.remove('hidden'); | |
return; | |
} | |
apiConfig.baseUrl = baseUrl; | |
apiConfig.apiKey = apiKey; | |
apiConfig.model = modelName; | |
if (autoScoringEnabledGlobal) { | |
scoringInstructionsEl.textContent = "Auto-score will be suggested and submitted shortly. Timer starts after response."; | |
} else { | |
scoringInstructionsEl.textContent = "Evaluate the AI's response. Timer starts after response. Auto-score is NOT active."; | |
} | |
configArea.classList.add('hidden'); | |
startControl.classList.add('hidden'); | |
leaderboardArea.classList.add('hidden'); | |
testArea.classList.remove('hidden'); | |
resultsArea.classList.add('hidden'); | |
currentTaskIndex = 0; | |
detailedScores = []; | |
currentTotalScore = 0; | |
displayTask(currentTaskIndex); | |
}); | |
nextButton.addEventListener('click', nextTask); | |
saveScoreButton.addEventListener('click', saveCurrentScore); | |
clearLeaderboardButton.addEventListener('click', clearLeaderboard); | |
modalCloseButton.addEventListener('click', closeDetailsModal); | |
window.addEventListener('click', (event) => { | |
if (event.target == detailsModal) closeDetailsModal(); | |
}); | |
// --- Initial Load --- | |
document.addEventListener('DOMContentLoaded', () => { | |
displayLeaderboard(); | |
scoringInstructionsEl.textContent = "Evaluate the AI's response. Timer starts after response. Auto-score is NOT active."; | |
}); | |
</script> | |
</body> | |
</html> | |