Conversation Playground (new section): - WhatsApp-style chat UI with user/assistant speech bubbles - Click-to-record mic button using MediaRecorder API - STT → LLM streaming → TTS pipeline via SSE (POST /api/conversation/turn) - LLM tokens stream into assistant bubble in real time - Audio auto-plays when TTS synthesises the reply - Right-side stats panel: STT / LLM TTFT / LLM total / TTS / Total with bar chart - Turn history list with per-turn total time and pass/fail indicator - Configurable: STT backend, LLM URL + model, TTS backend + voice, system prompt - Conversation history maintained across turns (last 20 messages sent to LLM) - GET /api/conversation/llm-models proxies model list from any OpenAI-compatible LLM XTTS v2 backend: - Registers xtts as a first-class TTS backend (xtts_url setting, display name, capabilities, health/voice discovery, OpenAI-compatible generation) - Added XTTS URL field to Settings → Connections - Use-as-TTS button now saves to xtts_url (not tts_url) - Batch benchmark backend select now refreshes alongside perf/preview selectors VibeVoice fix: - Added /voices to _TTS_VOICE_ENDPOINTS so VibeVoice voices are discovered Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
37 lines
1.4 KiB
JavaScript
37 lines
1.4 KiB
JavaScript
(async function () {
|
|
'use strict';
|
|
|
|
const SECTIONS = ['s-voices', 's-clone', 's-design', 's-studio', 's-tryout', 's-performance', 's-routing', 's-connect', 's-settings', 's-llms', 's-conversation'];
|
|
|
|
function loadScript(src) {
|
|
return new Promise(function (resolve, reject) {
|
|
var s = document.createElement('script');
|
|
s.src = src;
|
|
s.onload = resolve;
|
|
s.onerror = function () { reject(new Error('Failed to load ' + src)); };
|
|
document.body.appendChild(s);
|
|
});
|
|
}
|
|
|
|
// 1. Fetch all section partials in parallel and inject into their shells
|
|
await Promise.all(SECTIONS.map(async function (id) {
|
|
try {
|
|
var res = await fetch('/static/sections/' + id + '.html');
|
|
if (!res.ok) throw new Error(res.status + ' ' + res.statusText);
|
|
var html = await res.text();
|
|
var el = document.getElementById(id);
|
|
if (el) el.innerHTML = html;
|
|
} catch (e) {
|
|
console.error('[loader] section', id, 'failed:', e.message);
|
|
var el = document.getElementById(id);
|
|
if (el) el.innerHTML = '<p style="color:var(--red);padding:24px">Failed to load section <b>' + id + '</b>: ' + e.message + '</p>';
|
|
}
|
|
}));
|
|
|
|
// 2. Load main application logic (runs init immediately on parse — sections must exist first)
|
|
await loadScript('/static/app.js');
|
|
|
|
// 3. Apply scroll-based navigation overrides (must run after app.js defines switchTab etc.)
|
|
await loadScript('/static/nav.js');
|
|
})();
|