Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>LLM Hill Climber - HuggingFace Spaces</title> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css"> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.9.1/chart.min.js"></script> | |
<style> | |
* { | |
margin: 0; | |
padding: 0; | |
box-sizing: border-box; | |
} | |
body { | |
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
min-height: 100vh; | |
color: #333; | |
} | |
.header { | |
background: rgba(255, 255, 255, 0.1); | |
backdrop-filter: blur(10px); | |
padding: 20px; | |
text-align: center; | |
border-bottom: 1px solid rgba(255, 255, 255, 0.2); | |
} | |
.header h1 { | |
color: white; | |
font-size: 2.5em; | |
margin-bottom: 10px; | |
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3); | |
} | |
.header p { | |
color: rgba(255, 255, 255, 0.9); | |
font-size: 1.1em; | |
} | |
.spaces-info { | |
background: rgba(255, 255, 255, 0.95); | |
margin: 20px auto; | |
max-width: 1200px; | |
padding: 15px; | |
border-radius: 10px; | |
border-left: 4px solid #667eea; | |
} | |
.container { | |
max-width: 1400px; | |
margin: 0 auto; | |
padding: 20px; | |
display: grid; | |
grid-template-columns: 1fr 1fr; | |
gap: 20px; | |
min-height: calc(100vh - 200px); | |
} | |
.panel { | |
background: rgba(255, 255, 255, 0.95); | |
border-radius: 20px; | |
padding: 25px; | |
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1); | |
backdrop-filter: blur(10px); | |
} | |
.panel h2 { | |
color: #333; | |
margin-bottom: 20px; | |
font-size: 1.5em; | |
border-bottom: 3px solid #667eea; | |
padding-bottom: 10px; | |
} | |
.model-info { | |
background: #e8f5e8; | |
padding: 15px; | |
border-radius: 8px; | |
margin-bottom: 20px; | |
border-left: 4px solid #4caf50; | |
} | |
.problem-selector { | |
margin-bottom: 20px; | |
} | |
.problem-selector select { | |
width: 100%; | |
padding: 12px; | |
border: 2px solid #e0e0e0; | |
border-radius: 8px; | |
font-size: 16px; | |
background: white; | |
} | |
.problem-selector select:focus { | |
border-color: #667eea; | |
outline: none; | |
} | |
.problem-details { | |
background: #f8f9fa; | |
padding: 20px; | |
border-radius: 12px; | |
margin-bottom: 20px; | |
border-left: 4px solid #667eea; | |
} | |
.problem-details h3 { | |
color: #333; | |
margin-bottom: 10px; | |
} | |
.problem-details p { | |
line-height: 1.6; | |
margin-bottom: 15px; | |
} | |
.test-cases { | |
background: #2d3748; | |
color: #e2e8f0; | |
padding: 15px; | |
border-radius: 8px; | |
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; | |
font-size: 14px; | |
overflow-x: auto; | |
white-space: pre-wrap; | |
} | |
.controls { | |
display: flex; | |
gap: 15px; | |
margin-bottom: 20px; | |
flex-wrap: wrap; | |
} | |
.btn { | |
padding: 12px 24px; | |
border: none; | |
border-radius: 8px; | |
cursor: pointer; | |
font-size: 16px; | |
font-weight: bold; | |
transition: all 0.3s ease; | |
text-transform: uppercase; | |
letter-spacing: 0.5px; | |
} | |
.btn-primary { | |
background: linear-gradient(45deg, #667eea, #764ba2); | |
color: white; | |
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3); | |
} | |
.btn-primary:hover:not(:disabled) { | |
transform: translateY(-2px); | |
box-shadow: 0 8px 25px rgba(102, 126, 234, 0.4); | |
} | |
.btn-danger { | |
background: linear-gradient(45deg, #ff6b6b, #ee5a24); | |
color: white; | |
box-shadow: 0 4px 15px rgba(255, 107, 107, 0.3); | |
} | |
.btn-danger:hover:not(:disabled) { | |
transform: translateY(-2px); | |
box-shadow: 0 8px 25px rgba(255, 107, 107, 0.4); | |
} | |
.btn:disabled { | |
opacity: 0.6; | |
cursor: not-allowed; | |
transform: none; | |
} | |
.settings { | |
display: grid; | |
grid-template-columns: 1fr 1fr; | |
gap: 15px; | |
margin-bottom: 20px; | |
} | |
.setting-group { | |
display: flex; | |
flex-direction: column; | |
} | |
.setting-group label { | |
font-weight: bold; | |
margin-bottom: 5px; | |
color: #555; | |
} | |
.setting-group input { | |
padding: 8px; | |
border: 2px solid #e0e0e0; | |
border-radius: 6px; | |
font-size: 14px; | |
} | |
.setting-group input:focus { | |
border-color: #667eea; | |
outline: none; | |
} | |
.progress-section { | |
margin-bottom: 20px; | |
} | |
.progress-bar { | |
width: 100%; | |
height: 20px; | |
background: #e0e0e0; | |
border-radius: 10px; | |
overflow: hidden; | |
margin-bottom: 10px; | |
} | |
.progress-fill { | |
height: 100%; | |
background: linear-gradient(45deg, #667eea, #764ba2); | |
width: 0%; | |
transition: width 0.3s ease; | |
} | |
.progress-text { | |
text-align: center; | |
font-weight: bold; | |
color: #555; | |
} | |
.evolution-log { | |
background: #2d3748; | |
color: #e2e8f0; | |
padding: 15px; | |
border-radius: 8px; | |
height: 250px; | |
overflow-y: auto; | |
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; | |
font-size: 12px; | |
line-height: 1.4; | |
margin-bottom: 20px; | |
} | |
.current-solution { | |
background: #f8f9fa; | |
border: 2px solid #e0e0e0; | |
border-radius: 8px; | |
padding: 15px; | |
max-height: 300px; | |
overflow-y: auto; | |
} | |
.current-solution pre { | |
background: #2d3748; | |
color: #e2e8f0; | |
padding: 15px; | |
border-radius: 8px; | |
overflow-x: auto; | |
margin: 0; | |
} | |
.fitness-chart { | |
background: white; | |
padding: 15px; | |
border-radius: 8px; | |
border: 2px solid #e0e0e0; | |
margin-bottom: 20px; | |
} | |
.status { | |
padding: 15px; | |
border-radius: 8px; | |
margin-bottom: 20px; | |
font-weight: bold; | |
text-align: center; | |
} | |
.status-idle { | |
background: #e3f2fd; | |
color: #1976d2; | |
border: 2px solid #1976d2; | |
} | |
.status-running { | |
background: #fff3e0; | |
color: #f57c00; | |
border: 2px solid #f57c00; | |
} | |
.status-complete { | |
background: #e8f5e8; | |
color: #2e7d32; | |
border: 2px solid #2e7d32; | |
} | |
.status-error { | |
background: #ffebee; | |
color: #c62828; | |
border: 2px solid #c62828; | |
} | |
.spinner { | |
display: inline-block; | |
width: 20px; | |
height: 20px; | |
border: 2px solid #f3f3f3; | |
border-top: 2px solid #667eea; | |
border-radius: 50%; | |
animation: spin 1s linear infinite; | |
margin-right: 10px; | |
} | |
@keyframes spin { | |
0% { transform: rotate(0deg); } | |
100% { transform: rotate(360deg); } | |
} | |
.best-solution { | |
background: #e8f5e8; | |
border: 2px solid #4caf50; | |
border-radius: 8px; | |
padding: 20px; | |
margin-top: 20px; | |
} | |
.best-solution h3 { | |
color: #2e7d32; | |
margin-bottom: 15px; | |
} | |
.api-efficiency { | |
background: #fff3e0; | |
padding: 15px; | |
border-radius: 8px; | |
margin-bottom: 20px; | |
border-left: 4px solid #f57c00; | |
} | |
@media (max-width: 1200px) { | |
.container { | |
grid-template-columns: 1fr; | |
gap: 15px; | |
} | |
} | |
@media (max-width: 768px) { | |
.header h1 { | |
font-size: 2em; | |
} | |
.controls { | |
flex-direction: column; | |
} | |
.settings { | |
grid-template-columns: 1fr; | |
} | |
} | |
</style> | |
</head> | |
<body> | |
<div class="header"> | |
<h1>𧬠LLM Hill Climber</h1> | |
<p>Efficient program evolution using StarCoder2-3B on HuggingFace Spaces</p> | |
</div> | |
<div class="spaces-info"> | |
<h3>π HuggingFace Spaces Deployment</h3> | |
<p><strong>Model:</strong> bigcode/starcoder2-3b (specialized coding AI) β’ <strong>Strategy:</strong> Single mutation per generation β’ <strong>Auth:</strong> Add your HF token above or get one free at <a href="https://huggingface.co/settings/tokens" target="_blank" style="color: #667eea;">huggingface.co/settings/tokens</a></p> | |
</div> | |
<div class="container"> | |
<!-- Left Panel: Problem Setup & Controls --> | |
<div class="panel"> | |
<h2>π― Problem Configuration</h2> | |
<div class="model-info"> | |
<h4>π€ AI Model Selection</h4> | |
<select id="modelSelect" style="width: 100%; padding: 8px; border: 2px solid #e0e0e0; border-radius: 6px; margin-bottom: 10px;"> | |
<option value="starcoder2-3b">StarCoder2-3B (Recommended)</option> | |
<option value="starcoder2-7b">StarCoder2-7B (Better quality)</option> | |
<option value="codellama-7b">CodeLlama-7B (Alternative)</option> | |
<option value="codebert-base">CodeBERT-Base (Fallback)</option> | |
</select> | |
<p style="margin-bottom: 10px;">Specialized coding models by BigCode and Meta. Excellent at JavaScript generation.</p> | |
<div style="margin-top: 10px;"> | |
<label for="hfToken" style="font-weight: bold; display: block; margin-bottom: 5px;">HuggingFace Token:</label> | |
<input type="password" id="hfToken" placeholder="hf_xxxxx... (required for API access)" style="width: 100%; padding: 8px; border: 2px solid #e0e0e0; border-radius: 6px;"> | |
<small style="color: #666;">Get a free token at <a href="https://huggingface.co/settings/tokens" target="_blank">huggingface.co/settings/tokens</a></small> | |
</div> | |
</div> | |
<div class="problem-selector"> | |
<select id="problemSelect"> | |
<option value="">Select a coding problem...</option> | |
<option value="palindrome">Palindrome Checker</option> | |
<option value="two_sum">Two Sum Problem</option> | |
<option value="fibonacci">Fibonacci Generator</option> | |
<option value="prime_check">Prime Number Checker</option> | |
<option value="reverse_string">String Reversal</option> | |
<option value="max_subarray">Maximum Subarray Sum</option> | |
<option value="binary_search">Binary Search</option> | |
<option value="anagram_check">Anagram Checker</option> | |
</select> | |
</div> | |
<div class="problem-details" id="problemDetails"> | |
<h3>Select a problem to begin</h3> | |
<p>Choose a programming challenge for the AI to evolve solutions for.</p> | |
</div> | |
<div class="settings"> | |
<div class="setting-group"> | |
<label for="maxGenerations">Max Generations:</label> | |
<input type="number" id="maxGenerations" value="12" min="5" max="25"> | |
</div> | |
<div class="setting-group"> | |
<label for="temperature">Temperature:</label> | |
<input type="range" id="temperature" value="0.3" min="0.1" max="0.8" step="0.1"> | |
</div> | |
<div class="setting-group"> | |
<label for="maxTokens">Max Tokens:</label> | |
<input type="number" id="maxTokens" value="384" min="256" max="512" step="64"> | |
</div> | |
<div class="setting-group"> | |
<label for="retryDelay">Retry Delay (ms):</label> | |
<input type="number" id="retryDelay" value="2000" min="1000" max="5000" step="500"> | |
</div> | |
</div> | |
<div class="api-efficiency"> | |
<h4>β‘ API Efficiency</h4> | |
<p><strong>Single Mutation Strategy:</strong> One API call per generation for maximum efficiency. Estimated total: <span id="estimatedCalls">12</span> API calls.</p> | |
</div> | |
<div class="controls"> | |
<button class="btn btn-primary" id="startBtn">π Start Evolution</button> | |
<button class="btn btn-danger" id="stopBtn" disabled>βΉοΈ Stop</button> | |
</div> | |
<div class="status status-idle" id="status"> | |
Ready to evolve JavaScript programs | |
</div> | |
<div class="progress-section"> | |
<div class="progress-bar"> | |
<div class="progress-fill" id="progressFill"></div> | |
</div> | |
<div class="progress-text" id="progressText">0 / 0 generations</div> | |
</div> | |
<h2>π Fitness Evolution</h2> | |
<div class="fitness-chart"> | |
<canvas id="fitnessChart" width="400" height="200"></canvas> | |
</div> | |
</div> | |
<!-- Right Panel: Evolution Progress & Results --> | |
<div class="panel"> | |
<h2>π¬ Evolution Progress</h2> | |
<div class="evolution-log" id="evolutionLog"> | |
Ready to start evolution process... | |
𧬠Hill Climber Algorithm: | |
β’ Generate initial solution with StarCoder2-3B | |
β’ Each generation: create one mutation | |
β’ Keep mutation if fitness improves | |
β’ Stop when perfect solution found or max generations reached | |
</div> | |
<h2>π» Current Best Solution</h2> | |
<div class="current-solution" id="currentSolution"> | |
<p style="text-align: center; color: #666; padding: 20px;"> | |
No solution generated yet | |
</p> | |
</div> | |
<div class="best-solution" id="bestSolution" style="display: none;"> | |
<h3>π Final Best Solution</h3> | |
<div id="bestSolutionCode"></div> | |
<div id="bestSolutionStats"></div> | |
</div> | |
</div> | |
</div> | |
<script> | |
// Model options for different scenarios | |
const MODELS = { | |
'starcoder2-3b': 'https://api-inference.huggingface.co/models/bigcode/starcoder2-3b', | |
'starcoder2-7b': 'https://api-inference.huggingface.co/models/bigcode/starcoder2-7b', | |
'codellama-7b': 'https://api-inference.huggingface.co/models/codellama/CodeLlama-7b-Instruct-hf', | |
'codebert-base': 'https://api-inference.huggingface.co/models/microsoft/codebert-base' | |
}; | |
// Default model | |
let MODEL_URL = MODELS['starcoder2-3b']; | |
// Problem definitions optimized for StarCoder2 | |
const PROBLEMS = { | |
palindrome: { | |
name: "Palindrome Checker", | |
description: "Write a JavaScript function that checks if a string is a palindrome (reads the same forwards and backwards). Ignore case and non-alphanumeric characters.", | |
functionName: "isPalindrome", | |
parameters: "s", | |
testCases: [ | |
{ input: ["racecar"], expected: true }, | |
{ input: ["A man a plan a canal Panama"], expected: true }, | |
{ input: ["race a car"], expected: false }, | |
{ input: [""], expected: true }, | |
{ input: ["Madam"], expected: true } | |
] | |
}, | |
two_sum: { | |
name: "Two Sum Problem", | |
description: "Given an array of integers and a target sum, return the indices of two numbers that add up to the target. Assume exactly one solution exists.", | |
functionName: "twoSum", | |
parameters: "nums, target", | |
testCases: [ | |
{ input: [[2, 7, 11, 15], 9], expected: [0, 1] }, | |
{ input: [[3, 2, 4], 6], expected: [1, 2] }, | |
{ input: [[3, 3], 6], expected: [0, 1] }, | |
{ input: [[1, 5, 3, 7], 8], expected: [1, 3] } | |
] | |
}, | |
fibonacci: { | |
name: "Fibonacci Generator", | |
description: "Generate the nth Fibonacci number efficiently. Handle edge cases for n=0 and n=1.", | |
functionName: "fibonacci", | |
parameters: "n", | |
testCases: [ | |
{ input: [0], expected: 0 }, | |
{ input: [1], expected: 1 }, | |
{ input: [2], expected: 1 }, | |
{ input: [10], expected: 55 }, | |
{ input: [15], expected: 610 } | |
] | |
}, | |
prime_check: { | |
name: "Prime Number Checker", | |
description: "Determine if a given positive integer is a prime number. A prime number is only divisible by 1 and itself.", | |
functionName: "isPrime", | |
parameters: "n", | |
testCases: [ | |
{ input: [2], expected: true }, | |
{ input: [3], expected: true }, | |
{ input: [4], expected: false }, | |
{ input: [17], expected: true }, | |
{ input: [25], expected: false }, | |
{ input: [1], expected: false } | |
] | |
}, | |
reverse_string: { | |
name: "String Reversal", | |
description: "Write a function that reverses a string. Handle empty strings appropriately.", | |
functionName: "reverseString", | |
parameters: "s", | |
testCases: [ | |
{ input: ["hello"], expected: "olleh" }, | |
{ input: ["world"], expected: "dlrow" }, | |
{ input: [""], expected: "" }, | |
{ input: ["a"], expected: "a" }, | |
{ input: ["JavaScript"], expected: "tpircSavaJ" } | |
] | |
}, | |
max_subarray: { | |
name: "Maximum Subarray Sum", | |
description: "Find the contiguous subarray with the largest sum and return the sum (Kadane's algorithm).", | |
functionName: "maxSubarraySum", | |
parameters: "nums", | |
testCases: [ | |
{ input: [[-2, 1, -3, 4, -1, 2, 1, -5, 4]], expected: 6 }, | |
{ input: [[1]], expected: 1 }, | |
{ input: [[5, 4, -1, 7, 8]], expected: 23 }, | |
{ input: [[-1]], expected: -1 } | |
] | |
}, | |
binary_search: { | |
name: "Binary Search", | |
description: "Implement binary search to find the index of a target value in a sorted array. Return -1 if not found.", | |
functionName: "binarySearch", | |
parameters: "arr, target", | |
testCases: [ | |
{ input: [[-1, 0, 3, 5, 9, 12], 9], expected: 4 }, | |
{ input: [[-1, 0, 3, 5, 9, 12], 2], expected: -1 }, | |
{ input: [[1, 2, 3, 4, 5], 1], expected: 0 }, | |
{ input: [[1, 2, 3, 4, 5], 5], expected: 4 } | |
] | |
}, | |
anagram_check: { | |
name: "Anagram Checker", | |
description: "Check if two strings are anagrams of each other. Ignore case and spaces.", | |
functionName: "areAnagrams", | |
parameters: "s1, s2", | |
testCases: [ | |
{ input: ["listen", "silent"], expected: true }, | |
{ input: ["evil", "vile"], expected: true }, | |
{ input: ["hello", "bello"], expected: false }, | |
{ input: ["A gentleman", "Elegant man"], expected: true } | |
] | |
} | |
}; | |
// Hill Climber state | |
let isRunning = false; | |
let currentGeneration = 0; | |
let maxGenerations = 12; | |
let currentProblem = null; | |
let currentSolution = null; | |
let bestSolution = null; | |
let bestFitness = -1; | |
let fitnessHistory = []; | |
let chart = null; | |
let apiCallCount = 0; | |
// DOM elements | |
const problemSelect = document.getElementById('problemSelect'); | |
const problemDetails = document.getElementById('problemDetails'); | |
const startBtn = document.getElementById('startBtn'); | |
const stopBtn = document.getElementById('stopBtn'); | |
const status = document.getElementById('status'); | |
const progressFill = document.getElementById('progressFill'); | |
const progressText = document.getElementById('progressText'); | |
const evolutionLog = document.getElementById('evolutionLog'); | |
const currentSolutionDiv = document.getElementById('currentSolution'); | |
const bestSolutionDiv = document.getElementById('bestSolution'); | |
const estimatedCalls = document.getElementById('estimatedCalls'); | |
// Initialize | |
initializeChart(); | |
setupEventListeners(); | |
function initializeChart() { | |
const ctx = document.getElementById('fitnessChart').getContext('2d'); | |
chart = new Chart(ctx, { | |
type: 'line', | |
data: { | |
labels: [], | |
datasets: [{ | |
label: 'Fitness Score', | |
data: [], | |
borderColor: '#667eea', | |
backgroundColor: 'rgba(102, 126, 234, 0.1)', | |
tension: 0.4, | |
fill: true | |
}] | |
}, | |
options: { | |
responsive: true, | |
scales: { | |
y: { | |
beginAtZero: true, | |
max: 1 | |
} | |
}, | |
plugins: { | |
legend: { | |
display: false | |
} | |
} | |
} | |
}); | |
} | |
function setupEventListeners() { | |
problemSelect.addEventListener('change', handleProblemSelect); | |
startBtn.addEventListener('click', startEvolution); | |
stopBtn.addEventListener('click', stopEvolution); | |
// Model selector | |
document.getElementById('modelSelect').addEventListener('change', (e) => { | |
MODEL_URL = MODELS[e.target.value]; | |
log(`Model changed to: ${e.target.options[e.target.selectedIndex].text}`); | |
}); | |
// Update estimated API calls when generations change | |
document.getElementById('maxGenerations').addEventListener('input', (e) => { | |
estimatedCalls.textContent = e.target.value; | |
}); | |
} | |
function handleProblemSelect() { | |
const selectedProblem = problemSelect.value; | |
if (selectedProblem) { | |
currentProblem = PROBLEMS[selectedProblem]; | |
displayProblemDetails(currentProblem); | |
startBtn.disabled = false; | |
} else { | |
currentProblem = null; | |
problemDetails.innerHTML = ` | |
<h3>Select a problem to begin</h3> | |
<p>Choose a programming challenge for the AI to evolve solutions for.</p> | |
`; | |
startBtn.disabled = true; | |
} | |
} | |
function displayProblemDetails(problem) { | |
const testCasesText = problem.testCases.map((tc, i) => | |
`Test ${i + 1}: ${problem.functionName}(${tc.input.map(inp => JSON.stringify(inp)).join(', ')}) β ${JSON.stringify(tc.expected)}` | |
).join('\n'); | |
problemDetails.innerHTML = ` | |
<h3>${problem.name}</h3> | |
<p><strong>Description:</strong> ${problem.description}</p> | |
<p><strong>Function signature:</strong> <code>function ${problem.functionName}(${problem.parameters}) { ... }</code></p> | |
<div class="test-cases">${testCasesText}</div> | |
`; | |
} | |
async function startEvolution() { | |
if (!currentProblem) { | |
alert('Please select a problem first'); | |
return; | |
} | |
// Check for HF token | |
const hfToken = document.getElementById('hfToken').value.trim(); | |
if (!hfToken) { | |
alert('Please add your HuggingFace token above. Get a free one at: https://huggingface.co/settings/tokens'); | |
return; | |
} | |
if (!hfToken.startsWith('hf_')) { | |
alert('Invalid token format. HuggingFace tokens start with "hf_"'); | |
return; | |
} | |
isRunning = true; | |
currentGeneration = 0; | |
maxGenerations = parseInt(document.getElementById('maxGenerations').value); | |
fitnessHistory = []; | |
bestSolution = null; | |
bestFitness = -1; | |
apiCallCount = 0; | |
updateUI(); | |
updateStatus('running', 'Evolution in progress...'); | |
log('π Starting evolution with StarCoder2-3B...'); | |
log(`Problem: ${currentProblem.name}`); | |
log(`Strategy: Single mutation per generation (API efficient)`); | |
log(`Max generations: ${maxGenerations}`); | |
log(''); | |
try { | |
// Step 1: Generate initial solution | |
log('π¬ Generating initial solution...'); | |
currentSolution = await generateInitialSolution(); | |
apiCallCount++; | |
if (!currentSolution) { | |
throw new Error('Failed to generate initial solution'); | |
} | |
log(`β Initial solution generated (API calls: ${apiCallCount})`); | |
// Evaluate initial solution | |
let currentFitness = evaluateSolution(currentSolution); | |
fitnessHistory.push(currentFitness); | |
bestSolution = currentSolution; | |
bestFitness = currentFitness; | |
log(`Initial fitness: ${currentFitness.toFixed(3)} (${Math.round(currentFitness * currentProblem.testCases.length)}/${currentProblem.testCases.length} tests)`); | |
updateProgress(); | |
updateChart(); | |
updateCurrentSolution(); | |
// Step 2: Evolution loop | |
for (currentGeneration = 1; currentGeneration <= maxGenerations && isRunning; currentGeneration++) { | |
log(`\nπ Generation ${currentGeneration}/${maxGenerations}`); | |
// Check if perfect solution found | |
if (currentFitness >= 1.0) { | |
log('π Perfect solution found! Stopping evolution.'); | |
break; | |
} | |
// Generate single mutation | |
log('𧬠Generating mutation...'); | |
const mutatedSolution = await mutateSolution(currentSolution, currentFitness); | |
apiCallCount++; | |
if (!mutatedSolution) { | |
log('β οΈ Mutation failed, keeping current solution'); | |
continue; | |
} | |
// Evaluate mutation | |
const mutatedFitness = evaluateSolution(mutatedSolution); | |
log(`Mutation fitness: ${mutatedFitness.toFixed(3)} (${Math.round(mutatedFitness * currentProblem.testCases.length)}/${currentProblem.testCases.length} tests)`); | |
// Hill Climber decision: accept if better or equal | |
if (mutatedFitness >= currentFitness) { | |
currentSolution = mutatedSolution; | |
currentFitness = mutatedFitness; | |
log('β Mutation accepted (fitness improved or maintained)'); | |
// Update best solution | |
if (mutatedFitness > bestFitness) { | |
bestSolution = mutatedSolution; | |
bestFitness = mutatedFitness; | |
log('β¨ New best solution found!'); | |
} | |
} else { | |
log('β Mutation rejected (fitness decreased)'); | |
} | |
fitnessHistory.push(Math.max(currentFitness, bestFitness)); | |
log(`API calls used: ${apiCallCount}`); | |
updateProgress(); | |
updateChart(); | |
updateCurrentSolution(); | |
// Add delay to be respectful to API | |
const delay = parseInt(document.getElementById('retryDelay').value); | |
await new Promise(resolve => setTimeout(resolve, delay)); | |
} | |
// Evolution complete | |
if (isRunning) { | |
updateStatus('complete', `Evolution completed! Best fitness: ${bestFitness.toFixed(3)}`); | |
log(`\nπ Evolution completed!`); | |
log(`Best fitness: ${bestFitness.toFixed(3)}`); | |
log(`Total API calls: ${apiCallCount}`); | |
showBestSolution(); | |
} | |
} catch (error) { | |
updateStatus('error', `Error: ${error.message}`); | |
log(`β Error: ${error.message}`); | |
console.error('Evolution error:', error); | |
} finally { | |
isRunning = false; | |
updateUI(); | |
} | |
} | |
function stopEvolution() { | |
isRunning = false; | |
updateStatus('idle', 'Evolution stopped by user'); | |
log('\nβΉοΈ Evolution stopped by user'); | |
updateUI(); | |
} | |
async function generateInitialSolution() { | |
const prompt = `Write a JavaScript function to solve this coding problem: | |
**Problem:** ${currentProblem.description} | |
**Function signature:** function ${currentProblem.functionName}(${currentProblem.parameters}) { } | |
**Test cases:** | |
${currentProblem.testCases.map(tc => | |
`${currentProblem.functionName}(${tc.input.map(inp => JSON.stringify(inp)).join(', ')}) should return ${JSON.stringify(tc.expected)}` | |
).join('\n')} | |
Write only the complete JavaScript function with proper logic to pass all test cases:`; | |
return await callStarCoder(prompt); | |
} | |
async function mutateSolution(solution, currentFitness) { | |
const failingCases = []; | |
const passingCases = []; | |
for (const testCase of currentProblem.testCases) { | |
const result = executeTestCase(solution, testCase); | |
if (result.passed) { | |
passingCases.push(testCase); | |
} else { | |
failingCases.push({...testCase, actualOutput: result.actualOutput}); | |
} | |
} | |
let prompt; | |
if (failingCases.length > 0) { | |
// Focus on fixing failing cases | |
prompt = `Fix this JavaScript function to pass all test cases: | |
**Current function:** | |
${solution} | |
**Failing test cases:** | |
${failingCases.map(fc => | |
`${currentProblem.functionName}(${fc.input.map(inp => JSON.stringify(inp)).join(', ')}) returns ${JSON.stringify(fc.actualOutput)} but should return ${JSON.stringify(fc.expected)}` | |
).join('\n')} | |
**Passing test cases:** ${passingCases.length}/${currentProblem.testCases.length} | |
Write the improved JavaScript function:`; | |
} else { | |
// Optimize already working solution | |
prompt = `Optimize this working JavaScript function for better performance or readability: | |
**Current function:** | |
${solution} | |
**All test cases are passing** | |
Write an optimized version of the JavaScript function:`; | |
} | |
return await callStarCoder(prompt); | |
} | |
async function callStarCoder(prompt) { | |
const maxTokens = parseInt(document.getElementById('maxTokens').value); | |
const temperature = parseFloat(document.getElementById('temperature').value); | |
// Get HF token from user input | |
const hfToken = document.getElementById('hfToken').value.trim(); | |
try { | |
const response = await fetch(MODEL_URL, { | |
method: 'POST', | |
headers: { | |
'Content-Type': 'application/json', | |
'Authorization': `Bearer ${hfToken}` | |
}, | |
body: JSON.stringify({ | |
inputs: prompt, | |
parameters: { | |
max_new_tokens: maxTokens, | |
temperature: temperature, | |
top_p: 0.9, | |
do_sample: true, | |
return_full_text: false | |
} | |
}) | |
}); | |
if (!response.ok) { | |
if (response.status === 401) { | |
throw new Error('Authentication failed. Please add a valid HuggingFace token above.'); | |
} | |
if (response.status === 503) { | |
log('β οΈ Model loading, retrying in 10s...'); | |
await new Promise(resolve => setTimeout(resolve, 10000)); | |
return await callStarCoder(prompt); | |
} | |
if (response.status === 429) { | |
throw new Error('Rate limit exceeded. Please wait and try again, or use your own HF token.'); | |
} | |
throw new Error(`API request failed: ${response.status} ${response.statusText}`); | |
} | |
const data = await response.json(); | |
if (data.error) { | |
throw new Error(data.error); | |
} | |
let generatedText = data[0]?.generated_text || ''; | |
// Clean and extract JavaScript function | |
return extractJavaScriptFunction(generatedText); | |
} catch (error) { | |
log(`β οΈ API call failed: ${error.message}`); | |
throw error; | |
} | |
} | |
function extractJavaScriptFunction(text) { | |
// Clean the generated text | |
text = text.trim(); | |
// Look for function definition | |
const functionMatch = text.match(/function\s+\w+[^{]*\{[^]*?\n}/); | |
if (functionMatch) { | |
return functionMatch[0]; | |
} | |
// Look for arrow function | |
const arrowMatch = text.match(/const\s+\w+\s*=\s*\([^)]*\)\s*=>\s*\{[^]*?\n}/); | |
if (arrowMatch) { | |
return arrowMatch[0]; | |
} | |
// If no clear function found, try to construct one | |
if (currentProblem) { | |
const lines = text.split('\n').filter(line => line.trim()); | |
if (lines.length > 0) { | |
return `function ${currentProblem.functionName}(${currentProblem.parameters}) {\n ${lines.join('\n ')}\n}`; | |
} | |
} | |
return text; | |
} | |
function evaluateSolution(solution) { | |
let passedTests = 0; | |
for (const testCase of currentProblem.testCases) { | |
const result = executeTestCase(solution, testCase); | |
if (result.passed) { | |
passedTests++; | |
} | |
} | |
return passedTests / currentProblem.testCases.length; | |
} | |
function executeTestCase(solution, testCase) { | |
try { | |
const func = new Function(` | |
${solution} | |
return ${currentProblem.functionName}(...arguments); | |
`); | |
const actualOutput = func(...testCase.input); | |
const passed = JSON.stringify(actualOutput) === JSON.stringify(testCase.expected); | |
return { passed, actualOutput }; | |
} catch (error) { | |
return { passed: false, actualOutput: `Error: ${error.message}` }; | |
} | |
} | |
function updateUI() { | |
startBtn.disabled = isRunning || !currentProblem; | |
stopBtn.disabled = !isRunning; | |
problemSelect.disabled = isRunning; | |
} | |
function updateStatus(type, message) { | |
status.className = `status status-${type}`; | |
if (type === 'running') { | |
status.innerHTML = `<span class="spinner"></span>${message}`; | |
} else { | |
status.textContent = message; | |
} | |
} | |
function updateProgress() { | |
const progress = (currentGeneration / maxGenerations) * 100; | |
progressFill.style.width = `${progress}%`; | |
progressText.textContent = `${currentGeneration} / ${maxGenerations} generations`; | |
} | |
function updateChart() { | |
chart.data.labels.push(currentGeneration === 0 ? 'Initial' : `Gen ${currentGeneration}`); | |
chart.data.datasets[0].data.push(fitnessHistory[fitnessHistory.length - 1]); | |
chart.update('none'); | |
} | |
function updateCurrentSolution() { | |
if (bestSolution) { | |
currentSolutionDiv.innerHTML = ` | |
<pre><code class="language-javascript">${escapeHtml(bestSolution)}</code></pre> | |
`; | |
hljs.highlightAll(); | |
} | |
} | |
function showBestSolution() { | |
if (bestSolution) { | |
const passedTests = Math.round(bestFitness * currentProblem.testCases.length); | |
const totalTests = currentProblem.testCases.length; | |
document.getElementById('bestSolutionCode').innerHTML = ` | |
<pre><code class="language-javascript">${escapeHtml(bestSolution)}</code></pre> | |
`; | |
document.getElementById('bestSolutionStats').innerHTML = ` | |
<p><strong>Final Fitness:</strong> ${bestFitness.toFixed(3)}</p> | |
<p><strong>Test Cases Passed:</strong> ${passedTests} / ${totalTests}</p> | |
<p><strong>Generations Required:</strong> ${currentGeneration}</p> | |
<p><strong>Total API Calls:</strong> ${apiCallCount}</p> | |
<p><strong>Model Used:</strong> StarCoder2-3B</p> | |
`; | |
bestSolutionDiv.style.display = 'block'; | |
hljs.highlightAll(); | |
} | |
} | |
function log(message) { | |
evolutionLog.innerHTML += message + '\n'; | |
evolutionLog.scrollTop = evolutionLog.scrollHeight; | |
} | |
function escapeHtml(text) { | |
const map = { | |
'&': '&', | |
'<': '<', | |
'>': '>', | |
'"': '"', | |
"'": ''' | |
}; | |
return text.replace(/[&<>"']/g, function(m) { return map[m]; }); | |
} | |
// Initialize | |
log('𧬠LLM Hill Climber ready!'); | |
log('π€ Using StarCoder2-3B for code generation'); | |
log('β‘ Single mutation strategy for API efficiency'); | |
log('π Select a problem to begin evolution'); | |
log(''); | |
</script> | |
</body> | |
</html> |