allignment-bench / index.html
cryptonaut's picture
Update index.html
4dea16f verified
<!DOCTYPE html>
<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">&times;</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, "&lt;").replace(/>/g, "&gt;");
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>