PromptDialog-demo / index.html
Anonymous
update
ff857f3
Raw
History Blame Contribute Delete
12.7 kB
<!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">
<div class="demo-intro">
<p>
This page presents the results obtained by fine-tuning SoulX-Podcast on our dataset. We compare our model with other models that possess related capabilities across multiple tasks, in order to demonstrate the superiority of our dataset. Since our dataset is primarily composed of Chinese data, we mainly present the performance of our model on Chinese speech generation. In addition, we supplement the training data with 100 hours of English audiobook data for fine-tuning, thereby demonstrating that the construction of our dataset exhibits cross-lingual transferability.
</p>
</div>
<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: "mixed-style", root: "./static/task/mixed-style" },
{ id: "paral", root: "./static/task/paral" },
{ id: "punct", root: "./static/task/punct" },
{ id: "emo", root: "./static/task/emo" },
{ id: "multi", root: "./static/task/multi" }
];
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);
const hasMultipleVariants = item.variants.length > 1;
item.variants.forEach((variant, index) => {
const context = document.createElement("p");
context.className = "variant-context";
if (hasMultipleVariants) {
context.innerHTML = `<strong>instruction${index + 1}</strong> <span class="context-cue">${escapeHtml(variant.context)}</span>`;
} else {
context.innerHTML = `<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;
}
const hasMultipleVariants = modelItem.variants.length > 1;
modelItem.variants.forEach((variant, index) => {
const row = document.createElement("div");
row.className = "variant-audio-row";
if (hasMultipleVariants) {
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";
const caseItems = [];
const seenCases = new Set();
task.models.forEach((model) => {
manifests[model].forEach((item) => {
if (seenCases.has(item.utt)) {
return;
}
seenCases.add(item.utt);
caseItems.push(item);
});
});
caseItems.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 item = document.createElement("div");
item.className = "task-directory-item";
const link = document.createElement("a");
link.href = `#task-${id}`;
link.textContent = task.name;
const description = document.createElement("p");
description.textContent = task.description;
item.appendChild(link);
item.appendChild(description);
links.appendChild(item);
});
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>