mirror of
https://github.com/elder-plinius/AutoTemp.git
synced 2026-02-13 01:32:51 +00:00
feat(pages): hacker UI, multi-hyperparam sweep + UCB, live workflow dashboard, per-arm details layout, Chart.js temp vs score, and docs for Pages
This commit is contained in:
344
docs/app.js
Normal file
344
docs/app.js
Normal file
@@ -0,0 +1,344 @@
|
||||
async function openAIChat(apiKey, model, messages, temperature = 0.7, top_p = 1.0, extra = {}) {
|
||||
const url = 'https://api.openai.com/v1/chat/completions';
|
||||
const res = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${apiKey}`
|
||||
},
|
||||
body: JSON.stringify({ model, messages, temperature, top_p, ...extra })
|
||||
});
|
||||
if (!res.ok) {
|
||||
const errText = await res.text();
|
||||
throw new Error(`OpenAI error ${res.status}: ${errText}`);
|
||||
}
|
||||
const data = await res.json();
|
||||
const text = data.choices?.[0]?.message?.content || '';
|
||||
return text.trim();
|
||||
}
|
||||
|
||||
function escapeHtml(str){
|
||||
return String(str)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
async function generateOnce(apiKey, model, prompt, params) {
|
||||
return openAIChat(apiKey, model, [
|
||||
{ role: 'system', content: 'You are a helpful assistant.' },
|
||||
{ role: 'user', content: prompt }
|
||||
], params.temperature, params.top_p, params.extra);
|
||||
}
|
||||
|
||||
async function judgeOnce(apiKey, model, output, params, judgeId) {
|
||||
const evalPrompt = `You are Judge #${judgeId}. Evaluate the OUTPUT below which was generated at temperature ${params.temperature} and top_p ${params.top_p}.
|
||||
Return a STRICT minified JSON object with numeric fields only (no text outside JSON):
|
||||
{"relevance": float0to100, "clarity": float0to100, "utility": float0to100, "creativity": float0to100, "coherence": float0to100, "safety": float0to100, "overall": float0to100}
|
||||
Output between triple dashes:
|
||||
---
|
||||
${output}
|
||||
---`;
|
||||
const raw = await openAIChat(apiKey, model, [
|
||||
{ role: 'system', content: 'Return only the JSON.' },
|
||||
{ role: 'user', content: evalPrompt }
|
||||
], 0.2, 1.0);
|
||||
try {
|
||||
const jsonText = (raw.match(/\{[\s\S]*\}/) || [raw])[0];
|
||||
const obj = JSON.parse(jsonText);
|
||||
return {
|
||||
relevance: +obj.relevance || 0,
|
||||
clarity: +obj.clarity || 0,
|
||||
utility: +obj.utility || 0,
|
||||
creativity: +obj.creativity || 0,
|
||||
coherence: +obj.coherence || 0,
|
||||
safety: +obj.safety || 0,
|
||||
overall: +obj.overall || 0,
|
||||
};
|
||||
} catch (e) {
|
||||
const num = (raw.match(/\d+(?:\.\d+)?/) || [0])[0];
|
||||
return { relevance: 0, clarity: 0, utility: 0, creativity: 0, coherence: 0, safety: 0, overall: +num };
|
||||
}
|
||||
}
|
||||
|
||||
function mean(arr) { return arr.length ? arr.reduce((a,b)=>a+b,0) / arr.length : 0; }
|
||||
|
||||
function aggregateScores(scores) {
|
||||
const keys = ['relevance','clarity','utility','creativity','coherence','safety','overall'];
|
||||
const out = {};
|
||||
for (const k of keys) out[k] = +mean(scores.map(s=>s[k]||0)).toFixed(2);
|
||||
return out;
|
||||
}
|
||||
|
||||
async function standardMode(apiKey, model, prompt, arms, judges) {
|
||||
const outputs = {};
|
||||
const details = {};
|
||||
const overalls = {};
|
||||
|
||||
await Promise.all(arms.map(async (arm) => {
|
||||
const key = JSON.stringify(arm);
|
||||
const text = await generateOnce(apiKey, model, prompt, arm);
|
||||
outputs[key] = text;
|
||||
const judgeResults = await Promise.all(Array.from({length: judges}).map((_,i)=>
|
||||
judgeOnce(apiKey, model, text, arm, i+1)
|
||||
));
|
||||
const agg = aggregateScores(judgeResults);
|
||||
details[key] = agg;
|
||||
overalls[key] = agg.overall;
|
||||
}));
|
||||
|
||||
const ranked = Object.entries(overalls).sort((a,b)=>b[1]-a[1]);
|
||||
return { outputs, details, ranked };
|
||||
}
|
||||
|
||||
async function advancedModeUCB(apiKey, model, prompt, arms, judges, rounds, c) {
|
||||
const keys = arms.map(a=>JSON.stringify(a));
|
||||
const pulls = Object.fromEntries(keys.map(k=>[k,0]));
|
||||
const sums = Object.fromEntries(keys.map(k=>[k,0]));
|
||||
const best = Object.fromEntries(keys.map(k=>[k,{overall:-1,text:'',detail:{}}]));
|
||||
let total = 0;
|
||||
|
||||
// init
|
||||
for (const arm of arms) {
|
||||
const k = JSON.stringify(arm);
|
||||
const text = await generateOnce(apiKey, model, prompt, arm);
|
||||
const judgeResults = await Promise.all(Array.from({length: judges}).map((_,i)=>
|
||||
judgeOnce(apiKey, model, text, arm, i+1)
|
||||
));
|
||||
const agg = aggregateScores(judgeResults);
|
||||
pulls[k] += 1; sums[k] += agg.overall; total += 1;
|
||||
if (agg.overall > best[k].overall) best[k] = {overall: agg.overall, text, detail: agg};
|
||||
}
|
||||
|
||||
for (let r = 0; r < rounds - 1; r++) {
|
||||
const ucb = {};
|
||||
for (const k of keys) {
|
||||
const m = pulls[k] ? (sums[k]/pulls[k]) : Infinity;
|
||||
const bonus = pulls[k] ? c * Math.sqrt(Math.log(Math.max(1,total)) / pulls[k]) : Infinity;
|
||||
ucb[k] = m + bonus;
|
||||
}
|
||||
const nextK = keys.sort((a,b)=>ucb[b]-ucb[a])[0];
|
||||
const arm = JSON.parse(nextK);
|
||||
const text = await generateOnce(apiKey, model, prompt, arm);
|
||||
const judgeResults = await Promise.all(Array.from({length: judges}).map((_,i)=>
|
||||
judgeOnce(apiKey, model, text, arm, i+1)
|
||||
));
|
||||
const agg = aggregateScores(judgeResults);
|
||||
pulls[nextK] += 1; sums[nextK] += agg.overall; total += 1;
|
||||
if (agg.overall > best[nextK].overall) best[nextK] = {overall: agg.overall, text, detail: agg};
|
||||
}
|
||||
|
||||
const means = Object.fromEntries(keys.map(k=>[k, pulls[k] ? (sums[k]/pulls[k]) : 0]));
|
||||
const rankedKeys = keys.slice().sort((a,b)=>means[b]-means[a]);
|
||||
const bestK = rankedKeys[0];
|
||||
return { bestK, best: best[bestK], means, pulls };
|
||||
}
|
||||
|
||||
function getEl(id){ return document.getElementById(id); }
|
||||
function setText(id, txt){ getEl(id).textContent = txt; }
|
||||
function appendLog(msg){ const el=getEl('runLog'); if(!el) return; el.textContent += `\n${msg}`; el.scrollTop = el.scrollHeight; }
|
||||
|
||||
function renderArmsTable(arms){
|
||||
const tbody = getEl('armsTable').querySelector('tbody');
|
||||
tbody.innerHTML = '';
|
||||
for (const arm of arms){
|
||||
const k = JSON.stringify(arm);
|
||||
const tr = document.createElement('tr');
|
||||
tr.id = `arm-${btoa(k).replace(/=/g,'')}`;
|
||||
tr.innerHTML = `
|
||||
<td class="status status-wait">waiting</td>
|
||||
<td class="pulls">0</td>
|
||||
<td class="mean">-</td>
|
||||
<td class="best">-</td>
|
||||
<td><details><summary>view</summary><div class="arm-detail"></div></details></td>
|
||||
`;
|
||||
tbody.appendChild(tr);
|
||||
}
|
||||
}
|
||||
|
||||
function updateArmRow(arm, data){
|
||||
const k = JSON.stringify(arm);
|
||||
const id = `arm-${btoa(k).replace(/=/g,'')}`;
|
||||
const tr = getEl(id);
|
||||
if (!tr) return;
|
||||
if (data.status) { const s = tr.querySelector('.status'); s.textContent = data.status; s.className = `status ${data.statusClass||''}`; }
|
||||
if (data.pulls !== undefined) tr.querySelector('.pulls').textContent = String(data.pulls);
|
||||
if (data.mean !== undefined) tr.querySelector('.mean').textContent = (data.mean===null?'-':Number(data.mean).toFixed(2));
|
||||
if (data.best !== undefined) tr.querySelector('.best').textContent = (data.best===null?'-':Number(data.best).toFixed(2));
|
||||
if (data.detail) tr.querySelector('.arm-detail').innerHTML = data.detail;
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Chart setup
|
||||
let chart;
|
||||
function ensureChart(){
|
||||
const ctx = getEl('scoreChart');
|
||||
if (!ctx) return null;
|
||||
if (chart) return chart;
|
||||
chart = new Chart(ctx, {
|
||||
type: 'scatter',
|
||||
data: { datasets: [{ label: 'temp vs mean score', data: [], borderColor:'#00ff9c', backgroundColor:'rgba(0,255,156,0.3)' }]},
|
||||
options: {
|
||||
scales: {
|
||||
x: { title: { display:true, text:'temperature' }, grid: { color:'#0b442f' }, ticks:{ color:'#b5f5d2' } },
|
||||
y: { title: { display:true, text:'mean judge score' }, suggestedMin:0, suggestedMax:100, grid: { color:'#0b442f' }, ticks:{ color:'#b5f5d2' } }
|
||||
},
|
||||
plugins: { legend: { labels: { color:'#b5f5d2' } } }
|
||||
}
|
||||
});
|
||||
return chart;
|
||||
}
|
||||
function addChartPoint(temp, mean){
|
||||
const c = ensureChart(); if (!c) return;
|
||||
c.data.datasets[0].data.push({ x: temp, y: mean });
|
||||
c.update('none');
|
||||
}
|
||||
const judges = getEl('judges');
|
||||
const rounds = getEl('rounds');
|
||||
const explorationC = getEl('explorationC');
|
||||
judges.addEventListener('input', ()=> setText('judgesVal', judges.value));
|
||||
rounds.addEventListener('input', ()=> setText('roundsVal', rounds.value));
|
||||
explorationC.addEventListener('input', ()=> setText('cVal', (+explorationC.value).toFixed(2)));
|
||||
|
||||
getEl('runBtn').addEventListener('click', async () => {
|
||||
const apiKey = getEl('apiKey').value.trim();
|
||||
const remember = getEl('rememberKey').checked;
|
||||
if (!apiKey) { alert('Please enter an API key.'); return; }
|
||||
if (remember) localStorage.setItem('autotemp_api_key', apiKey); else localStorage.removeItem('autotemp_api_key');
|
||||
|
||||
const model = getEl('model').value.trim() || 'gpt-4o-mini';
|
||||
const temps = getEl('temperatures').value.split(',').map(s=>parseFloat(s.trim())).filter(n=>!Number.isNaN(n));
|
||||
const tops = getEl('tops').value.split(',').map(s=>parseFloat(s.trim())).filter(n=>!Number.isNaN(n));
|
||||
const maxTokens = getEl('maxTokens').value.split(',').map(s=>parseInt(s.trim(),10)).filter(n=>!Number.isNaN(n));
|
||||
const freqPen = getEl('freqPen').value.split(',').map(s=>parseFloat(s.trim())).filter(n=>!Number.isNaN(n));
|
||||
const presPen = getEl('presPen').value.split(',').map(s=>parseFloat(s.trim())).filter(n=>!Number.isNaN(n));
|
||||
const stopRaw = getEl('stopSeqs').value.trim();
|
||||
const stopTokens = stopRaw ? stopRaw.split(',').map(s=>s.replace(/\\n/g,'\n')) : undefined;
|
||||
const j = parseInt(getEl('judges').value, 10) || 3;
|
||||
const auto = getEl('autoSelect').checked;
|
||||
const adv = getEl('advancedMode').checked;
|
||||
const r = parseInt(getEl('rounds').value, 10) || 5;
|
||||
const c = parseFloat(getEl('explorationC').value) || 1.0;
|
||||
const prompt = getEl('userPrompt').value.trim();
|
||||
if (!prompt) { alert('Enter a prompt.'); return; }
|
||||
|
||||
// build arms (Cartesian product)
|
||||
function cartesian(arrs){ return arrs.reduce((a,b)=> a.flatMap(x=> b.map(y=>[...x,y])), [[]]); }
|
||||
const lists = [temps, tops, maxTokens, freqPen, presPen];
|
||||
const combos = cartesian(lists);
|
||||
const arms = combos.map(([temperature, top_p, max_tokens, frequency_penalty, presence_penalty]) => ({
|
||||
temperature, top_p,
|
||||
extra: {
|
||||
max_tokens,
|
||||
frequency_penalty,
|
||||
presence_penalty,
|
||||
...(stopTokens ? { stop: stopTokens } : {})
|
||||
}
|
||||
}));
|
||||
|
||||
const status = getEl('status');
|
||||
const results = getEl('results');
|
||||
results.textContent = '';
|
||||
status.textContent = 'Running...';
|
||||
appendLog(`Initialized ${arms.length} arms. Judges=${j}. Advanced=${adv ? 'UCB' : 'Standard'}.`);
|
||||
renderArmsTable(arms);
|
||||
try {
|
||||
const c = ensureChart(); if (c){ c.data.datasets[0].data = []; c.update('none'); }
|
||||
if (!adv) {
|
||||
const outputs = {}; const details = {}; const overalls = {};
|
||||
for (const arm of arms){
|
||||
updateArmRow(arm, { status:'running', statusClass:'status-running' });
|
||||
appendLog(`Generating for arm ${JSON.stringify(arm)}...`);
|
||||
const text = await generateOnce(apiKey, model, prompt, arm);
|
||||
outputs[JSON.stringify(arm)] = text;
|
||||
appendLog(`Judging arm ${JSON.stringify(arm)}...`);
|
||||
const judgeResults = await Promise.all(Array.from({length: j}).map((_,i)=> judgeOnce(apiKey, model, text, arm, i+1)));
|
||||
const agg = aggregateScores(judgeResults);
|
||||
details[JSON.stringify(arm)] = agg; overalls[JSON.stringify(arm)] = agg.overall;
|
||||
const paramHtml = `<div class="arm-params">Params: <code>${escapeHtml(JSON.stringify(arm))}</code></div>`;
|
||||
const outputHtml = `<div class="arm-output-box"><pre>${escapeHtml(text)}</pre></div>`;
|
||||
const scoresHtml = `<div class="arm-scores">Scores: <code>${escapeHtml(JSON.stringify(agg))}</code></div>`;
|
||||
updateArmRow(arm, { status:'done', statusClass:'status-done', pulls:1, mean:agg.overall, best:agg.overall, detail: paramHtml + outputHtml + scoresHtml });
|
||||
if (typeof arm.temperature === 'number') addChartPoint(arm.temperature, agg.overall);
|
||||
}
|
||||
const ranked = Object.entries(overalls).sort((a,b)=>b[1]-a[1]);
|
||||
if (auto) {
|
||||
const [bestK, bestScore] = ranked[0];
|
||||
const arm = JSON.parse(bestK);
|
||||
results.textContent = `Best Arm ${bestK} | Overall ${bestScore}\n` + outputs[bestK] + "\n\n" + `Judges: ${JSON.stringify(details[bestK])}`;
|
||||
} else {
|
||||
results.textContent = ranked.map(([t, s])=>
|
||||
`Arm ${t} | Overall ${s} | Detail ${JSON.stringify(details[t])}\n${outputs[t]}`
|
||||
).join('\n\n');
|
||||
}
|
||||
} else {
|
||||
// Transparent UCB loop with UI updates
|
||||
const keys = arms.map(a=>JSON.stringify(a));
|
||||
const pulls = Object.fromEntries(keys.map(k=>[k,0]));
|
||||
const sums = Object.fromEntries(keys.map(k=>[k,0]));
|
||||
const best = Object.fromEntries(keys.map(k=>[k,{overall:-1,text:'',detail:{}}]));
|
||||
let total = 0;
|
||||
for (const arm of arms){ updateArmRow(arm, { status:'running', statusClass:'status-running' }); }
|
||||
// init pull each arm
|
||||
for (const arm of arms){
|
||||
appendLog(`Init pull -> ${JSON.stringify(arm)}`);
|
||||
const k = JSON.stringify(arm);
|
||||
const text = await generateOnce(apiKey, model, prompt, arm);
|
||||
const judgeResults = await Promise.all(Array.from({length: j}).map((_,i)=> judgeOnce(apiKey, model, text, arm, i+1)));
|
||||
const agg = aggregateScores(judgeResults);
|
||||
pulls[k] += 1; sums[k] += agg.overall; total += 1;
|
||||
if (agg.overall > best[k].overall) best[k] = {overall: agg.overall, text, detail: agg};
|
||||
const paramHtml = `<div class="arm-params">Params: <code>${escapeHtml(JSON.stringify(arm))}</code></div>`;
|
||||
const outputHtml = `<div class="arm-output-box"><pre>${escapeHtml(text)}</pre></div>`;
|
||||
const scoresHtml = `<div class="arm-scores">Scores: <code>${escapeHtml(JSON.stringify(agg))}</code></div>`;
|
||||
updateArmRow(arm, { pulls:pulls[k], mean:(sums[k]/pulls[k]), best:best[k].overall, detail: paramHtml + outputHtml + scoresHtml });
|
||||
if (typeof arm.temperature === 'number') addChartPoint(arm.temperature, agg.overall);
|
||||
}
|
||||
for (let i=0;i<r-1;i++){
|
||||
// compute UCB
|
||||
const ucb = {};
|
||||
for (const arm of arms){
|
||||
const k = JSON.stringify(arm);
|
||||
const m = pulls[k] ? (sums[k]/pulls[k]) : Infinity;
|
||||
const bonus = pulls[k] ? c * Math.sqrt(Math.log(Math.max(1,total)) / pulls[k]) : Infinity;
|
||||
ucb[k] = m + bonus;
|
||||
}
|
||||
const nextK = keys.slice().sort((a,b)=>ucb[b]-ucb[a])[0];
|
||||
const arm = JSON.parse(nextK);
|
||||
appendLog(`Round ${i+1}: selecting arm ${nextK} (UCB=${ucb[nextK].toFixed(3)})`);
|
||||
const text = await generateOnce(apiKey, model, prompt, arm);
|
||||
const judgeResults = await Promise.all(Array.from({length: j}).map((_,i)=> judgeOnce(apiKey, model, text, arm, i+1)));
|
||||
const agg = aggregateScores(judgeResults);
|
||||
pulls[nextK] += 1; sums[nextK] += agg.overall; total += 1;
|
||||
if (agg.overall > best[nextK].overall) best[nextK] = {overall: agg.overall, text, detail: agg};
|
||||
const paramHtml = `<div class=\"arm-params\">Params: <code>${escapeHtml(JSON.stringify(arm))}</code></div>`;
|
||||
const outputHtml = `<div class=\"arm-output-box\"><pre>${escapeHtml(text)}</pre></div>`;
|
||||
const scoresHtml = `<div class=\"arm-scores\">Scores: <code>${escapeHtml(JSON.stringify(agg))}</code></div>`;
|
||||
updateArmRow(arm, { pulls:pulls[nextK], mean:(sums[nextK]/pulls[nextK]), best:best[nextK].overall, detail: paramHtml + outputHtml + scoresHtml });
|
||||
if (typeof arm.temperature === 'number') addChartPoint(arm.temperature, agg.overall);
|
||||
}
|
||||
for (const arm of arms){ updateArmRow(arm, { status:'done', statusClass:'status-done' }); }
|
||||
const means = Object.fromEntries(keys.map(k=>[k, pulls[k] ? (sums[k]/pulls[k]) : 0]));
|
||||
const ranked = keys.slice().sort((a,b)=>means[b]-means[a]);
|
||||
const bestK = ranked[0];
|
||||
const bestArm = JSON.parse(bestK);
|
||||
appendLog(`Complete. Best ${bestK} mean=${means[bestK].toFixed(2)} best_overall=${best[bestK].overall.toFixed(2)}`);
|
||||
if (auto){
|
||||
results.textContent = `Advanced (UCB) — Best Arm ${bestK} | Mean ${means[bestK].toFixed(2)} | Best Overall ${best[bestK].overall.toFixed(2)}\n` + best[bestK].text + "\n\n" + `Detail: ${JSON.stringify(best[bestK].detail)}`;
|
||||
} else {
|
||||
const lines = [`Advanced (UCB) — Best ${bestK}`, best[bestK].text, '', `Detail: ${JSON.stringify(best[bestK].detail)}`, ''];
|
||||
for (const k of ranked){ lines.push(`Arm ${k}: pulls=${pulls[k]}, mean_overall=${means[k].toFixed(2)}, best_overall=${best[k].overall.toFixed(2)}`); }
|
||||
results.textContent = lines.join('\n');
|
||||
}
|
||||
}
|
||||
status.textContent = 'Done.';
|
||||
} catch (e) {
|
||||
status.textContent = 'Error';
|
||||
results.textContent = String(e?.message || e);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
142
docs/index.html
Normal file
142
docs/index.html
Normal file
@@ -0,0 +1,142 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>AutoTemp — Research-Grade Hyperparameter Optimization</title>
|
||||
<link rel="stylesheet" href="./style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header class="header">
|
||||
<div class="logo">▌▌ AutoTemp</div>
|
||||
<div class="subtitle">Hyperparameter Lab — hacker mode</div>
|
||||
</header>
|
||||
|
||||
<section class="config">
|
||||
<div class="field">
|
||||
<label for="apiKey">OpenAI API Key</label>
|
||||
<input type="password" id="apiKey" placeholder="sk-..." />
|
||||
<label class="inline">
|
||||
<input type="checkbox" id="rememberKey" /> Remember in this browser
|
||||
</label>
|
||||
</div>
|
||||
<div class="grid-3">
|
||||
<div class="field">
|
||||
<label for="model">Model</label>
|
||||
<input id="model" value="gpt-5-chat-latest" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="judges">Judges: <span id="judgesVal">3</span></label>
|
||||
<input type="range" id="judges" min="1" max="7" step="1" value="3" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="inline"><input type="checkbox" id="autoSelect" checked /> Auto Select Best</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid-3">
|
||||
<div class="field">
|
||||
<label for="temperatures">temperature list</label>
|
||||
<input id="temperatures" value="0.4,0.6,0.8,1.0" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="tops">top_p list</label>
|
||||
<input id="tops" value="1.0" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="maxTokens">max_tokens list</label>
|
||||
<input id="maxTokens" value="256,512" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid-3">
|
||||
<div class="field">
|
||||
<label for="freqPen">frequency_penalty list</label>
|
||||
<input id="freqPen" value="0,0.2" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="presPen">presence_penalty list</label>
|
||||
<input id="presPen" value="0,0.2" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="stopSeqs">stop tokens (comma-separated)</label>
|
||||
<input id="stopSeqs" placeholder="e.g. \nEND,###" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid-3">
|
||||
<div class="field">
|
||||
<label class="inline"><input type="checkbox" id="advancedMode" /> Advanced Mode (UCB over arms)</label>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="rounds">Rounds (advanced): <span id="roundsVal">8</span></label>
|
||||
<input type="range" id="rounds" min="1" max="50" step="1" value="8" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="explorationC">Exploration c (UCB): <span id="cVal">1.0</span></label>
|
||||
<input type="range" id="explorationC" min="0" max="3" step="0.1" value="1.0" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="note">Provide comma-separated values to sweep. The app will form the Cartesian product across lists and evaluate each hyperparameter arm.</div>
|
||||
</section>
|
||||
|
||||
<section class="prompt terminal">
|
||||
<label for="userPrompt">Prompt</label>
|
||||
<textarea id="userPrompt" rows="8" placeholder="Enter your prompt..."></textarea>
|
||||
</section>
|
||||
|
||||
<section class="actions">
|
||||
<button id="runBtn">Run AutoTemp</button>
|
||||
<span id="status"></span>
|
||||
</section>
|
||||
|
||||
<section class="output terminal">
|
||||
<h2>Results</h2>
|
||||
<pre id="results" class="glow"></pre>
|
||||
</section>
|
||||
|
||||
<section class="workflow terminal">
|
||||
<h2>Workflow</h2>
|
||||
<div class="chart-wrap">
|
||||
<canvas id="scoreChart" height="180"></canvas>
|
||||
</div>
|
||||
<div class="table-wrap">
|
||||
<table id="armsTable" class="arms-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Status</th>
|
||||
<th>Pulls</th>
|
||||
<th>Mean</th>
|
||||
<th>Best</th>
|
||||
<th>Details</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="runlog terminal">
|
||||
<h2>Run Log</h2>
|
||||
<pre id="runLog" class="log"></pre>
|
||||
</section>
|
||||
|
||||
<footer class="footer">
|
||||
<div class="blink">>_</div>
|
||||
<p>Security: Key stays in-browser (localStorage opt-in). Prefer proxies for shared deployments.</p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script src="./app.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const savedKey = localStorage.getItem('autotemp_api_key');
|
||||
if (savedKey) {
|
||||
document.getElementById('apiKey').value = savedKey;
|
||||
document.getElementById('rememberKey').checked = true;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
43
docs/style.css
Normal file
43
docs/style.css
Normal file
@@ -0,0 +1,43 @@
|
||||
:root { --bg:#020b05; --panel:#03150e; --text:#b5f5d2; --accent:#00ff9c; --accent2:#13f1ff; --muted:#0a2a1f; }
|
||||
*{ box-sizing:border-box }
|
||||
body{ margin:0; font-family: ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace; background: radial-gradient(1200px 800px at 20% 0%, #03150e, #020b05), #020b05; color:var(--text) }
|
||||
.container{ max-width:1100px; margin:0 auto; padding:24px }
|
||||
.header{ display:flex; align-items:center; justify-content:space-between; margin-bottom:16px }
|
||||
.logo{ font-weight:900; color:var(--accent); letter-spacing:2px }
|
||||
.subtitle{ color:var(--accent2); opacity:.9 }
|
||||
section{ background:linear-gradient(180deg, rgba(3,21,14,.9), rgba(2,11,5,.9)); padding:16px; border-radius:10px; margin-bottom:16px; border:1px solid #0b442f; box-shadow:0 0 30px rgba(0,255,156,.05) inset }
|
||||
.field{ margin-bottom:12px }
|
||||
.field label{ display:block; font-weight:700; margin-bottom:6px; color:#a5ffd6 }
|
||||
.field input[type="text"], .field input[type="password"], .field input[type="number"], .field textarea { width:100%; padding:10px; border-radius:6px; border:1px solid #0b442f; background:#03150e; color:var(--text); outline:none; box-shadow:0 0 0 1px rgba(0,255,156,.05) inset }
|
||||
.field input[type="text"]:focus, .field input[type="password"]:focus, .field textarea:focus { box-shadow:0 0 0 2px rgba(19,241,255,.25) inset }
|
||||
.field input[type="range"]{ width:100% }
|
||||
.inline{ display:inline-flex; align-items:center; gap:8px }
|
||||
.grid-2{ display:grid; grid-template-columns:1fr 1fr; gap:12px }
|
||||
.grid-3{ display:grid; grid-template-columns:1fr 1fr 1fr; gap:12px }
|
||||
.actions{ display:flex; align-items:center; gap:12px }
|
||||
button{ background:linear-gradient(90deg, var(--accent), var(--accent2)); color:#00170e; font-weight:900; border:none; padding:10px 16px; border-radius:8px; cursor:pointer; box-shadow:0 0 15px rgba(0,255,156,.2) }
|
||||
button:hover{ filter:brightness(1.05) }
|
||||
.terminal{ border:1px solid #0b442f; background:#010a06; box-shadow:0 0 40px rgba(0,255,156,.06) inset }
|
||||
pre{ white-space:pre-wrap; background:#010a06; padding:12px; border-radius:8px; border:1px dashed #0b442f }
|
||||
.table-wrap{ overflow:auto; }
|
||||
.arms-table{ width:100%; border-collapse:collapse; font-size:13px }
|
||||
.arms-table th,.arms-table td{ border:1px dashed #0b442f; padding:6px 8px; vertical-align:top }
|
||||
.arms-table th{ color:#a5ffd6; background:#03150e; position:sticky; top:0 }
|
||||
.badge{ display:inline-block; padding:2px 6px; border-radius:999px; background:#062c1f; border:1px solid #0b442f }
|
||||
.status-running{ color:#13f1ff }
|
||||
.status-done{ color:#00ff9c }
|
||||
.status-wait{ color:#a5ffd6 }
|
||||
.log{ max-height:260px; overflow:auto }
|
||||
.chart-wrap{ background:#010a06; border:1px dashed #0b442f; border-radius:8px; padding:8px; margin-bottom:12px }
|
||||
.arm-params{ font-size:12px; color:#a5ffd6; margin-bottom:8px }
|
||||
.arm-params code{ background:#03150e; padding:2px 4px; border:1px solid #0b442f; border-radius:4px }
|
||||
.arm-output-box{ background:#0f1620; border:1px solid #0b442f; border-radius:8px; padding:16px; margin:10px auto; max-width:760px; box-shadow:0 0 20px rgba(0,255,156,.08) inset }
|
||||
.arm-output-box pre{ background:transparent; border:none; margin:0; padding:0; white-space:pre-wrap; color:#e4fff2; font-size:14px }
|
||||
.arm-scores{ font-size:12px; margin-top:8px; color:#b5f5d2 }
|
||||
.footer{ display:flex; align-items:center; gap:10px; opacity:.85 }
|
||||
.blink{ width:8px; height:18px; background:var(--accent); animation: blink 1s infinite }
|
||||
.glow{ text-shadow:0 0 8px rgba(0,255,156,.35) }
|
||||
@keyframes blink{ 50%{ opacity:.2 } }
|
||||
@media(max-width:720px){ .grid-2,.grid-3{ grid-template-columns:1fr } }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user