PrimeTTS-Streaming / index.html
luigi-liu-tw's picture
Claude Sonnet 5
Port g2p_en's real OOV GRU model (close English g2p parity gap)
6d42bcb
Raw History Blame Contribute Delete
6.54 kB
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>PrimeTTS · real-time streaming (WASM)</title>
<style>
:root { --bg:#0e1116; --card:#171c26; --fg:#e6edf3; --mut:#8b98a9; --acc:#5eead4; --acc2:#38bdf8; }
* { box-sizing:border-box; }
body { margin:0; font:15px/1.5 -apple-system,Segoe UI,Roboto,sans-serif; background:var(--bg); color:var(--fg); }
.wrap { max-width:720px; margin:0 auto; padding:32px 20px 64px; }
h1 { font-size:22px; margin:0 0 4px; }
.sub { color:var(--mut); margin:0 0 24px; font-size:13px; }
.badge { display:inline-block; padding:2px 8px; border-radius:999px; background:#0b2b28; color:var(--acc); font-size:11px; font-weight:600; }
.card { background:var(--card); border:1px solid #232a36; border-radius:14px; padding:18px; }
textarea { width:100%; min-height:96px; resize:vertical; background:#0b0f16; color:var(--fg); border:1px solid #2a3342; border-radius:10px; padding:12px; font-size:15px; }
.row { display:flex; gap:12px; align-items:center; margin-top:14px; flex-wrap:wrap; }
button { background:linear-gradient(135deg,var(--acc),var(--acc2)); color:#04121a; border:0; font-weight:700; padding:11px 22px; border-radius:10px; cursor:pointer; font-size:15px; }
button:disabled { opacity:.5; cursor:default; }
.stat { font-variant-numeric:tabular-nums; color:var(--mut); font-size:13px; }
.stat b { color:var(--fg); }
.meter { height:6px; background:#0b0f16; border-radius:6px; overflow:hidden; margin-top:16px; }
.meter > i { display:block; height:100%; width:0; background:linear-gradient(90deg,var(--acc),var(--acc2)); transition:width .1s; }
.chips { margin-top:12px; display:flex; gap:8px; flex-wrap:wrap; }
.chip { font-size:12px; color:var(--mut); border:1px solid #2a3342; padding:4px 10px; border-radius:999px; cursor:pointer; }
.chip:hover { color:var(--fg); border-color:var(--acc); }
label.opt { color:var(--mut); font-size:13px; display:flex; align-items:center; gap:6px; }
.sectitle { font-size:12px; font-weight:700; letter-spacing:.08em; text-transform:uppercase; color:var(--mut); margin:30px 0 12px; }
.grid2 { display:grid; grid-template-columns:1fr 1fr; gap:16px; }
@media (max-width:560px){ .grid2 { grid-template-columns:1fr; } }
.subcard { background:var(--card); border:1px solid #232a36; border-radius:14px; padding:16px; display:flex; flex-direction:column; }
.subcard h3 { font-size:15px; margin:0 0 4px; display:flex; align-items:center; gap:6px; }
.subcard .tag { font-size:11px; color:var(--mut); font-weight:400; }
.subcard p { color:var(--mut); font-size:12.5px; margin:0 0 14px; flex:1; }
.subcard button { width:100%; }
#transcript:empty { display:none; }
#loading { position:fixed; inset:0; background:#0b0f16; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:14px; z-index:50; transition:opacity .3s; }
#loading.done { opacity:0; pointer-events:none; }
#loadlabel { color:var(--mut); font-size:13px; }
#loadbarwrap { width:280px; height:6px; background:#171c26; border-radius:6px; overflow:hidden; }
#loadbar { height:100%; width:0; background:linear-gradient(90deg,var(--acc),var(--acc2)); transition:width .2s; }
#loadnote { color:var(--mut); font-size:11px; max-width:280px; text-align:center; }
</style>
<script type="module" crossorigin src="./assets/index-fhMCuR15.js"></script>
</head>
<body>
<div id="loading">
<div style="font-size:18px;font-weight:700;">PrimeTTS · loading models…</div>
<div id="loadbarwrap"><div id="loadbar"></div></div>
<div id="loadlabel">Starting…</div>
<div id="loadnote">First load fetches ~600MB of ONNX/GGUF models (cached after that). Runs 100% in your browser — no server.</div>
</div>
<div class="wrap">
<h1>PrimeTTS · real-time streaming <span class="badge" id="backend">…</span></h1>
<p class="sub">Token-level MB-iSTFT-VITS · audio streams chunk-by-chunk (24 frames ≈ 384 ms) — first sound in ~100 ms. zh-TW + English. Runs entirely client-side (WASM).</p>
<div class="sectitle">① Type &amp; speak</div>
<div class="card">
<textarea id="text" placeholder="輸入中文或 English…">您好,幫您轉接給 Jason 王經理,他的分機是 2580。</textarea>
<div class="chips" id="chips"></div>
<div class="row">
<button id="go" disabled>▶ Speak (stream)</button>
<label class="opt">voice
<select id="voice" style="background:#0b0f16;color:var(--fg);border:1px solid #2a3342;border-radius:8px;padding:5px 8px;font-size:13px;"></select>
</label>
<label class="opt"><input type="checkbox" id="onenter" checked> stream on Enter</label>
<span class="stat">first-audio <b id="first">–</b> · chunks <b id="nc">0</b> · <b id="audio">0.0s</b></span>
<a id="dl" download="primetts.wav" style="display:none;color:var(--acc);font-size:13px;text-decoration:none;border:1px solid #2a3342;border-radius:8px;padding:5px 10px;">⬇ WAV</a>
</div>
<div class="meter"><i id="bar"></i></div>
</div>
<div id="chatcard" style="display:none;">
<div class="sectitle">② Streaming input · text is generated live, then spoken (zh-TW)</div>
<div class="grid2">
<div class="subcard" id="storyopt" style="display:none;">
<h3>📖 Story generator <span class="tag">llama2.c-zh 15M</span></h3>
<p>No input needed — generates a fresh short tale and speaks it as it writes.</p>
<button id="storygo">▶ Generate story</button>
</div>
<div class="subcard" id="chatopt" style="display:none;">
<h3>💬 Real-time chat <span class="tag">Gemma-3-270m-it</span></h3>
<p>Ask anything — it replies in zh-TW as it generates, spoken clause-by-clause.</p>
<textarea id="prompt" style="min-height:52px; margin-bottom:10px;" placeholder="輸入訊息…">請用一百字介紹台灣</textarea>
<button id="chatgo">▶ Send</button>
</div>
</div>
<div class="row"><span class="stat">first-audio <b id="cfirst">–</b> · <b id="caudio">0.0s</b></span>
<a id="cdl" download="primetts.wav" style="display:none;color:var(--acc);font-size:13px;text-decoration:none;border:1px solid #2a3342;border-radius:8px;padding:5px 10px;">⬇ WAV</a>
</div>
<div id="transcript" style="margin-top:12px; padding:14px; background:var(--card); border:1px solid #232a36; border-radius:12px; color:#cdd9e5; font-size:15px; line-height:1.8;"></div>
</div>
</div>
</body>
</html>