PromptDialog-demo / index.html
Anonymous
Refine demo layout and task descriptions
5d8c977
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="description" content="PromptDialog multi-speaker dialogue audio demo.">
<meta name="keywords" content="PromptDialog, text-to-speech, dialogue, audio demo">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>PromptDialog</title>
<link rel="stylesheet" href="./static/css/bulma.min.css">
<link rel="stylesheet" href="./static/css/index.css">
</head>
<body>
<main>
<section class="prompt-hero">
<div class="container wide-container">
<h1 class="publication-title">PromptDialog</h1>
</div>
</section>
<section class="section demo-section" id="audio-demo">
<div class="container wide-container">
<nav class="task-directory" id="task-directory" aria-label="Audio demo tasks">
<p class="loading">Loading task directory...</p>
</nav>
<div class="task-list" id="task-list" aria-live="polite">
<div class="task-card">
<p class="loading">Loading audio demos...</p>
</div>
</div>
</div>
</section>
</main>
<script>
const TASKS = [
{ id: "multi", root: "./static/task/multi" },
{ id: "paral", root: "./static/task/paral" },
{ id: "punct", root: "./static/task/punct" },
{ id: "emo", root: "./static/task/emo" },
{ id: "mixed-style", root: "./static/task/mixed-style" }
];
function parseTaskYaml(source) {
const task = { name: "", description: "", show_reference: true, models: [] };
let listKey = "";
source.split(/\r?\n/).forEach((rawLine) => {
const line = rawLine.trim();
if (!line || line.startsWith("#")) return;
if (line.startsWith("- ") && listKey) {
task[listKey].push(line.slice(2).trim().replace(/^["']|["']$/g, ""));
return;
}
const match = line.match(/^([A-Za-z0-9_-]+):\s*(.*)$/);
if (!match) return;
const key = match[1];
const value = match[2].trim().replace(/^["']|["']$/g, "");
if (value === "true" || value === "false") {
task[key] = value === "true";
listKey = "";
} else if (Array.isArray(task[key])) {
listKey = key;
} else {
task[key] = value;
listKey = "";
}
});
return task;
}
async function fetchText(path) {
const response = await fetch(path);
if (!response.ok) {
throw new Error(`Failed to load ${path}`);
}
return response.text();
}
function parseJsonl(source) {
return source
.split(/\r?\n/)
.map((line) => line.trim())
.filter(Boolean)
.map((line) => JSON.parse(line));
}
function createAudio(src) {
const audio = document.createElement("audio");
audio.controls = true;
audio.preload = "none";
audio.src = src;
return audio;
}
function formatScriptText(text, highlightParalinguistic) {
const escapedText = escapeHtml(text);
if (!highlightParalinguistic) {
return escapedText;
}
return escapedText.replace(/[唉嗯哈呵嘿哼呼咳啧]/g, '<em class="paralinguistic-cue">$&</em>');
}
function createScriptCell(item, highlightParalinguistic = false) {
const wrapper = document.createElement("div");
wrapper.className = "script-cell";
if (Array.isArray(item.variants)) {
const target = document.createElement("p");
target.innerHTML = formatScriptText(item.target_text, highlightParalinguistic);
wrapper.appendChild(target);
item.variants.forEach((variant, index) => {
const context = document.createElement("p");
context.className = "variant-context";
context.innerHTML = `<strong>instruction${index + 1}</strong> <span class="context-cue">${escapeHtml(variant.context)}</span>`;
wrapper.appendChild(context);
});
return wrapper;
}
const texts = Array.isArray(item.target_text) ? item.target_text : [item.target_text];
const speakers = Array.isArray(item.speaker) ? item.speaker : [];
const contexts = Array.isArray(item.context) ? item.context : [item.context];
texts.forEach((text, index) => {
const turn = document.createElement("p");
const speaker = speakers[index];
const context = contexts[index];
const cleanContext = context ? String(context).replace(/[,,。.!!??;;::、\s]+$/u, "") : "";
const contextBadge = cleanContext ? ` <span class="context-cue">${escapeHtml(cleanContext)}</span>` : "";
const formattedText = formatScriptText(text, highlightParalinguistic);
if (speaker !== undefined && speaker !== "") {
turn.innerHTML = `<strong>[S${Number(speaker) + 1}]</strong> ${formattedText}${contextBadge}`;
} else {
turn.innerHTML = `${formattedText}${contextBadge}`;
}
wrapper.appendChild(turn);
});
return wrapper;
}
function createReferenceCell(item, modelRoot) {
const wrapper = document.createElement("div");
wrapper.className = "reference-cell";
const prompts = Array.isArray(item.prompt_audio) ? item.prompt_audio : [];
prompts.forEach((audioPath, index) => {
const block = document.createElement("div");
const label = document.createElement("span");
label.textContent = `Speaker ${index + 1}`;
block.appendChild(label);
block.appendChild(createAudio(`${modelRoot}/${audioPath}`));
wrapper.appendChild(block);
});
return wrapper;
}
function escapeHtml(text) {
return text.replace(/[&<>"']/g, (char) => ({
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': "&quot;",
"'": "&#039;"
}[char]));
}
function createAudioCard(label, content) {
const card = document.createElement("div");
card.className = "audio-card";
const title = document.createElement("div");
title.className = "audio-card-title";
title.textContent = label;
const body = document.createElement("div");
body.className = "audio-card-body";
if (content) {
body.appendChild(content);
}
card.appendChild(title);
card.appendChild(body);
return card;
}
function createVariantAudioCell(modelItem, taskRoot, model) {
const wrapper = document.createElement("div");
wrapper.className = "variant-audio-list";
if (!modelItem || !Array.isArray(modelItem.variants)) {
return wrapper;
}
modelItem.variants.forEach((variant, index) => {
const row = document.createElement("div");
row.className = "variant-audio-row";
const label = document.createElement("span");
label.textContent = `instruction${index + 1}`;
row.appendChild(label);
row.appendChild(createAudio(`${taskRoot}/${model}/${variant.output_audio}`));
wrapper.appendChild(row);
});
return wrapper;
}
function renderTable(task, manifests, taskRoot) {
const wrapper = document.createElement("div");
wrapper.className = "demo-cases";
const highlightParalinguistic = task.name === "Paralinguistic Cue Generation";
manifests[task.models[0]].forEach((item) => {
const caseBlock = document.createElement("article");
caseBlock.className = "case-block";
const scriptBlock = document.createElement("div");
scriptBlock.className = "case-script";
const scriptTitle = document.createElement("div");
scriptTitle.className = "case-section-title";
scriptTitle.textContent = "Script";
scriptBlock.appendChild(scriptTitle);
scriptBlock.appendChild(createScriptCell(item, highlightParalinguistic));
caseBlock.appendChild(scriptBlock);
const audioGrid = document.createElement("div");
audioGrid.className = "audio-grid";
if (task.show_reference) {
const modelRoot = `${taskRoot}/${task.models[0]}`;
audioGrid.appendChild(createAudioCard("Reference", createReferenceCell(item, modelRoot)));
}
task.models.forEach((model) => {
const modelItem = manifests[model].find((entry) => entry.utt === item.utt);
const content = document.createElement("div");
content.className = "audio-card-content";
if (modelItem && Array.isArray(modelItem.variants)) {
content.appendChild(createVariantAudioCell(modelItem, taskRoot, model));
} else if (modelItem) {
content.appendChild(createAudio(`${taskRoot}/${model}/${modelItem.output_audio}`));
}
audioGrid.appendChild(createAudioCard(model, content));
});
caseBlock.appendChild(audioGrid);
wrapper.appendChild(caseBlock);
});
return wrapper;
}
function renderDirectory(tasks) {
const directory = document.getElementById("task-directory");
const title = document.createElement("div");
title.className = "task-directory-title";
title.textContent = "Tasks";
const links = document.createElement("div");
links.className = "task-directory-links";
tasks.forEach(({ id, task }) => {
const link = document.createElement("a");
link.href = `#task-${id}`;
link.textContent = task.name;
links.appendChild(link);
});
directory.replaceChildren(title, links);
}
async function loadTask(config) {
const task = parseTaskYaml(await fetchText(`${config.root}/task.yaml`));
const manifests = {};
await Promise.all(task.models.map(async (model) => {
manifests[model] = parseJsonl(await fetchText(`${config.root}/${model}/manifest.jsonl`));
}));
return { ...config, task, manifests };
}
function renderTaskCard({ id, root, task, manifests }) {
const card = document.createElement("section");
card.className = "task-card";
card.id = `task-${id}`;
const heading = document.createElement("div");
heading.className = "task-card-heading";
const title = document.createElement("h3");
title.textContent = task.name;
const description = document.createElement("p");
description.textContent = task.description;
heading.appendChild(title);
heading.appendChild(description);
card.appendChild(heading);
card.appendChild(renderTable(task, manifests, root));
return card;
}
async function initDemo() {
const tasks = await Promise.all(TASKS.map(loadTask));
renderDirectory(tasks);
const list = document.getElementById("task-list");
list.replaceChildren(...tasks.map(renderTaskCard));
}
initDemo().catch((error) => {
document.getElementById("task-directory").textContent = "Failed to load task metadata.";
document.getElementById("task-list").textContent = error.message;
});
</script>
</body>
</html>