adi-voice-persona-lab / index.html
masterjedi
Convert voice persona lab to static Space
a3e7485
Raw
History Blame Contribute Delete
6.71 kB
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>ADI Voice Persona Lab</title>
<style>
:root { color-scheme: dark; --bg:#080b10; --panel:#151922; --line:#303745; --text:#f5f7fb; --muted:#aab2c0; --accent:#12b981; --orange:#f97316; }
* { box-sizing:border-box; }
body { margin:0; font:15px/1.45 system-ui, -apple-system, Segoe UI, sans-serif; background:var(--bg); color:var(--text); }
main { max-width:1180px; margin:0 auto; padding:28px 18px 40px; }
h1 { margin:0 0 18px; font-size:30px; }
textarea, select { width:100%; border:1px solid var(--line); border-radius:8px; background:#0d1118; color:var(--text); padding:12px; font:inherit; }
textarea { min-height:130px; resize:vertical; }
label { display:block; margin:0 0 8px; font-weight:800; }
.panel { background:var(--panel); border:1px solid var(--line); border-radius:8px; padding:16px; margin-bottom:16px; }
.grid { display:grid; grid-template-columns:repeat(3, minmax(240px, 1fr)); gap:14px; }
.row { display:grid; grid-template-columns:repeat(3, minmax(160px, 1fr)); gap:12px; }
button { min-height:42px; border:0; border-radius:8px; padding:0 16px; font-weight:900; cursor:pointer; color:#100701; background:var(--orange); }
.take { display:flex; flex-direction:column; gap:10px; }
.script { min-height:150px; color:#d8ffe9; }
.muted { color:var(--muted); }
@media (max-width:900px) { .grid, .row { grid-template-columns:1fr; } }
</style>
</head>
<body>
<main>
<h1>ADI Voice Persona Lab</h1>
<section class="panel">
<label for="base">Base ADI response</label>
<textarea id="base">Hello, I am ADI. I can help inspect models, compare prompts, generate training data, and turn your ideas into working demos on Hugging Face Spaces.</textarea>
<div class="row" style="margin-top:14px">
<div><label for="length">Script length</label><select id="length"><option>short</option><option selected>normal</option><option>expanded</option></select></div>
<div><label for="rate">Rate</label><select id="rate"><option value="0.85">slower</option><option value="1" selected>normal</option><option value="1.15">faster</option></select></div>
<div><label>&nbsp;</label><button id="generate">Generate Takes</button></div>
</div>
<p class="muted">Uses your browser's built-in speech voices, so it does not consume Hugging Face CPU quota.</p>
</section>
<section class="grid">
<div class="panel take">
<label>Persona A</label><select class="persona"><option>ADI calm</option><option>Technical mentor</option><option>Operator mode</option><option>Warm companion</option><option>Launch demo</option></select>
<label>Voice A</label><select class="voice"></select>
<textarea class="script"></textarea>
<button class="speak">Speak A</button>
</div>
<div class="panel take">
<label>Persona B</label><select class="persona"><option>Technical mentor</option><option>ADI calm</option><option>Operator mode</option><option>Warm companion</option><option>Launch demo</option></select>
<label>Voice B</label><select class="voice"></select>
<textarea class="script"></textarea>
<button class="speak">Speak B</button>
</div>
<div class="panel take">
<label>Persona C</label><select class="persona"><option>Launch demo</option><option>ADI calm</option><option>Technical mentor</option><option>Operator mode</option><option>Warm companion</option></select>
<label>Voice C</label><select class="voice"></select>
<textarea class="script"></textarea>
<button class="speak">Speak C</button>
</div>
</section>
</main>
<script>
const personaStyles = {
"ADI calm": ["Here is the calm version.", "steady, concise, and reassuring"],
"Technical mentor": ["Here is the technical mentor version.", "precise, capable, and lightly explanatory"],
"Operator mode": ["Status: ready.", "brief, action-oriented, and status-aware"],
"Warm companion": ["Here is the warm companion version.", "friendly, grounded, and conversational"],
"Launch demo": ["Here is the launch demo version.", "polished, confident, and presentation-ready"]
};
const clean = text => (text || "").trim().replace(/\s+/g, " ");
function rewrite(text, persona, length) {
const [prefix, style] = personaStyles[persona];
let body = clean(text);
const sentences = body.split(/(?<=[.!?])\s+/).filter(Boolean);
if (length === "short") body = sentences.slice(0, 2).join(" ");
if (length === "expanded") body += ` The intended delivery is ${style}, with enough detail to feel useful.`;
if (persona === "Operator mode") body = `${prefix} ${body} Next step: tell me what you want to run.`;
else if (persona === "Technical mentor") body = `${prefix} ${body} The key idea is to make the next action obvious.`;
else if (persona === "Warm companion") body = `${prefix} ${body} I can stay with you while we tune it.`;
else if (persona === "Launch demo") body = `${prefix} ${body} This is ready to show as a live ADI capability.`;
else body = `${prefix} ${body}`;
return body;
}
function loadVoices() {
const voices = speechSynthesis.getVoices();
document.querySelectorAll(".voice").forEach((select, idx) => {
const chosen = select.value;
select.innerHTML = "";
voices.forEach((voice, i) => {
const option = document.createElement("option");
option.value = i;
option.textContent = `${voice.name} (${voice.lang})`;
select.appendChild(option);
});
if (chosen) select.value = chosen;
else select.selectedIndex = Math.min(idx, Math.max(voices.length - 1, 0));
});
}
function generate() {
const text = document.querySelector("#base").value;
const length = document.querySelector("#length").value;
document.querySelectorAll(".take").forEach(take => {
take.querySelector(".script").value = rewrite(text, take.querySelector(".persona").value, length);
});
}
function speak(take) {
speechSynthesis.cancel();
const utterance = new SpeechSynthesisUtterance(take.querySelector(".script").value);
const voices = speechSynthesis.getVoices();
const voice = voices[Number(take.querySelector(".voice").value)];
if (voice) utterance.voice = voice;
utterance.rate = Number(document.querySelector("#rate").value);
speechSynthesis.speak(utterance);
}
document.querySelector("#generate").addEventListener("click", generate);
document.querySelectorAll(".speak").forEach(button => button.addEventListener("click", e => speak(e.target.closest(".take"))));
speechSynthesis.onvoiceschanged = loadVoices;
loadVoices();
generate();
</script>
</body>
</html>