@lewist9x/distil
Version:
An opinionated library for managing LLM pipelines. Define, track, rate, and curate prompt–completion pairs for fine-tuning.
227 lines (195 loc) • 8.98 kB
HTML
{{!< main}}
<div class="uk-container uk-margin-top">
<h2>Fine-tune Model</h2>
<div class="uk-margin">
<label class="uk-form-label">Select Pipeline</label>
<select class="uk-select" id="pipelineSelect" onchange="loadPipelineGenerations()">
<option value="">Select a pipeline...</option>
</select>
<div class="uk-text-meta">Choose which pipeline's generations to use for fine-tuning</div>
</div>
<div id="generationsList" class="uk-margin">
<!-- Generations will be loaded here -->
</div>
<div class="uk-margin">
<label class="uk-form-label">Model</label>
<select class="uk-select" id="modelSelect">
<option value="gpt-3.5-turbo">GPT-3.5 Turbo</option>
<option value="gpt-4">GPT-4</option>
</select>
</div>
<button class="uk-button uk-button-primary" onclick="startFineTuning()" id="startBtn" disabled>
Start Fine-tuning
</button>
</div>
<script>
window.finetune = {
db: null,
selectedGenerations: new Set(),
async initDB() {
const request = indexedDB.open('DistilDB', 1);
request.onerror = (event) => {
console.error('IndexedDB error:', event.target.error);
};
request.onupgradeneeded = (event) => {
this.db = event.target.result;
if (!this.db.objectStoreNames.contains('generations')) {
const store = this.db.createObjectStore('generations', { keyPath: 'id' });
store.createIndex('timestamp', 'timestamp');
store.createIndex('pipelineName', 'pipelineName');
store.createIndex('versionId', 'versionId');
}
};
request.onsuccess = (event) => {
this.db = event.target.result;
this.loadPipelines();
};
},
async loadPipelines() {
const transaction = this.db.transaction(['generations'], 'readonly');
const store = transaction.objectStore('generations');
const index = store.index('pipelineName');
const request = index.getAllKeys();
request.onsuccess = () => {
const pipelines = new Set(request.result);
const select = document.getElementById('pipelineSelect');
// Clear existing options except the first one
while (select.options.length > 1) {
select.remove(1);
}
// Add pipeline options
pipelines.forEach(pipeline => {
const option = document.createElement('option');
option.value = pipeline;
option.textContent = pipeline;
select.appendChild(option);
});
};
},
async loadPipelineGenerations(pipelineName) {
if (!pipelineName) {
document.getElementById('generationsList').innerHTML = '';
document.getElementById('startBtn').disabled = true;
return;
}
const transaction = this.db.transaction(['generations'], 'readonly');
const store = transaction.objectStore('generations');
const index = store.index('pipelineName');
const request = index.getAll(IDBKeyRange.only(pipelineName));
request.onsuccess = () => {
const generations = request.result;
const container = document.getElementById('generationsList');
container.innerHTML = `
<div class="uk-margin">
<h3>Collected Generations (${generations.length})</h3>
<div class="uk-overflow-auto">
<table class="uk-table uk-table-small uk-table-divider">
<thead>
<tr>
<th><input type="checkbox" onchange="finetune.toggleAll(this)"></th>
<th>Version</th>
<th>Rating</th>
<th>Input</th>
<th>Output</th>
</tr>
</thead>
<tbody>
${generations.map(gen => `
<tr>
<td><input type="checkbox" data-id="${gen.id}" onchange="finetune.toggleGeneration('${gen.id}')"></td>
<td>${gen.versionId}</td>
<td>${gen.rating}</td>
<td>${this.truncate(gen.input)}</td>
<td>${this.truncate(gen.output)}</td>
</tr>
`).join('')}
</tbody>
</table>
</div>
</div>
`;
document.getElementById('startBtn').disabled = generations.length === 0;
};
},
truncate(text, length = 50) {
text = text.replace(/^"|"$/g, '');
return text.length > length ? text.substring(0, length) + '...' : text;
},
toggleAll(checkbox) {
const checkboxes = document.querySelectorAll('#generationsList input[type="checkbox"][data-id]');
checkboxes.forEach(cb => {
cb.checked = checkbox.checked;
this.toggleGeneration(cb.dataset.id, checkbox.checked);
});
},
toggleGeneration(id, force) {
if (force === true) {
this.selectedGenerations.add(id);
} else if (force === false) {
this.selectedGenerations.delete(id);
} else {
if (this.selectedGenerations.has(id)) {
this.selectedGenerations.delete(id);
} else {
this.selectedGenerations.add(id);
}
}
},
async startFineTuning() {
const pipelineName = document.getElementById('pipelineSelect').value;
const model = document.getElementById('modelSelect').value;
if (!pipelineName || this.selectedGenerations.size === 0) {
alert('Please select a pipeline and at least one generation');
return;
}
try {
const response = await fetch('/api/finetune/prepare-openai', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
pipelineName,
generationIds: Array.from(this.selectedGenerations),
model
})
});
const result = await response.json();
if (result.success) {
alert(`Successfully prepared ${this.selectedGenerations.size} generations for fine-tuning`);
// Start the fine-tuning job
const jobResponse = await fetch('/api/finetune/start', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
fileId: result.file,
model
})
});
const jobResult = await jobResponse.json();
if (jobResult.success) {
alert(`Started fine-tuning job: ${jobResult.jobId}`);
} else {
throw new Error(jobResult.error);
}
} else {
throw new Error(result.error);
}
} catch (error) {
console.error('Error starting fine-tuning:', error);
alert(`Error: ${error.message}`);
}
}
};
// Initialize
finetune.initDB();
function loadPipelineGenerations() {
const pipelineName = document.getElementById('pipelineSelect').value;
finetune.loadPipelineGenerations(pipelineName);
}
function startFineTuning() {
finetune.startFineTuning();
}
</script>