UNPKG

@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
{{!< 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>