Replace all icons with MDI (Material Design Icons) v7.4.47 — 171 replacements across 10 files
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
21a219f816
commit
61a48ee84a
@ -38,7 +38,7 @@ function selectVoice(wrap) {
|
|||||||
restoreToRow(wrap);
|
restoreToRow(wrap);
|
||||||
wrap.classList.remove('vr-selected');
|
wrap.classList.remove('vr-selected');
|
||||||
_selectedVoiceWrap = null;
|
_selectedVoiceWrap = null;
|
||||||
inspector.innerHTML = '<div class="inspector-placeholder"><span>🔊</span><p>Pick a voice on the left<br>to edit it here</p></div>';
|
inspector.innerHTML = '<div class="inspector-placeholder"><span><span class="mdi mdi-account-voice"></span></span><p>Pick a voice on the left<br>to edit it here</p></div>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,7 +73,7 @@ function selectVoice(wrap) {
|
|||||||
const flagIconHtml = inspFlagIcon || '';
|
const flagIconHtml = inspFlagIcon || '';
|
||||||
|
|
||||||
const makeStars = n => [1,2,3,4,5].map(i =>
|
const makeStars = n => [1,2,3,4,5].map(i =>
|
||||||
`<span class="insp-star${i <= n ? ' on' : ''}" data-val="${i}">★</span>`
|
`<span class="insp-star${i <= n ? ' on' : ''}" data-val="${i}"><span class="mdi mdi-star"></span></span>`
|
||||||
).join('');
|
).join('');
|
||||||
|
|
||||||
const LANGS = ['EN','DE','IT','ES','FR','PT','NL','PL','ZH','JA','KO','AR','RU','TR','HI','SV','DA','FI','NB','HU','CS','RO','UK'];
|
const LANGS = ['EN','DE','IT','ES','FR','PT','NL','PL','ZH','JA','KO','AR','RU','TR','HI','SV','DA','FI','NB','HU','CS','RO','UK'];
|
||||||
@ -529,7 +529,7 @@ applyTheme(localStorage.getItem('vcf-theme') || 'dark');
|
|||||||
// ── Language helpers ──────────────────────────────────────────────────────
|
// ── Language helpers ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
function cc2flag(cc) {
|
function cc2flag(cc) {
|
||||||
if (!cc || cc.length !== 2) return '🌐';
|
if (!cc || cc.length !== 2) return '<span class="mdi mdi-earth"></span>';
|
||||||
return cc.toUpperCase().replace(/./g, c => String.fromCodePoint(c.charCodeAt(0) + 127397));
|
return cc.toUpperCase().replace(/./g, c => String.fromCodePoint(c.charCodeAt(0) + 127397));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1789,10 +1789,10 @@ function backendHelpHtml(b, compact = false) {
|
|||||||
b.true_streaming ? ['good', 'true streaming'] : ['', 'buffered/normal'],
|
b.true_streaming ? ['good', 'true streaming'] : ['', 'buffered/normal'],
|
||||||
].map(([cls, text]) => `<span class="backend-tag ${cls}">${escHtml(text)}</span>`).join('');
|
].map(([cls, text]) => `<span class="backend-tag ${cls}">${escHtml(text)}</span>`).join('');
|
||||||
const metricParts = [];
|
const metricParts = [];
|
||||||
if (b.speed) metricParts.push(`<span class="backend-metric-tag">⚡ ${escHtml(b.speed)}</span>`);
|
if (b.speed) metricParts.push(`<span class="backend-metric-tag"><span class="mdi mdi-lightning-bolt"></span> ${escHtml(b.speed)}</span>`);
|
||||||
if (b.latency) metricParts.push(`<span class="backend-metric-tag">⏰ ${escHtml(b.latency)}</span>`);
|
if (b.latency) metricParts.push(`<span class="backend-metric-tag"><span class="mdi mdi-clock-outline"></span> ${escHtml(b.latency)}</span>`);
|
||||||
if (b.quality) metricParts.push(`<span class="backend-metric-tag">⭐ ${escHtml(b.quality)}</span>`);
|
if (b.quality) metricParts.push(`<span class="backend-metric-tag"><span class="mdi mdi-star-circle-outline"></span> ${escHtml(b.quality)}</span>`);
|
||||||
if (b.ram) metricParts.push(`<span class="backend-metric-tag">💾 ${escHtml(b.ram)}</span>`);
|
if (b.ram) metricParts.push(`<span class="backend-metric-tag"><span class="mdi mdi-memory"></span> ${escHtml(b.ram)}</span>`);
|
||||||
const metrics = metricParts.length ? `<div class="backend-metrics-row">${metricParts.join('')}</div>` : '';
|
const metrics = metricParts.length ? `<div class="backend-metrics-row">${metricParts.join('')}</div>` : '';
|
||||||
const detail = compact ? escHtml(b.best_for || '') : `${escHtml(b.purpose || '')}<br><strong>Identity:</strong> ${escHtml(b.identity || '')}<br><strong>Style:</strong> ${escHtml(b.style || '')}<br><strong>Best for:</strong> ${escHtml(b.best_for || '')}`;
|
const detail = compact ? escHtml(b.best_for || '') : `${escHtml(b.purpose || '')}<br><strong>Identity:</strong> ${escHtml(b.identity || '')}<br><strong>Style:</strong> ${escHtml(b.style || '')}<br><strong>Best for:</strong> ${escHtml(b.best_for || '')}`;
|
||||||
return `<strong>${escHtml(b.label)}</strong><div class="backend-help-tags">${tags}</div>${metrics}<div>${detail}</div>`;
|
return `<strong>${escHtml(b.label)}</strong><div class="backend-help-tags">${tags}</div>${metrics}<div>${detail}</div>`;
|
||||||
@ -1802,10 +1802,10 @@ function sttBackendHelpHtml(b) {
|
|||||||
if (!b) return 'No STT engine selected.';
|
if (!b) return 'No STT engine selected.';
|
||||||
const m = b.metrics || {};
|
const m = b.metrics || {};
|
||||||
const metricParts = [];
|
const metricParts = [];
|
||||||
if (m.speed) metricParts.push(`<span class="backend-metric-tag">⚡ ${escHtml(m.speed)}</span>`);
|
if (m.speed) metricParts.push(`<span class="backend-metric-tag"><span class="mdi mdi-lightning-bolt"></span> ${escHtml(m.speed)}</span>`);
|
||||||
if (m.latency) metricParts.push(`<span class="backend-metric-tag">⏰ ${escHtml(m.latency)}</span>`);
|
if (m.latency) metricParts.push(`<span class="backend-metric-tag"><span class="mdi mdi-clock-outline"></span> ${escHtml(m.latency)}</span>`);
|
||||||
if (m.quality) metricParts.push(`<span class="backend-metric-tag">⭐ ${escHtml(m.quality)}</span>`);
|
if (m.quality) metricParts.push(`<span class="backend-metric-tag"><span class="mdi mdi-star-circle-outline"></span> ${escHtml(m.quality)}</span>`);
|
||||||
if (m.ram) metricParts.push(`<span class="backend-metric-tag">💾 ${escHtml(m.ram)}</span>`);
|
if (m.ram) metricParts.push(`<span class="backend-metric-tag"><span class="mdi mdi-memory"></span> ${escHtml(m.ram)}</span>`);
|
||||||
const metrics = metricParts.length ? `<div class="backend-metrics-row">${metricParts.join('')}</div>` : '';
|
const metrics = metricParts.length ? `<div class="backend-metrics-row">${metricParts.join('')}</div>` : '';
|
||||||
const modelList = Array.isArray(b.models) && b.models.length ? ' Models: ' + b.models.slice(0, 4).join(', ') + '.' : '';
|
const modelList = Array.isArray(b.models) && b.models.length ? ' Models: ' + b.models.slice(0, 4).join(', ') + '.' : '';
|
||||||
const avail = b.available ? `<span class="backend-tag good">ready</span>` : `<span class="backend-tag warn">unavailable</span>`;
|
const avail = b.available ? `<span class="backend-tag good">ready</span>` : `<span class="backend-tag warn">unavailable</span>`;
|
||||||
@ -1832,7 +1832,7 @@ function updateStyleBackendHelp(scope = document) {
|
|||||||
const suggestion = styleAwareBacks.length
|
const suggestion = styleAwareBacks.length
|
||||||
? ` Try <strong>${escHtml(styleAwareBacks[0].label)}</strong> instead.`
|
? ` Try <strong>${escHtml(styleAwareBacks[0].label)}</strong> instead.`
|
||||||
: ' No style-aware backend is currently reachable.';
|
: ' No style-aware backend is currently reachable.';
|
||||||
box.innerHTML += `<div class="style-backend-warn">⚠ This backend ignores the style instruction — output will sound the same regardless of what you type.${suggestion}</div>`;
|
box.innerHTML += `<div class="style-backend-warn"><span class="mdi mdi-alert-outline"></span> This backend ignores the style instruction — output will sound the same regardless of what you type.${suggestion}</div>`;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -4195,12 +4195,12 @@ function renderVoiceList() {
|
|||||||
emptyEl.innerHTML = `
|
emptyEl.innerHTML = `
|
||||||
<div class="voices-empty-cases">
|
<div class="voices-empty-cases">
|
||||||
<div class="voices-empty-case">
|
<div class="voices-empty-case">
|
||||||
<div class="voices-empty-case-icon">📁</div>
|
<div class="voices-empty-case-icon"><span class="mdi mdi-folder-outline"></span></div>
|
||||||
<h4>Wrong folder path?</h4>
|
<h4>Wrong folder path?</h4>
|
||||||
<p>Set the path where your <code>.wav</code> voice files live <em>inside the container</em>.</p>
|
<p>Set the path where your <code>.wav</code> voice files live <em>inside the container</em>.</p>
|
||||||
<div class="voices-folder-row">
|
<div class="voices-folder-row">
|
||||||
<input id="voices-empty-scan-dir" type="text" class="voices-folder-input" placeholder="/voices">
|
<input id="voices-empty-scan-dir" type="text" class="voices-folder-input" placeholder="/voices">
|
||||||
<button class="btn-secondary" id="voices-empty-browse-btn" title="Browse container filesystem">📁 Browse</button>
|
<button class="btn-secondary" id="voices-empty-browse-btn" title="Browse container filesystem"><span class="mdi mdi-folder-outline"></span> Browse</button>
|
||||||
<button class="btn-primary" id="voices-empty-save-btn">Set & reload</button>
|
<button class="btn-primary" id="voices-empty-save-btn">Set & reload</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="vef-dir-browser" id="vef-dir-browser" hidden>
|
<div class="vef-dir-browser" id="vef-dir-browser" hidden>
|
||||||
@ -4217,12 +4217,12 @@ function renderVoiceList() {
|
|||||||
</div>
|
</div>
|
||||||
<div class="voices-empty-divider">or</div>
|
<div class="voices-empty-divider">or</div>
|
||||||
<div class="voices-empty-case">
|
<div class="voices-empty-case">
|
||||||
<div class="voices-empty-case-icon">🎤</div>
|
<div class="voices-empty-case-icon"><span class="mdi mdi-microphone-variant"></span></div>
|
||||||
<h4>Folder is empty?</h4>
|
<h4>Folder is empty?</h4>
|
||||||
<p>Create your first voice from a recording or download ready-made voices.</p>
|
<p>Create your first voice from a recording or download ready-made voices.</p>
|
||||||
<div class="voices-empty-actions">
|
<div class="voices-empty-actions">
|
||||||
<button class="btn-primary voices-empty-action-btn" id="voices-goto-clone">⚡ Clone a Voice</button>
|
<button class="btn-primary voices-empty-action-btn" id="voices-goto-clone"><span class="mdi mdi-lightning-bolt"></span> Clone a Voice</button>
|
||||||
<button class="btn-secondary voices-empty-action-btn" id="voices-goto-studio">🌐 Get Voices Online</button>
|
<button class="btn-secondary voices-empty-action-btn" id="voices-goto-studio"><span class="mdi mdi-earth"></span> Get Voices Online</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -4262,7 +4262,7 @@ function renderVoiceList() {
|
|||||||
vefDirList.innerHTML = '<span class="vef-empty">No subdirectories here.</span>';
|
vefDirList.innerHTML = '<span class="vef-empty">No subdirectories here.</span>';
|
||||||
} else {
|
} else {
|
||||||
vefDirList.innerHTML = data.dirs.map(d =>
|
vefDirList.innerHTML = data.dirs.map(d =>
|
||||||
`<button class="vef-dir-item" data-path="${escHtml(data.path === '/' ? '/' + d : data.path + '/' + d)}">📁 ${escHtml(d)}</button>`
|
`<button class="vef-dir-item" data-path="${escHtml(data.path === '/' ? '/' + d : data.path + '/' + d)}"><span class="mdi mdi-folder-outline"></span> ${escHtml(d)}</button>`
|
||||||
).join('');
|
).join('');
|
||||||
vefDirList.querySelectorAll('.vef-dir-item').forEach(b => b.addEventListener('click', () => vefNavigate(b.dataset.path)));
|
vefDirList.querySelectorAll('.vef-dir-item').forEach(b => b.addEventListener('click', () => vefNavigate(b.dataset.path)));
|
||||||
}
|
}
|
||||||
@ -4413,7 +4413,7 @@ function makeVoiceRow(v) {
|
|||||||
// Use language-specific variants if there are multiple; fall back to world picker otherwise
|
// Use language-specific variants if there are multiple; fall back to world picker otherwise
|
||||||
const flagOpts = langOpts.length > 1 ? langOpts : ALL_FLAGS;
|
const flagOpts = langOpts.length > 1 ? langOpts : ALL_FLAGS;
|
||||||
const currentFlag = v.flag || LANG_FLAG_DEFAULT[langCode] || '';
|
const currentFlag = v.flag || LANG_FLAG_DEFAULT[langCode] || '';
|
||||||
const flagEmoji = currentFlag ? cc2flag(currentFlag) : '🌐';
|
const flagEmoji = currentFlag ? cc2flag(currentFlag) : '<span class="mdi mdi-earth"></span>';
|
||||||
const flagCode = currentFlag ? ccDisplay(currentFlag) : '?';
|
const flagCode = currentFlag ? ccDisplay(currentFlag) : '?';
|
||||||
wrap.dataset.flagEmoji = flagEmoji;
|
wrap.dataset.flagEmoji = flagEmoji;
|
||||||
wrap.dataset.flagCc = currentFlag || '';
|
wrap.dataset.flagCc = currentFlag || '';
|
||||||
@ -4432,7 +4432,7 @@ function makeVoiceRow(v) {
|
|||||||
const gender = v.gender || '';
|
const gender = v.gender || '';
|
||||||
|
|
||||||
const starsHtml = [1,2,3,4,5].map(i =>
|
const starsHtml = [1,2,3,4,5].map(i =>
|
||||||
`<span class="star${i<=(v.rating||0)?' on':''}" data-val="${i}">★</span>`
|
`<span class="star${i<=(v.rating||0)?' on':''}" data-val="${i}"><span class="mdi mdi-star"></span></span>`
|
||||||
).join('');
|
).join('');
|
||||||
|
|
||||||
const picSrc = v.has_picture ? `/api/voice/picture/${encodeURIComponent(v.id)}` : null;
|
const picSrc = v.has_picture ? `/api/voice/picture/${encodeURIComponent(v.id)}` : null;
|
||||||
@ -4468,14 +4468,14 @@ function makeVoiceRow(v) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="vr-play-group">
|
<div class="vr-play-group">
|
||||||
<div class="vr-play vr-play-original"><button class="play-btn-orig" title="Play original recording">▶</button></div>
|
<div class="vr-play vr-play-original"><button class="play-btn-orig" title="Play original recording"><span class="mdi mdi-play"></span></button></div>
|
||||||
<div class="vr-play vr-play-synth"><button class="play-btn-synth" title="Generate and play TTS preview">▶</button></div>
|
<div class="vr-play vr-play-synth"><button class="play-btn-synth" title="Generate and play TTS preview"><span class="mdi mdi-play"></span></button></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="vr-main-row">
|
<div class="vr-main-row">
|
||||||
<div class="vr-photo" title="Click to upload photo">
|
<div class="vr-photo" title="Click to upload photo">
|
||||||
${picSrc ? `<img src="${picSrc}" alt="">` : '<div class="ph-icon">👤</div>'}
|
${picSrc ? `<img src="${picSrc}" alt="">` : '<div class="ph-icon"><span class="mdi mdi-account-outline"></span></div>'}
|
||||||
<input type="file" class="photo-input" accept="image/*">
|
<input type="file" class="photo-input" accept="image/*">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -4497,8 +4497,8 @@ function makeVoiceRow(v) {
|
|||||||
<span class="vr-name-text" title="${escHtml(v.id)}">${escHtml(v.id)}</span>
|
<span class="vr-name-text" title="${escHtml(v.id)}">${escHtml(v.id)}</span>
|
||||||
<span class="rename-confirm">
|
<span class="rename-confirm">
|
||||||
<input class="vr-name-input" value="${escHtml(v.id)}" spellcheck="false">
|
<input class="vr-name-input" value="${escHtml(v.id)}" spellcheck="false">
|
||||||
<button class="icon-btn rename-ok" title="Save">✓</button>
|
<button class="icon-btn rename-ok" title="Save"><span class="mdi mdi-check"></span></button>
|
||||||
<button class="icon-btn rename-cancel" title="Cancel">✕</button>
|
<button class="icon-btn rename-cancel" title="Cancel"><span class="mdi mdi-close"></span></button>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -4519,15 +4519,15 @@ function makeVoiceRow(v) {
|
|||||||
<div class="vr-rating">${starsHtml}</div>
|
<div class="vr-rating">${starsHtml}</div>
|
||||||
|
|
||||||
<div class="vr-edit">
|
<div class="vr-edit">
|
||||||
<button class="edit-audio-btn" title="Edit audio">✎</button>
|
<button class="edit-audio-btn" title="Edit audio"><span class="mdi mdi-pencil-outline"></span></button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="vr-play-group">
|
<div class="vr-play-group">
|
||||||
<div class="vr-play vr-play-original">
|
<div class="vr-play vr-play-original">
|
||||||
<button title="Play / pause original recording">▶</button>
|
<button title="Play / pause original recording"><span class="mdi mdi-play"></span></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="vr-play vr-play-synth">
|
<div class="vr-play vr-play-synth">
|
||||||
<button title="Generate and play synthesized sample">▶</button>
|
<button title="Generate and play synthesized sample"><span class="mdi mdi-play"></span></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -4538,7 +4538,7 @@ function makeVoiceRow(v) {
|
|||||||
<label>Reference</label>
|
<label>Reference</label>
|
||||||
<div class="vr-inline">
|
<div class="vr-inline">
|
||||||
<input type="text" value="${escHtml(v.transcript||'')}" placeholder="No reference text" title="${escHtml(v.transcript||'')}">
|
<input type="text" value="${escHtml(v.transcript||'')}" placeholder="No reference text" title="${escHtml(v.transcript||'')}">
|
||||||
<button class="ref-transcribe-btn" title="Recognise reference text" style="${v.transcript ? 'display:none' : ''}">💬</button>
|
<button class="ref-transcribe-btn" title="Recognise reference text" style="${v.transcript ? 'display:none' : ''}"><span class="mdi mdi-message-text-outline"></span></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -5338,7 +5338,7 @@ function makeVoiceRow(v) {
|
|||||||
// Play original recording or synthesized sample
|
// Play original recording or synthesized sample
|
||||||
const originalPlayBtn = wrap.querySelector('.vr-play-original button');
|
const originalPlayBtn = wrap.querySelector('.vr-play-original button');
|
||||||
const synthPlayBtn = wrap.querySelector('.vr-play-synth button');
|
const synthPlayBtn = wrap.querySelector('.vr-play-synth button');
|
||||||
const playIcon = '▶', pauseIcon = '❙❙', generatingIcon = '⌛';
|
const playIcon = '<span class="mdi mdi-play"></span>', pauseIcon = '<span class="mdi mdi-pause-circle-outline"></span>', generatingIcon = '<span class="mdi mdi-timer-sand"></span>';
|
||||||
function setLibraryPlayButtonState(btn, state) {
|
function setLibraryPlayButtonState(btn, state) {
|
||||||
btn.classList.toggle('is-generating', state === 'generating');
|
btn.classList.toggle('is-generating', state === 'generating');
|
||||||
btn.innerHTML = state === 'playing' ? pauseIcon : (state === 'generating' ? generatingIcon : playIcon);
|
btn.innerHTML = state === 'playing' ? pauseIcon : (state === 'generating' ? generatingIcon : playIcon);
|
||||||
@ -5840,7 +5840,7 @@ $('save-preview-btn').addEventListener('click', () => {
|
|||||||
<span class="perf-stat"><strong>${min} ms</strong> best</span>
|
<span class="perf-stat"><strong>${min} ms</strong> best</span>
|
||||||
<span class="perf-stat"><strong>${max} ms</strong> worst</span>
|
<span class="perf-stat"><strong>${max} ms</strong> worst</span>
|
||||||
<span class="perf-stat"><strong>${avgRtf.toFixed(2)}</strong> avg RTF</span>
|
<span class="perf-stat"><strong>${avgRtf.toFixed(2)}</strong> avg RTF</span>
|
||||||
<span class="perf-stat ${avgRtf < 1 ? 'perf-good' : 'perf-slow'}">${avgRtf < 1 ? '✅ Real-time capable' : '⚠ Slower than real-time'}</span>
|
<span class="perf-stat ${avgRtf < 1 ? 'perf-good' : 'perf-slow'}">${avgRtf < 1 ? '<span class="mdi mdi-check-circle-outline"></span> Real-time capable' : '<span class="mdi mdi-alert-outline"></span> Slower than real-time'}</span>
|
||||||
`;
|
`;
|
||||||
} else { perfSummary.innerHTML = '<span class="perf-err">All runs failed</span>'; }
|
} else { perfSummary.innerHTML = '<span class="perf-err">All runs failed</span>'; }
|
||||||
}
|
}
|
||||||
@ -6370,7 +6370,7 @@ loadSettings().then(() => {
|
|||||||
<button class="el-play" title="Play preview" data-preview="${prev}">▶ Play</button>
|
<button class="el-play" title="Play preview" data-preview="${prev}">▶ Play</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
${prev ? `<div class="el-vc-footer"><button class="el-clone" title="Import to Clone a Voice" data-preview="${prev}" data-name="${nm}">↓ Clone</button></div>` : ''}
|
${prev ? `<div class="el-vc-footer"><button class="el-clone" title="Import to Clone a Voice" data-preview="${prev}" data-name="${nm}"><span class="mdi mdi-download"></span> Clone</button></div>` : ''}
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -6448,24 +6448,24 @@ function renderLocalContainers(containers) {
|
|||||||
|
|
||||||
const ROLE_LABEL = { tts: 'TTS', stt: 'STT', 'stt+tts': 'STT · TTS', llm: 'LLM' };
|
const ROLE_LABEL = { tts: 'TTS', stt: 'STT', 'stt+tts': 'STT · TTS', llm: 'LLM' };
|
||||||
const DC_ICONS = {
|
const DC_ICONS = {
|
||||||
'faster-qwen3-tts-voiceclone': '🔊',
|
'faster-qwen3-tts-voiceclone': '<span class="mdi mdi-speaker-outline"></span>',
|
||||||
'faster-qwen3-tts-voicedesign': '✨',
|
'faster-qwen3-tts-voicedesign': '<span class="mdi mdi-auto-fix"></span>',
|
||||||
'faster-qwen3-tts-customvoice': '🎭',
|
'faster-qwen3-tts-customvoice': '🎭',
|
||||||
'faster-qwen3-tts-streaming': '⚡',
|
'faster-qwen3-tts-streaming': '<span class="mdi mdi-lightning-bolt"></span>',
|
||||||
'parakeet-asr': '🦜',
|
'parakeet-asr': '🦜',
|
||||||
'magpie-tts': '🐦',
|
'magpie-tts': '🐦',
|
||||||
'parakeet-rnnt-nim': '🦜',
|
'parakeet-rnnt-nim': '🦜',
|
||||||
};
|
};
|
||||||
const DC_METRICS = {
|
const DC_METRICS = {
|
||||||
'faster-qwen3-tts-voiceclone': [['⚡','~0.3× GPU'],['⏰','1–3 s'],['⭐','Premium clone'],['💾','6–8 GB VRAM']],
|
'faster-qwen3-tts-voiceclone': [['<span class="mdi mdi-lightning-bolt"></span>','~0.3× GPU'],['⏰','1–3 s'],['⭐','Premium clone'],['💾','6–8 GB VRAM']],
|
||||||
'faster-qwen3-tts-voicedesign': [['⚡','~0.4× GPU'],['⏰','1–3 s'],['⭐','Premium'], ['💾','6–8 GB VRAM']],
|
'faster-qwen3-tts-voicedesign': [['<span class="mdi mdi-lightning-bolt"></span>','~0.4× GPU'],['⏰','1–3 s'],['⭐','Premium'], ['💾','6–8 GB VRAM']],
|
||||||
'faster-qwen3-tts-customvoice': [['⚡','~0.3× GPU'],['⏰','1–3 s'],['⭐','Premium'], ['💾','6–8 GB VRAM']],
|
'faster-qwen3-tts-customvoice': [['<span class="mdi mdi-lightning-bolt"></span>','~0.3× GPU'],['⏰','1–3 s'],['⭐','Premium'], ['💾','6–8 GB VRAM']],
|
||||||
'faster-qwen3-tts-streaming': [['⚡','~0.1× GPU'],['⏰','0.5–1 s'],['⭐','Premium'], ['💾','6–8 GB VRAM']],
|
'faster-qwen3-tts-streaming': [['<span class="mdi mdi-lightning-bolt"></span>','~0.1× GPU'],['⏰','0.5–1 s'],['⭐','Premium'], ['💾','6–8 GB VRAM']],
|
||||||
'magpie-tts': [['⚡','~0.05× GPU'],['⏰','0.3–0.8 s'],['⭐','High'], ['💾','4–6 GB VRAM']],
|
'magpie-tts': [['<span class="mdi mdi-lightning-bolt"></span>','~0.05× GPU'],['⏰','0.3–0.8 s'],['⭐','High'], ['💾','4–6 GB VRAM']],
|
||||||
'parakeet-asr': [['⚡','~200× RT GPU'],['⏰','<0.3 s'],['⭐','Parakeet-TDT'],['💾','2 GB VRAM']],
|
'parakeet-asr': [['<span class="mdi mdi-lightning-bolt"></span>','~200× RT GPU'],['⏰','<0.3 s'],['⭐','Parakeet-TDT'],['💾','2 GB VRAM']],
|
||||||
'parakeet-rnnt-nim': [['⚡','~200× RT GPU'],['⏰','<0.3 s'],['⭐','Parakeet-1B'], ['💾','2 GB VRAM']],
|
'parakeet-rnnt-nim': [['<span class="mdi mdi-lightning-bolt"></span>','~200× RT GPU'],['⏰','<0.3 s'],['⭐','Parakeet-1B'], ['💾','2 GB VRAM']],
|
||||||
};
|
};
|
||||||
const roleIcon = { tts: '🔊', stt: '🎙️', 'stt+tts': '🔄', llm: '🤖' };
|
const roleIcon = { tts: '<span class="mdi mdi-speaker-outline"></span>', stt: '<span class="mdi mdi-microphone-variant"></span>️', 'stt+tts': '🔄', llm: '<span class="mdi mdi-robot-outline"></span>' };
|
||||||
|
|
||||||
grid.innerHTML = containers.map(c => {
|
grid.innerHTML = containers.map(c => {
|
||||||
const st = c.status || 'not_found';
|
const st = c.status || 'not_found';
|
||||||
@ -6493,7 +6493,7 @@ function renderLocalContainers(containers) {
|
|||||||
: `<button class="btn-primary dc-btn" data-dc-action="start" data-dc-name="${n}">Start</button>
|
: `<button class="btn-primary dc-btn" data-dc-action="start" data-dc-name="${n}">Start</button>
|
||||||
<button class="btn-secondary dc-btn" data-dc-action="restart" data-dc-name="${n}">Restart</button>`)
|
<button class="btn-secondary dc-btn" data-dc-action="restart" data-dc-name="${n}">Restart</button>`)
|
||||||
: (c.repo
|
: (c.repo
|
||||||
? `<a class="btn-secondary dc-btn" href="${escHtml(c.repo)}" target="_blank" rel="noopener">View on GitHub ↗</a>`
|
? `<a class="btn-secondary dc-btn" href="${escHtml(c.repo)}" target="_blank" rel="noopener">View on GitHub <span class="mdi mdi-open-in-new link-icon"></span></a>`
|
||||||
: '');
|
: '');
|
||||||
|
|
||||||
return `<div class="dc-card${st === 'running' ? ' dc-card-running' : ''}">
|
return `<div class="dc-card${st === 'running' ? ' dc-card-running' : ''}">
|
||||||
@ -6864,8 +6864,8 @@ function renderHistory() {
|
|||||||
</div>
|
</div>
|
||||||
<div class="history-item-text">${preview}</div>
|
<div class="history-item-text">${preview}</div>
|
||||||
<div class="history-item-actions">
|
<div class="history-item-actions">
|
||||||
<button class="btn-secondary btn-xs hist-play-btn" ${item.blob?'':'disabled'}>▶ Play</button>
|
<button class="btn-secondary btn-xs hist-play-btn" ${item.blob?'':'disabled'}><span class="mdi mdi-play"></span> Play</button>
|
||||||
<button class="btn-secondary btn-xs hist-reuse-btn">↵ Reuse</button>
|
<button class="btn-secondary btn-xs hist-reuse-btn"><span class="mdi mdi-restore"></span> Reuse</button>
|
||||||
<button class="btn-secondary btn-xs hist-playlist-btn" ${item.blob?'':'disabled'}>+ Playlist</button>
|
<button class="btn-secondary btn-xs hist-playlist-btn" ${item.blob?'':'disabled'}>+ Playlist</button>
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
@ -6932,9 +6932,9 @@ function renderPlaylist() {
|
|||||||
<span class="playlist-text">${escHtml(item.text.length > 70 ? item.text.slice(0,70)+'…' : item.text)}</span>
|
<span class="playlist-text">${escHtml(item.text.length > 70 ? item.text.slice(0,70)+'…' : item.text)}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="playlist-item-actions">
|
<div class="playlist-item-actions">
|
||||||
<button class="btn-secondary btn-xs pl-up" ${i===0?'disabled':''}>↑</button>
|
<button class="btn-secondary btn-xs pl-up" ${i===0?'disabled':''}><span class="mdi mdi-upload"></span></button>
|
||||||
<button class="btn-secondary btn-xs pl-dn" ${i===_playlist.length-1?'disabled':''}>↓</button>
|
<button class="btn-secondary btn-xs pl-dn" ${i===_playlist.length-1?'disabled':''}><span class="mdi mdi-download"></span></button>
|
||||||
<button class="btn-secondary btn-xs pl-rm">✕</button>
|
<button class="btn-secondary btn-xs pl-rm"><span class="mdi mdi-close"></span></button>
|
||||||
</div>
|
</div>
|
||||||
</div>`).join('');
|
</div>`).join('');
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
<!-- WaveSurfer must load synchronously before app.js initialises audio -->
|
<!-- WaveSurfer must load synchronously before app.js initialises audio -->
|
||||||
<script src="https://unpkg.com/wavesurfer.js@7/dist/wavesurfer.min.js"></script>
|
<script src="https://unpkg.com/wavesurfer.js@7/dist/wavesurfer.min.js"></script>
|
||||||
<script src="https://unpkg.com/wavesurfer.js@7/dist/plugins/regions.min.js"></script>
|
<script src="https://unpkg.com/wavesurfer.js@7/dist/plugins/regions.min.js"></script>
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@7.4.47/css/materialdesignicons.min.css">
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flag-icons@7.3.2/css/flag-icons.min.css">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flag-icons@7.3.2/css/flag-icons.min.css">
|
||||||
<link rel="stylesheet" href="/static/style.css">
|
<link rel="stylesheet" href="/static/style.css">
|
||||||
</head>
|
</head>
|
||||||
@ -32,17 +33,17 @@
|
|||||||
<!-- Sidebar -->
|
<!-- Sidebar -->
|
||||||
<aside id="sidebar">
|
<aside id="sidebar">
|
||||||
<div class="sidebar-brand">
|
<div class="sidebar-brand">
|
||||||
<h1>🎤 Voice Creator</h1>
|
<h1><span class="mdi mdi-microphone-variant"></span> Voice Creator</h1>
|
||||||
<p>Clone · Design · Deploy</p>
|
<p>Clone · Design · Deploy</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<nav class="sidebar-nav">
|
<nav class="sidebar-nav">
|
||||||
<div class="nav-group-label">Voices</div>
|
<div class="nav-group-label">Voices</div>
|
||||||
<div class="nav-item nav-tree-head active" data-nav-section="s-voices" id="nav-voices-head" onclick="navTo('s-voices')">
|
<div class="nav-item nav-tree-head active" data-nav-section="s-voices" id="nav-voices-head" onclick="navTo('s-voices')">
|
||||||
<span class="nav-icon">🔊</span>
|
<span class="nav-icon"><span class="mdi mdi-account-voice"></span></span>
|
||||||
<span class="nav-label">My Voices</span>
|
<span class="nav-label">My Voices</span>
|
||||||
<span class="nav-badge" id="nav-voices-count"></span>
|
<span class="nav-badge" id="nav-voices-count"></span>
|
||||||
<span class="nav-chevron" id="nav-voices-chevron">⌄</span>
|
<span class="nav-chevron" id="nav-voices-chevron"><span class="mdi mdi-chevron-down"></span></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="nav-tree open" id="nav-voices-tree">
|
<div class="nav-tree open" id="nav-voices-tree">
|
||||||
<div class="nav-tree-item is-active" data-voice-cat="all" onclick="navVoicesCat('all')">All voices <span class="ntc" id="ntc-all"></span></div>
|
<div class="nav-tree-item is-active" data-voice-cat="all" onclick="navVoicesCat('all')">All voices <span class="ntc" id="ntc-all"></span></div>
|
||||||
@ -53,36 +54,36 @@
|
|||||||
<div class="nav-tree-item" data-voice-cat="tools" onclick="navVoicesCat('tools')">Library tools</div>
|
<div class="nav-tree-item" data-voice-cat="tools" onclick="navVoicesCat('tools')">Library tools</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="nav-item" data-nav-section="s-clone" onclick="navTo('s-clone')">
|
<div class="nav-item" data-nav-section="s-clone" onclick="navTo('s-clone')">
|
||||||
<span class="nav-icon">🎤</span> Clone a Voice
|
<span class="nav-icon"><span class="mdi mdi-microphone-variant"></span></span> Clone a Voice
|
||||||
</div>
|
</div>
|
||||||
<div class="nav-item" data-nav-section="s-design" onclick="navTo('s-design')">
|
<div class="nav-item" data-nav-section="s-design" onclick="navTo('s-design')">
|
||||||
<span class="nav-icon">✨</span> Design a Voice
|
<span class="nav-icon"><span class="mdi mdi-auto-fix"></span></span> Design a Voice
|
||||||
</div>
|
</div>
|
||||||
<div class="nav-item" data-nav-section="s-studio" onclick="navTo('s-studio')">
|
<div class="nav-item" data-nav-section="s-studio" onclick="navTo('s-studio')">
|
||||||
<span class="nav-icon">🌐</span> Get Voices Online
|
<span class="nav-icon"><span class="mdi mdi-earth"></span></span> Get Voices Online
|
||||||
</div>
|
</div>
|
||||||
<div class="nav-item" data-nav-section="s-tryout" onclick="navTo('s-tryout')">
|
<div class="nav-item" data-nav-section="s-tryout" onclick="navTo('s-tryout')">
|
||||||
<span class="nav-icon">▶</span> Try It Out
|
<span class="nav-icon"><span class="mdi mdi-play"></span></span> Try It Out
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="nav-group-label" style="margin-top:6px">Setup</div>
|
<div class="nav-group-label" style="margin-top:6px">Setup</div>
|
||||||
<div class="nav-item" data-nav-section="s-llms" onclick="navTo('s-llms')">
|
<div class="nav-item" data-nav-section="s-llms" onclick="navTo('s-llms')">
|
||||||
<span class="nav-icon">🧠</span> LLMs
|
<span class="nav-icon"><span class="mdi mdi-brain"></span></span> LLMs
|
||||||
</div>
|
</div>
|
||||||
<div class="nav-item" data-nav-section="s-routing" onclick="navTo('s-routing')">
|
<div class="nav-item" data-nav-section="s-routing" onclick="navTo('s-routing')">
|
||||||
<span class="nav-icon">⇌</span> App Routing
|
<span class="nav-icon"><span class="mdi mdi-swap-horizontal"></span></span> App Routing
|
||||||
</div>
|
</div>
|
||||||
<div class="nav-item" data-nav-section="s-connect" onclick="navTo('s-connect')">
|
<div class="nav-item" data-nav-section="s-connect" onclick="navTo('s-connect')">
|
||||||
<span class="nav-icon">🔗</span> Connect Apps
|
<span class="nav-icon"><span class="mdi mdi-api"></span></span> Connect Apps
|
||||||
</div>
|
</div>
|
||||||
<div class="nav-item" data-nav-section="s-settings" onclick="navTo('s-settings')">
|
<div class="nav-item" data-nav-section="s-settings" onclick="navTo('s-settings')">
|
||||||
<span class="nav-icon">⚙</span> Settings
|
<span class="nav-icon"><span class="mdi mdi-cog-outline"></span></span> Settings
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div class="sidebar-footer">
|
<div class="sidebar-footer">
|
||||||
<button id="theme-btn" title="Switch theme" style="display:none">☀</button>
|
<button id="theme-btn" title="Switch theme" style="display:none"><span class="mdi mdi-weather-sunny"></span></button>
|
||||||
<button id="settings-btn" onclick="navTo('s-settings')" title="Open settings">⚙ Settings</button>
|
<button id="settings-btn" onclick="navTo('s-settings')" title="Open settings"><span class="mdi mdi-cog-outline"></span> Settings</button>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<div class="section-head">
|
<div class="section-head">
|
||||||
<span class="section-icon">🎤</span>
|
<span class="section-icon"><span class="mdi mdi-microphone-variant"></span></span>
|
||||||
<div class="section-title">
|
<div class="section-title">
|
||||||
<h2>Clone a Voice</h2>
|
<h2>Clone a Voice</h2>
|
||||||
<p>Upload or record 3-20 seconds of audio, trim it, then save it as a reusable voice clone.</p>
|
<p>Upload or record 3-20 seconds of audio, trim it, then save it as a reusable voice clone.</p>
|
||||||
@ -31,8 +31,8 @@
|
|||||||
<h2>Microphone</h2>
|
<h2>Microphone</h2>
|
||||||
<p class="card-subtitle">Record a fresh sample with live input level monitoring.</p>
|
<p class="card-subtitle">Record a fresh sample with live input level monitoring.</p>
|
||||||
<div class="btn-row">
|
<div class="btn-row">
|
||||||
<button class="btn-red" id="rec-start-btn">● Record</button>
|
<button class="btn-red" id="rec-start-btn"><span class="mdi mdi-record-circle-outline"></span> Record</button>
|
||||||
<button class="btn-secondary" id="rec-stop-btn" disabled>■ Stop</button>
|
<button class="btn-secondary" id="rec-stop-btn" disabled><span class="mdi mdi-stop-circle-outline"></span> Stop</button>
|
||||||
<div id="rec-indicator"><div id="rec-dot"></div><span id="rec-time">0:00</span></div>
|
<div id="rec-indicator"><div id="rec-dot"></div><span id="rec-time">0:00</span></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -48,8 +48,8 @@
|
|||||||
<div class="btn-row">
|
<div class="btn-row">
|
||||||
<button class="btn-primary" id="trim-btn">Confirm trim → Name & Save</button>
|
<button class="btn-primary" id="trim-btn">Confirm trim → Name & Save</button>
|
||||||
<button class="btn-secondary" id="auto-trim-btn">Auto trim</button>
|
<button class="btn-secondary" id="auto-trim-btn">Auto trim</button>
|
||||||
<button class="btn-secondary" id="play-btn">▶ Play</button>
|
<button class="btn-secondary" id="play-btn"><span class="mdi mdi-play"></span> Play</button>
|
||||||
<button class="btn-secondary" id="play-selection-btn">▶ Play selection</button>
|
<button class="btn-secondary" id="play-selection-btn"><span class="mdi mdi-play"></span> Play selection</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div><!-- /tab-source -->
|
</div><!-- /tab-source -->
|
||||||
@ -95,7 +95,7 @@
|
|||||||
<h2>Transcript (reference text)</h2>
|
<h2>Transcript (reference text)</h2>
|
||||||
<p class="card-subtitle">The spoken text that matches this audio — type it or auto-transcribe.</p>
|
<p class="card-subtitle">The spoken text that matches this audio — type it or auto-transcribe.</p>
|
||||||
<div class="btn-row">
|
<div class="btn-row">
|
||||||
<button class="btn-secondary" id="transcribe-btn">💬 Auto-transcribe</button>
|
<button class="btn-secondary" id="transcribe-btn"><span class="mdi mdi-message-text-outline"></span> Auto-transcribe</button>
|
||||||
<span id="transcribe-status" style="font-size:13px;color:var(--subtext)"></span>
|
<span id="transcribe-status" style="font-size:13px;color:var(--subtext)"></span>
|
||||||
</div>
|
</div>
|
||||||
<textarea id="transcript-area" placeholder="Type or auto-transcribe the spoken text…"></textarea>
|
<textarea id="transcript-area" placeholder="Type or auto-transcribe the spoken text…"></textarea>
|
||||||
@ -110,7 +110,7 @@
|
|||||||
<h2>Step 4 — Save to library</h2>
|
<h2>Step 4 — Save to library</h2>
|
||||||
<p class="card-subtitle">Write the cloned voice to your active voices folder.</p>
|
<p class="card-subtitle">Write the cloned voice to your active voices folder.</p>
|
||||||
<div class="btn-row">
|
<div class="btn-row">
|
||||||
<button class="btn-green" id="save-btn">💾 Save to Voice Library</button>
|
<button class="btn-green" id="save-btn"><span class="mdi mdi-memory"></span> Save to Voice Library</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="save-result" class="note" style="display:none"></div>
|
<div id="save-result" class="note" style="display:none"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<div class="section-head">
|
<div class="section-head">
|
||||||
<span class="section-icon">🔗</span>
|
<span class="section-icon"><span class="mdi mdi-api"></span></span>
|
||||||
<div class="section-title">
|
<div class="section-title">
|
||||||
<h2>Connect Your Apps</h2>
|
<h2>Connect Your Apps</h2>
|
||||||
<p>Copy ready-made configuration snippets for SillyTavern, Open WebUI, Home Assistant, and more.</p>
|
<p>Copy ready-made configuration snippets for SillyTavern, Open WebUI, Home Assistant, and more.</p>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<div class="section-head">
|
<div class="section-head">
|
||||||
<span class="section-icon">✨</span>
|
<span class="section-icon"><span class="mdi mdi-auto-fix"></span></span>
|
||||||
<div class="section-title">
|
<div class="section-title">
|
||||||
<h2>Design a Voice</h2>
|
<h2>Design a Voice</h2>
|
||||||
<p>Describe a voice in words and let the AI create it. No recording needed.</p>
|
<p>Describe a voice in words and let the AI create it. No recording needed.</p>
|
||||||
@ -70,7 +70,7 @@
|
|||||||
<p class="card-subtitle">This shared reference transcript is used by samples, prompt presets, generation, preview, download, and export to the Voice Clone Library.</p>
|
<p class="card-subtitle">This shared reference transcript is used by samples, prompt presets, generation, preview, download, and export to the Voice Clone Library.</p>
|
||||||
<textarea id="design-sample-text" placeholder="The exact text the generated voice should speak...">Hello! This is a voice design sample. I hope you enjoy listening to me.</textarea>
|
<textarea id="design-sample-text" placeholder="The exact text the generated voice should speak...">Hello! This is a voice design sample. I hope you enjoy listening to me.</textarea>
|
||||||
<div class="btn-row">
|
<div class="btn-row">
|
||||||
<button class="btn-teal" id="design-generate-btn" style="align-self:flex-end">✨ Generate voice</button>
|
<button class="btn-teal" id="design-generate-btn" style="align-self:flex-end"><span class="mdi mdi-auto-fix"></span> Generate voice</button>
|
||||||
<span id="design-status" style="font-size:13px;color:var(--subtext);align-self:flex-end"></span>
|
<span id="design-status" style="font-size:13px;color:var(--subtext);align-self:flex-end"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -115,9 +115,9 @@
|
|||||||
<textarea id="d-transcript" placeholder="The spoken text from the sample (auto-filled)…"></textarea>
|
<textarea id="d-transcript" placeholder="The spoken text from the sample (auto-filled)…"></textarea>
|
||||||
</div>
|
</div>
|
||||||
<div class="btn-row">
|
<div class="btn-row">
|
||||||
<button class="btn-secondary" id="design-retry-btn">↻ Regenerate</button>
|
<button class="btn-secondary" id="design-retry-btn"><span class="mdi mdi-refresh"></span> Regenerate</button>
|
||||||
<button class="btn-secondary" id="design-download-btn">↓ Download WAV</button>
|
<button class="btn-secondary" id="design-download-btn"><span class="mdi mdi-download"></span> Download WAV</button>
|
||||||
<button class="btn-green" id="design-save-btn">💾 Export to Voice Clone Library</button>
|
<button class="btn-green" id="design-save-btn"><span class="mdi mdi-memory"></span> Export to Voice Clone Library</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="design-save-result" style="display:none">
|
<div id="design-save-result" style="display:none">
|
||||||
<hr>
|
<hr>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<div class="section-head">
|
<div class="section-head">
|
||||||
<span class="section-icon">🤖</span>
|
<span class="section-icon"><span class="mdi mdi-robot-outline"></span></span>
|
||||||
<div class="section-title">
|
<div class="section-title">
|
||||||
<h2>AI Backends</h2>
|
<h2>AI Backends</h2>
|
||||||
<p>Connect local or cloud services for speech recognition, synthesis, and text generation.</p>
|
<p>Connect local or cloud services for speech recognition, synthesis, and text generation.</p>
|
||||||
@ -8,8 +8,8 @@
|
|||||||
|
|
||||||
<!-- Category tabs: Local / Online -->
|
<!-- Category tabs: Local / Online -->
|
||||||
<div class="llm-cat-tabs" id="llm-cat-tabs">
|
<div class="llm-cat-tabs" id="llm-cat-tabs">
|
||||||
<button class="llm-cat-tab active" data-llm-cat="local">💻 Local</button>
|
<button class="llm-cat-tab active" data-llm-cat="local"><span class="mdi mdi-laptop"></span> Local</button>
|
||||||
<button class="llm-cat-tab" data-llm-cat="online">🌐 Online APIs</button>
|
<button class="llm-cat-tab" data-llm-cat="online"><span class="mdi mdi-earth"></span> Online APIs</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ── LOCAL section ──────────────────────────────────────── -->
|
<!-- ── LOCAL section ──────────────────────────────────────── -->
|
||||||
@ -28,7 +28,7 @@
|
|||||||
<h4 class="llm-local-cat" style="margin:0">Active Docker Stack</h4>
|
<h4 class="llm-local-cat" style="margin:0">Active Docker Stack</h4>
|
||||||
<p class="llm-sec-note" style="margin:2px 0 0">Start, stop, or install TTS and ASR containers running on this machine.</p>
|
<p class="llm-sec-note" style="margin:2px 0 0">Start, stop, or install TTS and ASR containers running on this machine.</p>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn-secondary" id="dc-refresh-btn" type="button">↻ Refresh</button>
|
<button class="btn-secondary" id="dc-refresh-btn" type="button"><span class="mdi mdi-refresh"></span> Refresh</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="dc-grid" class="dc-grid">
|
<div id="dc-grid" class="dc-grid">
|
||||||
<div class="dc-loading">Checking container status…</div>
|
<div class="dc-loading">Checking container status…</div>
|
||||||
@ -40,7 +40,7 @@
|
|||||||
|
|
||||||
<div class="llm-local-card">
|
<div class="llm-local-card">
|
||||||
<div class="llm-local-head">
|
<div class="llm-local-head">
|
||||||
<span class="llm-local-icon">🦙</span>
|
<span class="llm-local-icon"><span class="mdi mdi-head-dots-horizontal-outline"></span></span>
|
||||||
<span class="llm-local-name">Ollama</span>
|
<span class="llm-local-name">Ollama</span>
|
||||||
<span class="llm-local-compat">OpenAI-compat</span>
|
<span class="llm-local-compat">OpenAI-compat</span>
|
||||||
</div>
|
</div>
|
||||||
@ -58,12 +58,12 @@
|
|||||||
<pre>curl https://ollama.ai/install.sh | sh
|
<pre>curl https://ollama.ai/install.sh | sh
|
||||||
ollama pull llama3.3</pre>
|
ollama pull llama3.3</pre>
|
||||||
</div>
|
</div>
|
||||||
<a class="llm-local-link" href="https://ollama.ai" target="_blank" rel="noopener">ollama.ai ↗</a>
|
<a class="llm-local-link" href="https://ollama.ai" target="_blank" rel="noopener">ollama.ai <span class="mdi mdi-open-in-new link-icon"></span></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="llm-local-card llm-local-card-running">
|
<div class="llm-local-card llm-local-card-running">
|
||||||
<div class="llm-local-head">
|
<div class="llm-local-head">
|
||||||
<span class="llm-local-icon">⚡</span>
|
<span class="llm-local-icon"><span class="mdi mdi-lightning-bolt"></span></span>
|
||||||
<span class="llm-local-name">vLLM</span>
|
<span class="llm-local-name">vLLM</span>
|
||||||
<span class="llm-local-compat llm-compat-detected">Detected in stack</span>
|
<span class="llm-local-compat llm-compat-detected">Detected in stack</span>
|
||||||
</div>
|
</div>
|
||||||
@ -81,7 +81,7 @@ ollama pull llama3.3</pre>
|
|||||||
<pre>--model Qwen/Qwen3-8B \
|
<pre>--model Qwen/Qwen3-8B \
|
||||||
--served-model-name qwen3-8b</pre>
|
--served-model-name qwen3-8b</pre>
|
||||||
</div>
|
</div>
|
||||||
<a class="llm-local-link" href="https://docs.vllm.ai" target="_blank" rel="noopener">docs.vllm.ai ↗</a>
|
<a class="llm-local-link" href="https://docs.vllm.ai" target="_blank" rel="noopener">docs.vllm.ai <span class="mdi mdi-open-in-new link-icon"></span></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="llm-local-card">
|
<div class="llm-local-card">
|
||||||
@ -101,7 +101,7 @@ ollama pull llama3.3</pre>
|
|||||||
<pre>Developer tab → Start server → Port 1234
|
<pre>Developer tab → Start server → Port 1234
|
||||||
Check "Enable CORS" for browser access</pre>
|
Check "Enable CORS" for browser access</pre>
|
||||||
</div>
|
</div>
|
||||||
<a class="llm-local-link" href="https://lmstudio.ai" target="_blank" rel="noopener">lmstudio.ai ↗</a>
|
<a class="llm-local-link" href="https://lmstudio.ai" target="_blank" rel="noopener">lmstudio.ai <span class="mdi mdi-open-in-new link-icon"></span></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="llm-local-card">
|
<div class="llm-local-card">
|
||||||
@ -125,7 +125,7 @@ Check "Enable CORS" for browser access</pre>
|
|||||||
-hf QuantFactory/Meta-Llama-3-8B-GGUF \
|
-hf QuantFactory/Meta-Llama-3-8B-GGUF \
|
||||||
-hff Meta-Llama-3-8B.Q4_K_M.gguf</pre>
|
-hff Meta-Llama-3-8B.Q4_K_M.gguf</pre>
|
||||||
</div>
|
</div>
|
||||||
<a class="llm-local-link" href="https://github.com/ggml-org/llama.cpp" target="_blank" rel="noopener">github.com/ggml-org/llama.cpp ↗</a>
|
<a class="llm-local-link" href="https://github.com/ggml-org/llama.cpp" target="_blank" rel="noopener">github.com/ggml-org/llama.cpp <span class="mdi mdi-open-in-new link-icon"></span></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@ -136,15 +136,15 @@ Check "Enable CORS" for browser access</pre>
|
|||||||
|
|
||||||
<div class="llm-local-card">
|
<div class="llm-local-card">
|
||||||
<div class="llm-local-head">
|
<div class="llm-local-head">
|
||||||
<span class="llm-local-icon">🎙️</span>
|
<span class="llm-local-icon"><span class="mdi mdi-microphone-variant"></span>️</span>
|
||||||
<span class="llm-local-name">faster-whisper-server</span>
|
<span class="llm-local-name">faster-whisper-server</span>
|
||||||
<span class="llm-local-compat">OpenAI-compat</span>
|
<span class="llm-local-compat">OpenAI-compat</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="llm-local-metrics">
|
<div class="llm-local-metrics">
|
||||||
<span class="llm-metric-chip">⚡ ~70× RT GPU</span>
|
<span class="llm-metric-chip"><span class="mdi mdi-lightning-bolt"></span> ~70× RT GPU</span>
|
||||||
<span class="llm-metric-chip">⏰ 0.5–2 s</span>
|
<span class="llm-metric-chip"><span class="mdi mdi-clock-outline"></span> 0.5–2 s</span>
|
||||||
<span class="llm-metric-chip">⭐ large-v3</span>
|
<span class="llm-metric-chip"><span class="mdi mdi-star-circle-outline"></span> large-v3</span>
|
||||||
<span class="llm-metric-chip">💾 1.5 GB VRAM</span>
|
<span class="llm-metric-chip"><span class="mdi mdi-memory"></span> 1.5 GB VRAM</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="llm-local-desc">Drop-in local replacement for the Whisper API. GPU-accelerated via CTranslate2. OpenAI-compatible endpoint.</p>
|
<p class="llm-local-desc">Drop-in local replacement for the Whisper API. GPU-accelerated via CTranslate2. OpenAI-compatible endpoint.</p>
|
||||||
<div class="llm-local-url">
|
<div class="llm-local-url">
|
||||||
@ -153,7 +153,7 @@ Check "Enable CORS" for browser access</pre>
|
|||||||
<button class="llm-local-ping" data-ping-key="faster-whisper" title="Test connection">Connect</button>
|
<button class="llm-local-ping" data-ping-key="faster-whisper" title="Test connection">Connect</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="llm-local-actions">
|
<div class="llm-local-actions">
|
||||||
<button class="llm-use-btn" id="llm-use-faster-whisper-stt" type="button" title="Copy URL to Settings → faster-whisper-server URL and set as active STT">📋 Use as STT</button>
|
<button class="llm-use-btn" id="llm-use-faster-whisper-stt" type="button" title="Copy URL to Settings → faster-whisper-server URL and set as active STT"><span class="mdi mdi-content-copy"></span> Use as STT</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="llm-local-snippet">
|
<div class="llm-local-snippet">
|
||||||
<div class="llm-snippet-bar">
|
<div class="llm-snippet-bar">
|
||||||
@ -171,7 +171,7 @@ Check "Enable CORS" for browser access</pre>
|
|||||||
reservations:
|
reservations:
|
||||||
devices: [{driver: nvidia, count: 1, capabilities: [gpu]}]</pre>
|
devices: [{driver: nvidia, count: 1, capabilities: [gpu]}]</pre>
|
||||||
</div>
|
</div>
|
||||||
<a class="llm-local-link" href="https://github.com/fedirz/faster-whisper-server" target="_blank" rel="noopener">github.com/fedirz/faster-whisper-server ↗</a>
|
<a class="llm-local-link" href="https://github.com/fedirz/faster-whisper-server" target="_blank" rel="noopener">github.com/fedirz/faster-whisper-server <span class="mdi mdi-open-in-new link-icon"></span></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="llm-local-card">
|
<div class="llm-local-card">
|
||||||
@ -181,10 +181,10 @@ Check "Enable CORS" for browser access</pre>
|
|||||||
<span class="llm-local-compat">HTTP server</span>
|
<span class="llm-local-compat">HTTP server</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="llm-local-metrics">
|
<div class="llm-local-metrics">
|
||||||
<span class="llm-metric-chip">⚡ ~8–15× RT CPU</span>
|
<span class="llm-metric-chip"><span class="mdi mdi-lightning-bolt"></span> ~8–15× RT CPU</span>
|
||||||
<span class="llm-metric-chip">⏰ 1–5 s</span>
|
<span class="llm-metric-chip"><span class="mdi mdi-clock-outline"></span> 1–5 s</span>
|
||||||
<span class="llm-metric-chip">⭐ large-v3 Q5</span>
|
<span class="llm-metric-chip"><span class="mdi mdi-star-circle-outline"></span> large-v3 Q5</span>
|
||||||
<span class="llm-metric-chip">💾 ~1 GB RAM</span>
|
<span class="llm-metric-chip"><span class="mdi mdi-memory"></span> ~1 GB RAM</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="llm-local-desc">Minimal C++ Whisper with a built-in HTTP server. CPU or Metal/CUDA. Low memory, fast on consumer hardware.</p>
|
<p class="llm-local-desc">Minimal C++ Whisper with a built-in HTTP server. CPU or Metal/CUDA. Low memory, fast on consumer hardware.</p>
|
||||||
<div class="llm-local-url">
|
<div class="llm-local-url">
|
||||||
@ -193,7 +193,7 @@ Check "Enable CORS" for browser access</pre>
|
|||||||
<button class="llm-local-ping" data-ping-key="whisper-cpp" title="Test connection">Connect</button>
|
<button class="llm-local-ping" data-ping-key="whisper-cpp" title="Test connection">Connect</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="llm-local-actions">
|
<div class="llm-local-actions">
|
||||||
<button class="llm-use-btn" id="llm-use-whisper-cpp-stt" type="button" title="Copy URL to Settings → whisper.cpp URL and set as active STT">📋 Use as STT</button>
|
<button class="llm-use-btn" id="llm-use-whisper-cpp-stt" type="button" title="Copy URL to Settings → whisper.cpp URL and set as active STT"><span class="mdi mdi-content-copy"></span> Use as STT</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="llm-local-snippet">
|
<div class="llm-local-snippet">
|
||||||
<div class="llm-snippet-bar">
|
<div class="llm-snippet-bar">
|
||||||
@ -205,7 +205,7 @@ cd whisper.cpp && cmake -B build && cmake --build build -j
|
|||||||
./build/bin/whisper-server \
|
./build/bin/whisper-server \
|
||||||
-m models/ggml-large-v3.bin --port 8080</pre>
|
-m models/ggml-large-v3.bin --port 8080</pre>
|
||||||
</div>
|
</div>
|
||||||
<a class="llm-local-link" href="https://github.com/ggml-org/whisper.cpp" target="_blank" rel="noopener">github.com/ggml-org/whisper.cpp ↗</a>
|
<a class="llm-local-link" href="https://github.com/ggml-org/whisper.cpp" target="_blank" rel="noopener">github.com/ggml-org/whisper.cpp <span class="mdi mdi-open-in-new link-icon"></span></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@ -216,15 +216,15 @@ cd whisper.cpp && cmake -B build && cmake --build build -j
|
|||||||
|
|
||||||
<div class="llm-local-card">
|
<div class="llm-local-card">
|
||||||
<div class="llm-local-head">
|
<div class="llm-local-head">
|
||||||
<span class="llm-local-icon">🔊</span>
|
<span class="llm-local-icon"><span class="mdi mdi-speaker-outline"></span></span>
|
||||||
<span class="llm-local-name">Piper TTS</span>
|
<span class="llm-local-name">Piper TTS</span>
|
||||||
<span class="llm-local-compat">Fast · offline</span>
|
<span class="llm-local-compat">Fast · offline</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="llm-local-metrics">
|
<div class="llm-local-metrics">
|
||||||
<span class="llm-metric-chip">⚡ ~1× CPU realtime</span>
|
<span class="llm-metric-chip"><span class="mdi mdi-lightning-bolt"></span> ~1× CPU realtime</span>
|
||||||
<span class="llm-metric-chip">⏰ <50 ms</span>
|
<span class="llm-metric-chip"><span class="mdi mdi-clock-outline"></span> <50 ms</span>
|
||||||
<span class="llm-metric-chip">⭐ Good (VITS)</span>
|
<span class="llm-metric-chip"><span class="mdi mdi-star-circle-outline"></span> Good (VITS)</span>
|
||||||
<span class="llm-metric-chip">💾 ~50 MB RAM</span>
|
<span class="llm-metric-chip"><span class="mdi mdi-memory"></span> ~50 MB RAM</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="llm-local-desc">Lightning-fast offline TTS. Runs on CPU in real time. 50+ language voices available. Uses Wyoming protocol (port 10200) — not directly OpenAI-compatible.</p>
|
<p class="llm-local-desc">Lightning-fast offline TTS. Runs on CPU in real time. 50+ language voices available. Uses Wyoming protocol (port 10200) — not directly OpenAI-compatible.</p>
|
||||||
<div class="llm-local-url">
|
<div class="llm-local-url">
|
||||||
@ -241,7 +241,7 @@ cd whisper.cpp && cmake -B build && cmake --build build -j
|
|||||||
rhasspy/wyoming-piper \
|
rhasspy/wyoming-piper \
|
||||||
--voice en_US-lessac-medium</pre>
|
--voice en_US-lessac-medium</pre>
|
||||||
</div>
|
</div>
|
||||||
<a class="llm-local-link" href="https://github.com/rhasspy/piper" target="_blank" rel="noopener">github.com/rhasspy/piper ↗</a>
|
<a class="llm-local-link" href="https://github.com/rhasspy/piper" target="_blank" rel="noopener">github.com/rhasspy/piper <span class="mdi mdi-open-in-new link-icon"></span></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="llm-local-card">
|
<div class="llm-local-card">
|
||||||
@ -251,10 +251,10 @@ cd whisper.cpp && cmake -B build && cmake --build build -j
|
|||||||
<span class="llm-local-compat">OpenAI-compat TTS</span>
|
<span class="llm-local-compat">OpenAI-compat TTS</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="llm-local-metrics">
|
<div class="llm-local-metrics">
|
||||||
<span class="llm-metric-chip">⚡ ~0.1× CPU RTF</span>
|
<span class="llm-metric-chip"><span class="mdi mdi-lightning-bolt"></span> ~0.1× CPU RTF</span>
|
||||||
<span class="llm-metric-chip">⏰ ~200 ms</span>
|
<span class="llm-metric-chip"><span class="mdi mdi-clock-outline"></span> ~200 ms</span>
|
||||||
<span class="llm-metric-chip">⭐ High (82M)</span>
|
<span class="llm-metric-chip"><span class="mdi mdi-star-circle-outline"></span> High (82M)</span>
|
||||||
<span class="llm-metric-chip">💾 300 MB CPU</span>
|
<span class="llm-metric-chip"><span class="mdi mdi-memory"></span> 300 MB CPU</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="llm-local-desc">Kokoro-82M running behind an OpenAI-compatible TTS endpoint. 11 built-in voices (af_bella, bf_emma, am_adam…). Drop-in replacement for OpenAI’s TTS API.</p>
|
<p class="llm-local-desc">Kokoro-82M running behind an OpenAI-compatible TTS endpoint. 11 built-in voices (af_bella, bf_emma, am_adam…). Drop-in replacement for OpenAI’s TTS API.</p>
|
||||||
<div class="llm-local-url">
|
<div class="llm-local-url">
|
||||||
@ -263,7 +263,7 @@ cd whisper.cpp && cmake -B build && cmake --build build -j
|
|||||||
<button class="llm-local-ping" data-ping-key="kokoro" title="Test connection">Connect</button>
|
<button class="llm-local-ping" data-ping-key="kokoro" title="Test connection">Connect</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="llm-local-actions">
|
<div class="llm-local-actions">
|
||||||
<button class="llm-use-btn" id="llm-use-kokoro-tts" type="button" title="Copy URL to Settings → Kokoro FastAPI URL and enable Kokoro in Try It Out">📋 Use as TTS</button>
|
<button class="llm-use-btn" id="llm-use-kokoro-tts" type="button" title="Copy URL to Settings → Kokoro FastAPI URL and enable Kokoro in Try It Out"><span class="mdi mdi-content-copy"></span> Use as TTS</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="llm-local-snippet">
|
<div class="llm-local-snippet">
|
||||||
<div class="llm-snippet-bar">
|
<div class="llm-snippet-bar">
|
||||||
@ -276,7 +276,7 @@ cd whisper.cpp && cmake -B build && cmake --build build -j
|
|||||||
docker run -p 8880:8880 --gpus all \
|
docker run -p 8880:8880 --gpus all \
|
||||||
ghcr.io/remsky/kokoro-fastapi-gpu:v0.2.2</pre>
|
ghcr.io/remsky/kokoro-fastapi-gpu:v0.2.2</pre>
|
||||||
</div>
|
</div>
|
||||||
<a class="llm-local-link" href="https://github.com/remsky/Kokoro-FastAPI" target="_blank" rel="noopener">github.com/remsky/Kokoro-FastAPI ↗</a>
|
<a class="llm-local-link" href="https://github.com/remsky/Kokoro-FastAPI" target="_blank" rel="noopener">github.com/remsky/Kokoro-FastAPI <span class="mdi mdi-open-in-new link-icon"></span></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="llm-local-card">
|
<div class="llm-local-card">
|
||||||
@ -286,10 +286,10 @@ docker run -p 8880:8880 --gpus all \
|
|||||||
<span class="llm-local-compat">Voice cloning</span>
|
<span class="llm-local-compat">Voice cloning</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="llm-local-metrics">
|
<div class="llm-local-metrics">
|
||||||
<span class="llm-metric-chip">⚡ ~0.5× GPU RTF</span>
|
<span class="llm-metric-chip"><span class="mdi mdi-lightning-bolt"></span> ~0.5× GPU RTF</span>
|
||||||
<span class="llm-metric-chip">⏰ 1–3 s</span>
|
<span class="llm-metric-chip"><span class="mdi mdi-clock-outline"></span> 1–3 s</span>
|
||||||
<span class="llm-metric-chip">⭐ High (WAV clone)</span>
|
<span class="llm-metric-chip"><span class="mdi mdi-star-circle-outline"></span> High (WAV clone)</span>
|
||||||
<span class="llm-metric-chip">💾 3–4 GB VRAM</span>
|
<span class="llm-metric-chip"><span class="mdi mdi-memory"></span> 3–4 GB VRAM</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="llm-local-desc">Coqui XTTS — multilingual voice cloning from a 6-second sample. 17 languages. Compatible with this app’s voice library.</p>
|
<p class="llm-local-desc">Coqui XTTS — multilingual voice cloning from a 6-second sample. 17 languages. Compatible with this app’s voice library.</p>
|
||||||
<div class="llm-local-url">
|
<div class="llm-local-url">
|
||||||
@ -306,13 +306,13 @@ docker run -p 8880:8880 --gpus all \
|
|||||||
-v /voices:/voices \
|
-v /voices:/voices \
|
||||||
daswer123/xtts-api-server:latest</pre>
|
daswer123/xtts-api-server:latest</pre>
|
||||||
</div>
|
</div>
|
||||||
<a class="llm-local-link" href="https://github.com/daswer123/xtts-api-server" target="_blank" rel="noopener">xtts-api-server ↗</a>
|
<a class="llm-local-link" href="https://github.com/daswer123/xtts-api-server" target="_blank" rel="noopener">xtts-api-server <span class="mdi mdi-open-in-new link-icon"></span></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="llm-local-howto">
|
<div class="llm-local-howto">
|
||||||
<div class="llm-howto-icon">💡</div>
|
<div class="llm-howto-icon"><span class="mdi mdi-lightbulb-outline"></span></div>
|
||||||
<div class="llm-howto-body">
|
<div class="llm-howto-body">
|
||||||
<strong>Adding a local service to this app</strong>
|
<strong>Adding a local service to this app</strong>
|
||||||
<p>All OpenAI-compatible services work the same way: open <em>App Routing</em> in the sidebar, add a new backend entry with the local URL, leave the API key field empty, and set it as the default backend for the voice type you want.</p>
|
<p>All OpenAI-compatible services work the same way: open <em>App Routing</em> in the sidebar, add a new backend entry with the local URL, leave the API key field empty, and set it as the default backend for the voice type you want.</p>
|
||||||
@ -332,12 +332,12 @@ docker run -p 8880:8880 --gpus all \
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ── ASR / STT ─────────────────────────────────────────── -->
|
<!-- ── ASR / STT ─────────────────────────────────────────── -->
|
||||||
<h4 class="llm-local-cat" style="margin-bottom:12px">🎤 ASR · Speech-to-Text</h4>
|
<h4 class="llm-local-cat" style="margin-bottom:12px"><span class="mdi mdi-microphone-variant"></span> ASR · Speech-to-Text</h4>
|
||||||
<div class="llm-service-grid">
|
<div class="llm-service-grid">
|
||||||
|
|
||||||
<div class="llm-card llm-card-highlight">
|
<div class="llm-card llm-card-highlight">
|
||||||
<div class="llm-card-head">
|
<div class="llm-card-head">
|
||||||
<span class="llm-card-icon">⚡</span>
|
<span class="llm-card-icon"><span class="mdi mdi-lightning-bolt"></span></span>
|
||||||
<div>
|
<div>
|
||||||
<div class="llm-card-name">Groq Whisper</div>
|
<div class="llm-card-name">Groq Whisper</div>
|
||||||
<div class="llm-card-sub">whisper-large-v3-turbo · OpenAI-compatible</div>
|
<div class="llm-card-sub">whisper-large-v3-turbo · OpenAI-compatible</div>
|
||||||
@ -346,13 +346,13 @@ docker run -p 8880:8880 --gpus all \
|
|||||||
</div>
|
</div>
|
||||||
<div class="llm-card-stats">
|
<div class="llm-card-stats">
|
||||||
<span>2 000 req / day</span>
|
<span>2 000 req / day</span>
|
||||||
<span>⚡ Fastest cloud STT</span>
|
<span><span class="mdi mdi-lightning-bolt"></span> Fastest cloud STT</span>
|
||||||
<span>💾 Cloud · 0 VRAM</span>
|
<span><span class="mdi mdi-memory"></span> Cloud · 0 VRAM</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="llm-field-row">
|
<div class="llm-field-row">
|
||||||
<label class="llm-label">API key</label>
|
<label class="llm-label">API key</label>
|
||||||
<input type="password" class="llm-input" placeholder="gsk_…" data-llm-key="groq_stt">
|
<input type="password" class="llm-input" placeholder="gsk_…" data-llm-key="groq_stt">
|
||||||
<a class="llm-link" href="https://console.groq.com" target="_blank" rel="noopener">Get key ↗</a>
|
<a class="llm-link" href="https://console.groq.com" target="_blank" rel="noopener">Get key <span class="mdi mdi-open-in-new link-icon"></span></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="llm-endpoint">
|
<div class="llm-endpoint">
|
||||||
<span class="llm-endpoint-label">Endpoint</span>
|
<span class="llm-endpoint-label">Endpoint</span>
|
||||||
@ -368,7 +368,7 @@ docker run -p 8880:8880 --gpus all \
|
|||||||
|
|
||||||
<div class="llm-card">
|
<div class="llm-card">
|
||||||
<div class="llm-card-head">
|
<div class="llm-card-head">
|
||||||
<span class="llm-card-icon">🤗</span>
|
<span class="llm-card-icon"><span class="mdi mdi-emoticon-happy-outline"></span></span>
|
||||||
<div>
|
<div>
|
||||||
<div class="llm-card-name">HuggingFace Inference</div>
|
<div class="llm-card-name">HuggingFace Inference</div>
|
||||||
<div class="llm-card-sub">Serverless Whisper models</div>
|
<div class="llm-card-sub">Serverless Whisper models</div>
|
||||||
@ -383,7 +383,7 @@ docker run -p 8880:8880 --gpus all \
|
|||||||
<div class="llm-field-row">
|
<div class="llm-field-row">
|
||||||
<label class="llm-label">API key</label>
|
<label class="llm-label">API key</label>
|
||||||
<input type="password" class="llm-input" placeholder="hf_…" data-llm-key="hf_stt">
|
<input type="password" class="llm-input" placeholder="hf_…" data-llm-key="hf_stt">
|
||||||
<a class="llm-link" href="https://huggingface.co/settings/tokens" target="_blank" rel="noopener">Get key ↗</a>
|
<a class="llm-link" href="https://huggingface.co/settings/tokens" target="_blank" rel="noopener">Get key <span class="mdi mdi-open-in-new link-icon"></span></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="llm-endpoint">
|
<div class="llm-endpoint">
|
||||||
<span class="llm-endpoint-label">Endpoint</span>
|
<span class="llm-endpoint-label">Endpoint</span>
|
||||||
@ -393,7 +393,7 @@ docker run -p 8880:8880 --gpus all \
|
|||||||
|
|
||||||
<div class="llm-card">
|
<div class="llm-card">
|
||||||
<div class="llm-card-head">
|
<div class="llm-card-head">
|
||||||
<span class="llm-card-icon">📋</span>
|
<span class="llm-card-icon"><span class="mdi mdi-content-copy"></span></span>
|
||||||
<div>
|
<div>
|
||||||
<div class="llm-card-name">AssemblyAI</div>
|
<div class="llm-card-name">AssemblyAI</div>
|
||||||
<div class="llm-card-sub">High-accuracy transcription + speaker diarization</div>
|
<div class="llm-card-sub">High-accuracy transcription + speaker diarization</div>
|
||||||
@ -408,7 +408,7 @@ docker run -p 8880:8880 --gpus all \
|
|||||||
<div class="llm-field-row">
|
<div class="llm-field-row">
|
||||||
<label class="llm-label">API key</label>
|
<label class="llm-label">API key</label>
|
||||||
<input type="password" class="llm-input" placeholder="AssemblyAI key…" data-llm-key="assemblyai">
|
<input type="password" class="llm-input" placeholder="AssemblyAI key…" data-llm-key="assemblyai">
|
||||||
<a class="llm-link" href="https://www.assemblyai.com" target="_blank" rel="noopener">Get key ↗</a>
|
<a class="llm-link" href="https://www.assemblyai.com" target="_blank" rel="noopener">Get key <span class="mdi mdi-open-in-new link-icon"></span></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="llm-endpoint">
|
<div class="llm-endpoint">
|
||||||
<span class="llm-endpoint-label">Endpoint</span>
|
<span class="llm-endpoint-label">Endpoint</span>
|
||||||
@ -419,12 +419,12 @@ docker run -p 8880:8880 --gpus all \
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ── TTS ──────────────────────────────────────────────── -->
|
<!-- ── TTS ──────────────────────────────────────────────── -->
|
||||||
<h4 class="llm-local-cat" style="margin-top:28px;margin-bottom:12px">🔊 Text-to-Speech</h4>
|
<h4 class="llm-local-cat" style="margin-top:28px;margin-bottom:12px"><span class="mdi mdi-account-voice"></span> Text-to-Speech</h4>
|
||||||
<div class="llm-service-grid">
|
<div class="llm-service-grid">
|
||||||
|
|
||||||
<div class="llm-card llm-card-highlight">
|
<div class="llm-card llm-card-highlight">
|
||||||
<div class="llm-card-head">
|
<div class="llm-card-head">
|
||||||
<span class="llm-card-icon">💉</span>
|
<span class="llm-card-icon"><span class="mdi mdi-waveform"></span></span>
|
||||||
<div>
|
<div>
|
||||||
<div class="llm-card-name">ElevenLabs</div>
|
<div class="llm-card-name">ElevenLabs</div>
|
||||||
<div class="llm-card-sub">High-quality voice cloning & synthesis</div>
|
<div class="llm-card-sub">High-quality voice cloning & synthesis</div>
|
||||||
@ -439,7 +439,7 @@ docker run -p 8880:8880 --gpus all \
|
|||||||
<div class="llm-field-row">
|
<div class="llm-field-row">
|
||||||
<label class="llm-label">API key</label>
|
<label class="llm-label">API key</label>
|
||||||
<input type="password" class="llm-input" placeholder="xi-api-key…" data-llm-key="elevenlabs">
|
<input type="password" class="llm-input" placeholder="xi-api-key…" data-llm-key="elevenlabs">
|
||||||
<a class="llm-link" href="https://elevenlabs.io" target="_blank" rel="noopener">Get key ↗</a>
|
<a class="llm-link" href="https://elevenlabs.io" target="_blank" rel="noopener">Get key <span class="mdi mdi-open-in-new link-icon"></span></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="llm-endpoint">
|
<div class="llm-endpoint">
|
||||||
<span class="llm-endpoint-label">Endpoint</span>
|
<span class="llm-endpoint-label">Endpoint</span>
|
||||||
@ -449,7 +449,7 @@ docker run -p 8880:8880 --gpus all \
|
|||||||
|
|
||||||
<div class="llm-card">
|
<div class="llm-card">
|
||||||
<div class="llm-card-head">
|
<div class="llm-card-head">
|
||||||
<span class="llm-card-icon">🏭</span>
|
<span class="llm-card-icon"><span class="mdi mdi-fish"></span></span>
|
||||||
<div>
|
<div>
|
||||||
<div class="llm-card-name">Fish Audio</div>
|
<div class="llm-card-name">Fish Audio</div>
|
||||||
<div class="llm-card-sub">Voice cloning & multilingual TTS</div>
|
<div class="llm-card-sub">Voice cloning & multilingual TTS</div>
|
||||||
@ -464,7 +464,7 @@ docker run -p 8880:8880 --gpus all \
|
|||||||
<div class="llm-field-row">
|
<div class="llm-field-row">
|
||||||
<label class="llm-label">API key</label>
|
<label class="llm-label">API key</label>
|
||||||
<input type="password" class="llm-input" placeholder="Fish Audio key…" data-llm-key="fish_audio">
|
<input type="password" class="llm-input" placeholder="Fish Audio key…" data-llm-key="fish_audio">
|
||||||
<a class="llm-link" href="https://fish.audio" target="_blank" rel="noopener">Get key ↗</a>
|
<a class="llm-link" href="https://fish.audio" target="_blank" rel="noopener">Get key <span class="mdi mdi-open-in-new link-icon"></span></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="llm-endpoint">
|
<div class="llm-endpoint">
|
||||||
<span class="llm-endpoint-label">Endpoint</span>
|
<span class="llm-endpoint-label">Endpoint</span>
|
||||||
@ -474,7 +474,7 @@ docker run -p 8880:8880 --gpus all \
|
|||||||
|
|
||||||
<div class="llm-card">
|
<div class="llm-card">
|
||||||
<div class="llm-card-head">
|
<div class="llm-card-head">
|
||||||
<span class="llm-card-icon">🎵</span>
|
<span class="llm-card-icon"><span class="mdi mdi-music-note"></span></span>
|
||||||
<div>
|
<div>
|
||||||
<div class="llm-card-name">Kokoro TTS</div>
|
<div class="llm-card-name">Kokoro TTS</div>
|
||||||
<div class="llm-card-sub">82M model · HuggingFace Spaces demo</div>
|
<div class="llm-card-sub">82M model · HuggingFace Spaces demo</div>
|
||||||
@ -489,19 +489,19 @@ docker run -p 8880:8880 --gpus all \
|
|||||||
<div class="llm-info-box">
|
<div class="llm-info-box">
|
||||||
Use the HF Spaces web demo for quick tests, or run Kokoro locally via Docker for production use.
|
Use the HF Spaces web demo for quick tests, or run Kokoro locally via Docker for production use.
|
||||||
<br>
|
<br>
|
||||||
<a class="llm-link-inline" href="https://huggingface.co/spaces/hexgrad/Kokoro-TTS" target="_blank" rel="noopener">→ Open Kokoro HF Space</a>
|
<a class="llm-link-inline" href="https://huggingface.co/spaces/hexgrad/Kokoro-TTS" target="_blank" rel="noopener"><span class="mdi mdi-arrow-right"></span> Open Kokoro HF Space</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ── LLM ──────────────────────────────────────────────── -->
|
<!-- ── LLM ──────────────────────────────────────────────── -->
|
||||||
<h4 class="llm-local-cat" style="margin-top:28px;margin-bottom:12px">🤖 Large Language Models</h4>
|
<h4 class="llm-local-cat" style="margin-top:28px;margin-bottom:12px"><span class="mdi mdi-robot-outline"></span> Large Language Models</h4>
|
||||||
<div class="llm-service-grid">
|
<div class="llm-service-grid">
|
||||||
|
|
||||||
<div class="llm-card llm-card-highlight">
|
<div class="llm-card llm-card-highlight">
|
||||||
<div class="llm-card-head">
|
<div class="llm-card-head">
|
||||||
<span class="llm-card-icon">⚡</span>
|
<span class="llm-card-icon"><span class="mdi mdi-lightning-bolt"></span></span>
|
||||||
<div>
|
<div>
|
||||||
<div class="llm-card-name">Groq</div>
|
<div class="llm-card-name">Groq</div>
|
||||||
<div class="llm-card-sub">Ultra-fast inference · OpenAI-compatible</div>
|
<div class="llm-card-sub">Ultra-fast inference · OpenAI-compatible</div>
|
||||||
@ -516,7 +516,7 @@ docker run -p 8880:8880 --gpus all \
|
|||||||
<div class="llm-field-row">
|
<div class="llm-field-row">
|
||||||
<label class="llm-label">API key</label>
|
<label class="llm-label">API key</label>
|
||||||
<input type="password" class="llm-input" placeholder="gsk_…" data-llm-key="groq_llm">
|
<input type="password" class="llm-input" placeholder="gsk_…" data-llm-key="groq_llm">
|
||||||
<a class="llm-link" href="https://console.groq.com" target="_blank" rel="noopener">Get key ↗</a>
|
<a class="llm-link" href="https://console.groq.com" target="_blank" rel="noopener">Get key <span class="mdi mdi-open-in-new link-icon"></span></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="llm-endpoint">
|
<div class="llm-endpoint">
|
||||||
<span class="llm-endpoint-label">Endpoint</span>
|
<span class="llm-endpoint-label">Endpoint</span>
|
||||||
@ -531,7 +531,7 @@ docker run -p 8880:8880 --gpus all \
|
|||||||
|
|
||||||
<div class="llm-card">
|
<div class="llm-card">
|
||||||
<div class="llm-card-head">
|
<div class="llm-card-head">
|
||||||
<span class="llm-card-icon">🌐</span>
|
<span class="llm-card-icon"><span class="mdi mdi-earth"></span></span>
|
||||||
<div>
|
<div>
|
||||||
<div class="llm-card-name">OpenRouter</div>
|
<div class="llm-card-name">OpenRouter</div>
|
||||||
<div class="llm-card-sub">50+ free models · OpenAI-compatible</div>
|
<div class="llm-card-sub">50+ free models · OpenAI-compatible</div>
|
||||||
@ -546,7 +546,7 @@ docker run -p 8880:8880 --gpus all \
|
|||||||
<div class="llm-field-row">
|
<div class="llm-field-row">
|
||||||
<label class="llm-label">API key</label>
|
<label class="llm-label">API key</label>
|
||||||
<input type="password" class="llm-input" placeholder="sk-or-…" data-llm-key="openrouter">
|
<input type="password" class="llm-input" placeholder="sk-or-…" data-llm-key="openrouter">
|
||||||
<a class="llm-link" href="https://openrouter.ai" target="_blank" rel="noopener">Get key ↗</a>
|
<a class="llm-link" href="https://openrouter.ai" target="_blank" rel="noopener">Get key <span class="mdi mdi-open-in-new link-icon"></span></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="llm-endpoint">
|
<div class="llm-endpoint">
|
||||||
<span class="llm-endpoint-label">Endpoint</span>
|
<span class="llm-endpoint-label">Endpoint</span>
|
||||||
@ -561,7 +561,7 @@ docker run -p 8880:8880 --gpus all \
|
|||||||
|
|
||||||
<div class="llm-card">
|
<div class="llm-card">
|
||||||
<div class="llm-card-head">
|
<div class="llm-card-head">
|
||||||
<span class="llm-card-icon">💎</span>
|
<span class="llm-card-icon"><span class="mdi mdi-diamond-outline"></span></span>
|
||||||
<div>
|
<div>
|
||||||
<div class="llm-card-name">Google Gemini</div>
|
<div class="llm-card-name">Google Gemini</div>
|
||||||
<div class="llm-card-sub">Gemini 2.5 Flash · Generous free tier</div>
|
<div class="llm-card-sub">Gemini 2.5 Flash · Generous free tier</div>
|
||||||
@ -576,7 +576,7 @@ docker run -p 8880:8880 --gpus all \
|
|||||||
<div class="llm-field-row">
|
<div class="llm-field-row">
|
||||||
<label class="llm-label">API key</label>
|
<label class="llm-label">API key</label>
|
||||||
<input type="password" class="llm-input" placeholder="AIza…" data-llm-key="gemini">
|
<input type="password" class="llm-input" placeholder="AIza…" data-llm-key="gemini">
|
||||||
<a class="llm-link" href="https://ai.google.dev" target="_blank" rel="noopener">Get key ↗</a>
|
<a class="llm-link" href="https://ai.google.dev" target="_blank" rel="noopener">Get key <span class="mdi mdi-open-in-new link-icon"></span></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="llm-endpoint">
|
<div class="llm-endpoint">
|
||||||
<span class="llm-endpoint-label">Endpoint</span>
|
<span class="llm-endpoint-label">Endpoint</span>
|
||||||
@ -587,7 +587,7 @@ docker run -p 8880:8880 --gpus all \
|
|||||||
|
|
||||||
<div class="llm-card">
|
<div class="llm-card">
|
||||||
<div class="llm-card-head">
|
<div class="llm-card-head">
|
||||||
<span class="llm-card-icon">🅓</span>
|
<span class="llm-card-icon"><span class="mdi mdi-alpha-m-circle-outline"></span></span>
|
||||||
<div>
|
<div>
|
||||||
<div class="llm-card-name">Mistral AI</div>
|
<div class="llm-card-name">Mistral AI</div>
|
||||||
<div class="llm-card-sub">OpenAI-compatible · EU-based</div>
|
<div class="llm-card-sub">OpenAI-compatible · EU-based</div>
|
||||||
@ -602,7 +602,7 @@ docker run -p 8880:8880 --gpus all \
|
|||||||
<div class="llm-field-row">
|
<div class="llm-field-row">
|
||||||
<label class="llm-label">API key</label>
|
<label class="llm-label">API key</label>
|
||||||
<input type="password" class="llm-input" placeholder="Mistral key…" data-llm-key="mistral">
|
<input type="password" class="llm-input" placeholder="Mistral key…" data-llm-key="mistral">
|
||||||
<a class="llm-link" href="https://mistral.ai" target="_blank" rel="noopener">Get key ↗</a>
|
<a class="llm-link" href="https://mistral.ai" target="_blank" rel="noopener">Get key <span class="mdi mdi-open-in-new link-icon"></span></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="llm-endpoint">
|
<div class="llm-endpoint">
|
||||||
<span class="llm-endpoint-label">Endpoint</span>
|
<span class="llm-endpoint-label">Endpoint</span>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<div class="section-head">
|
<div class="section-head">
|
||||||
<span class="section-icon">⇌</span>
|
<span class="section-icon"><span class="mdi mdi-swap-horizontal"></span></span>
|
||||||
<div class="section-title">
|
<div class="section-title">
|
||||||
<h2>App Routing</h2>
|
<h2>App Routing</h2>
|
||||||
<p>Map incoming requests from apps like Open WebUI or SillyTavern to specific voices and backends.</p>
|
<p>Map incoming requests from apps like Open WebUI or SillyTavern to specific voices and backends.</p>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<div class="section-head">
|
<div class="section-head">
|
||||||
<span class="section-icon">⚙</span>
|
<span class="section-icon"><span class="mdi mdi-cog-outline"></span></span>
|
||||||
<div class="section-title">
|
<div class="section-title">
|
||||||
<h2>Settings</h2>
|
<h2>Settings</h2>
|
||||||
<p>Configure backend URLs, API keys, voice folders, and playback preferences.</p>
|
<p>Configure backend URLs, API keys, voice folders, and playback preferences.</p>
|
||||||
@ -130,7 +130,7 @@
|
|||||||
<label>Groq API key <span style="font-weight:400">(Groq Whisper & LLM)</span></label>
|
<label>Groq API key <span style="font-weight:400">(Groq Whisper & LLM)</span></label>
|
||||||
<div class="s-key-row">
|
<div class="s-key-row">
|
||||||
<input type="password" id="s-groq-api-key" placeholder="gsk_…" autocomplete="off">
|
<input type="password" id="s-groq-api-key" placeholder="gsk_…" autocomplete="off">
|
||||||
<button type="button" class="s-eye-btn" data-target="s-groq-api-key">👁</button>
|
<button type="button" class="s-eye-btn" data-target="s-groq-api-key"><span class="mdi mdi-eye-outline"></span></button>
|
||||||
</div>
|
</div>
|
||||||
<span class="s-hint">Used for Groq Whisper STT (whisper-large-v3-turbo) and Groq LLM. Free: 2 000 req/day. Fastest cloud transcription.</span>
|
<span class="s-hint">Used for Groq Whisper STT (whisper-large-v3-turbo) and Groq LLM. Free: 2 000 req/day. Fastest cloud transcription.</span>
|
||||||
</div>
|
</div>
|
||||||
@ -247,21 +247,21 @@
|
|||||||
<label>TTS API key <span style="font-weight:400">(optional)</span></label>
|
<label>TTS API key <span style="font-weight:400">(optional)</span></label>
|
||||||
<div class="s-key-row">
|
<div class="s-key-row">
|
||||||
<input type="password" id="s-tts-key" placeholder="dummy, sk-local, or leave empty" autocomplete="off">
|
<input type="password" id="s-tts-key" placeholder="dummy, sk-local, or leave empty" autocomplete="off">
|
||||||
<button type="button" class="s-eye-btn" data-target="s-tts-key">👁</button>
|
<button type="button" class="s-eye-btn" data-target="s-tts-key"><span class="mdi mdi-eye-outline"></span></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="s-field">
|
<div class="s-field">
|
||||||
<label>Voice Design API key <span style="font-weight:400">(optional)</span></label>
|
<label>Voice Design API key <span style="font-weight:400">(optional)</span></label>
|
||||||
<div class="s-key-row">
|
<div class="s-key-row">
|
||||||
<input type="password" id="s-vd-key" placeholder="dummy, sk-local, or leave empty" autocomplete="off">
|
<input type="password" id="s-vd-key" placeholder="dummy, sk-local, or leave empty" autocomplete="off">
|
||||||
<button type="button" class="s-eye-btn" data-target="s-vd-key">👁</button>
|
<button type="button" class="s-eye-btn" data-target="s-vd-key"><span class="mdi mdi-eye-outline"></span></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="s-field">
|
<div class="s-field">
|
||||||
<label>Whisper API key <span style="font-weight:400">(optional)</span></label>
|
<label>Whisper API key <span style="font-weight:400">(optional)</span></label>
|
||||||
<div class="s-key-row">
|
<div class="s-key-row">
|
||||||
<input type="password" id="s-whisper-key" placeholder="dummy, sk-local, or leave empty" autocomplete="off">
|
<input type="password" id="s-whisper-key" placeholder="dummy, sk-local, or leave empty" autocomplete="off">
|
||||||
<button type="button" class="s-eye-btn" data-target="s-whisper-key">👁</button>
|
<button type="button" class="s-eye-btn" data-target="s-whisper-key"><span class="mdi mdi-eye-outline"></span></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -277,9 +277,9 @@
|
|||||||
<p class="s-hint" style="margin-top:4px">Export all voices and settings as a ZIP for backup or migration. Import to restore. API keys are excluded from exports.</p>
|
<p class="s-hint" style="margin-top:4px">Export all voices and settings as a ZIP for backup or migration. Import to restore. API keys are excluded from exports.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="btn-row" style="margin-top:12px;gap:10px;flex-wrap:wrap">
|
<div class="btn-row" style="margin-top:12px;gap:10px;flex-wrap:wrap">
|
||||||
<a class="btn-secondary" id="s-export-voices-btn" href="/api/voices/export" download>↓ Export voices</a>
|
<a class="btn-secondary" id="s-export-voices-btn" href="/api/voices/export" download><span class="mdi mdi-download"></span> Export voices</a>
|
||||||
<label class="btn-secondary" style="cursor:pointer" title="Import a voices ZIP">
|
<label class="btn-secondary" style="cursor:pointer" title="Import a voices ZIP">
|
||||||
↑ Import voices
|
<span class="mdi mdi-upload"></span> Import voices
|
||||||
<input type="file" id="s-import-voices-file" accept=".zip" style="display:none">
|
<input type="file" id="s-import-voices-file" accept=".zip" style="display:none">
|
||||||
</label>
|
</label>
|
||||||
<span class="note" id="s-import-status"></span>
|
<span class="note" id="s-import-status"></span>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<div class="section-head">
|
<div class="section-head">
|
||||||
<span class="section-icon">▶</span>
|
<span class="section-icon"><span class="mdi mdi-play"></span></span>
|
||||||
<div class="section-title">
|
<div class="section-title">
|
||||||
<h2>Try It Out</h2>
|
<h2>Try It Out</h2>
|
||||||
<p>Generate speech from text using any backend and voice. Also transcribe audio and re-speak it.</p>
|
<p>Generate speech from text using any backend and voice. Also transcribe audio and re-speak it.</p>
|
||||||
@ -74,9 +74,9 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="btn-row">
|
<div class="btn-row">
|
||||||
<button class="btn-primary" id="preview-btn">▶ Generate & play</button>
|
<button class="btn-primary" id="preview-btn"><span class="mdi mdi-play"></span> Generate & play</button>
|
||||||
<button class="btn-secondary" id="save-preview-mp3-btn" disabled>↓ Save MP3</button>
|
<button class="btn-secondary" id="save-preview-mp3-btn" disabled><span class="mdi mdi-download"></span> Save MP3</button>
|
||||||
<button class="btn-secondary" id="save-preview-btn" disabled>↓ Save WAV</button>
|
<button class="btn-secondary" id="save-preview-btn" disabled><span class="mdi mdi-download"></span> Save WAV</button>
|
||||||
<button class="btn-secondary" id="add-to-playlist-btn" disabled>+ Playlist</button>
|
<button class="btn-secondary" id="add-to-playlist-btn" disabled>+ Playlist</button>
|
||||||
</div>
|
</div>
|
||||||
<audio id="preview-audio" controls style="display:none"></audio>
|
<audio id="preview-audio" controls style="display:none"></audio>
|
||||||
@ -154,7 +154,7 @@
|
|||||||
<div class="history-empty">No clips in playlist. Use <strong>+ Playlist</strong> after generating.</div>
|
<div class="history-empty">No clips in playlist. Use <strong>+ Playlist</strong> after generating.</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="btn-row" style="margin-top:12px">
|
<div class="btn-row" style="margin-top:12px">
|
||||||
<button class="btn-primary" id="playlist-export-btn" disabled>↓ Export merged WAV</button>
|
<button class="btn-primary" id="playlist-export-btn" disabled><span class="mdi mdi-download"></span> Export merged WAV</button>
|
||||||
<button class="btn-secondary btn-sm" id="playlist-clear-btn">Clear</button>
|
<button class="btn-secondary btn-sm" id="playlist-clear-btn">Clear</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -164,7 +164,7 @@
|
|||||||
<!-- STT -> TTS -->
|
<!-- STT -> TTS -->
|
||||||
<div class="tab-content" id="tab-stt-tts">
|
<div class="tab-content" id="tab-stt-tts">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h2>STT → TTS workspace</h2>
|
<h2>STT <span class="mdi mdi-arrow-right"></span> TTS workspace</h2>
|
||||||
<p class="card-subtitle">Upload speech audio, transcribe it with the configured STT endpoint, then synthesize the resulting text with any available TTS backend.</p>
|
<p class="card-subtitle">Upload speech audio, transcribe it with the configured STT endpoint, then synthesize the resulting text with any available TTS backend.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
@ -183,7 +183,7 @@
|
|||||||
<label>Speech audio</label>
|
<label>Speech audio</label>
|
||||||
<div class="btn-row" style="gap:8px;align-items:center">
|
<div class="btn-row" style="gap:8px;align-items:center">
|
||||||
<input type="file" id="stt-tts-file" accept="audio/*,video/*">
|
<input type="file" id="stt-tts-file" accept="audio/*,video/*">
|
||||||
<button class="btn-secondary" id="stt-tts-rec-start" type="button" title="Record from microphone">🎤 Record</button>
|
<button class="btn-secondary" id="stt-tts-rec-start" type="button" title="Record from microphone"><span class="mdi mdi-microphone-variant"></span> Record</button>
|
||||||
<button class="btn-secondary" id="stt-tts-rec-stop" type="button" disabled>Stop</button>
|
<button class="btn-secondary" id="stt-tts-rec-stop" type="button" disabled>Stop</button>
|
||||||
<span class="mic-timer" id="stt-tts-rec-time">0:00</span>
|
<span class="mic-timer" id="stt-tts-rec-time">0:00</span>
|
||||||
</div>
|
</div>
|
||||||
@ -227,7 +227,7 @@
|
|||||||
<label class="refine-toggle"><input type="checkbox" id="refine-punctuation" checked> Fix punctuation</label>
|
<label class="refine-toggle"><input type="checkbox" id="refine-punctuation" checked> Fix punctuation</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="btn-row" style="margin-top:12px">
|
<div class="btn-row" style="margin-top:12px">
|
||||||
<button class="btn-primary" id="refine-btn" disabled>✨ Refine transcription</button>
|
<button class="btn-primary" id="refine-btn" disabled><span class="mdi mdi-auto-fix"></span> Refine transcription</button>
|
||||||
<button class="btn-secondary" id="refine-restore-btn" disabled>Restore original</button>
|
<button class="btn-secondary" id="refine-restore-btn" disabled>Restore original</button>
|
||||||
<span class="note" id="refine-status"></span>
|
<span class="note" id="refine-status"></span>
|
||||||
</div>
|
</div>
|
||||||
@ -305,7 +305,7 @@
|
|||||||
<textarea id="perf-text" rows="3">Hello, how are you today? Please read this sample clearly for a fair voice benchmark.</textarea>
|
<textarea id="perf-text" rows="3">Hello, how are you today? Please read this sample clearly for a fair voice benchmark.</textarea>
|
||||||
</div>
|
</div>
|
||||||
<div class="btn-row">
|
<div class="btn-row">
|
||||||
<button class="btn-primary" id="perf-run-btn">▶ Run benchmark</button>
|
<button class="btn-primary" id="perf-run-btn"><span class="mdi mdi-play"></span> Run benchmark</button>
|
||||||
<button class="btn-secondary" id="perf-clear-btn">Clear results</button>
|
<button class="btn-secondary" id="perf-clear-btn">Clear results</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="perf-progress" class="note" style="display:none"></div>
|
<div id="perf-progress" class="note" style="display:none"></div>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<div class="section-head">
|
<div class="section-head">
|
||||||
<span class="section-icon">🔊</span>
|
<span class="section-icon"><span class="mdi mdi-account-voice"></span></span>
|
||||||
<div class="section-title">
|
<div class="section-title">
|
||||||
<h2>My Voices</h2>
|
<h2>My Voices</h2>
|
||||||
<p>Pick a voice on the left, edit on the right.</p>
|
<p>Pick a voice on the left, edit on the right.</p>
|
||||||
@ -20,7 +20,7 @@
|
|||||||
<!-- TTS synth mode + preview sentence -->
|
<!-- TTS synth mode + preview sentence -->
|
||||||
<div class="vl-synth-panel">
|
<div class="vl-synth-panel">
|
||||||
<div class="vl-synth-mode-row">
|
<div class="vl-synth-mode-row">
|
||||||
<span class="vl-synth-icon">▶▶</span>
|
<span class="vl-synth-icon"><span class="mdi mdi-play"></span><span class="mdi mdi-play"></span></span>
|
||||||
<span class="vl-synth-label">Synth uses</span>
|
<span class="vl-synth-label">Synth uses</span>
|
||||||
<div class="vl-synth-seg" id="vl-synth-mode-seg">
|
<div class="vl-synth-seg" id="vl-synth-mode-seg">
|
||||||
<button class="vl-synth-seg-btn active" data-mode="preview"
|
<button class="vl-synth-seg-btn active" data-mode="preview"
|
||||||
@ -61,7 +61,7 @@
|
|||||||
|
|
||||||
<div id="voice-list"></div>
|
<div id="voice-list"></div>
|
||||||
<div class="vl-toolbar">
|
<div class="vl-toolbar">
|
||||||
<button class="btn-secondary vl-tb-btn" id="refresh-voices-btn" title="Refresh voice list">↻ Refresh</button>
|
<button class="btn-secondary vl-tb-btn" id="refresh-voices-btn" title="Refresh voice list"><span class="mdi mdi-refresh"></span> Refresh</button>
|
||||||
<button class="btn-secondary vl-tb-btn" id="sync-voice-folders-btn" title="Sync active/hidden folders">Sync</button>
|
<button class="btn-secondary vl-tb-btn" id="sync-voice-folders-btn" title="Sync active/hidden folders">Sync</button>
|
||||||
<button class="btn-secondary vl-tb-btn" id="calculate-db-btn" title="Calculate dBFS">Calc dB</button>
|
<button class="btn-secondary vl-tb-btn" id="calculate-db-btn" title="Calculate dBFS">Calc dB</button>
|
||||||
<button class="btn-secondary vl-tb-btn" id="benchmark-voices-btn" title="Benchmark TTS speed">Benchmark</button>
|
<button class="btn-secondary vl-tb-btn" id="benchmark-voices-btn" title="Benchmark TTS speed">Benchmark</button>
|
||||||
@ -78,7 +78,7 @@
|
|||||||
<!-- RIGHT: inspector / editor pane -->
|
<!-- RIGHT: inspector / editor pane -->
|
||||||
<div class="voices-inspector-pane" id="voices-inspector">
|
<div class="voices-inspector-pane" id="voices-inspector">
|
||||||
<div class="inspector-placeholder">
|
<div class="inspector-placeholder">
|
||||||
<span>🔊</span>
|
<span><span class="mdi mdi-account-voice"></span></span>
|
||||||
<p>Pick a voice on the left<br>to edit it here</p>
|
<p>Pick a voice on the left<br>to edit it here</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -162,14 +162,14 @@
|
|||||||
<div class="mic-buttons">
|
<div class="mic-buttons">
|
||||||
<button class="btn-secondary" id="lib-add-monitor-btn">Check level</button>
|
<button class="btn-secondary" id="lib-add-monitor-btn">Check level</button>
|
||||||
<button class="btn-secondary" id="lib-add-monitor-stop" disabled>Stop monitor</button>
|
<button class="btn-secondary" id="lib-add-monitor-stop" disabled>Stop monitor</button>
|
||||||
<button class="btn-red" id="lib-add-rec-start">● Record</button>
|
<button class="btn-red" id="lib-add-rec-start"><span class="mdi mdi-record-circle-outline"></span> Record</button>
|
||||||
<button class="btn-secondary" id="lib-add-rec-stop" disabled>■ Stop</button>
|
<button class="btn-secondary" id="lib-add-rec-stop" disabled><span class="mdi mdi-stop-circle-outline"></span> Stop</button>
|
||||||
<span class="mic-timer" id="lib-add-rec-time">0:00</span>
|
<span class="mic-timer" id="lib-add-rec-time">0:00</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="mic-monitor-box">
|
<div class="mic-monitor-box">
|
||||||
<div class="mic-monitor-head">
|
<div class="mic-monitor-head">
|
||||||
<span>Input level</span>
|
<span>Input level</span>
|
||||||
<span class="meter-readout" id="lib-add-db-readout">-∞ dB</span>
|
<span class="meter-readout" id="lib-add-db-readout">-<span class="mdi mdi-infinity"></span> dB</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="mic-meter" id="lib-add-mic-meter" aria-hidden="true"></div>
|
<div class="mic-meter" id="lib-add-mic-meter" aria-hidden="true"></div>
|
||||||
<div class="mic-gain-row">
|
<div class="mic-gain-row">
|
||||||
|
|||||||
@ -1,3 +1,17 @@
|
|||||||
|
/* ── MDI icon integration ────────────────────────────────────────────────── */
|
||||||
|
.mdi {
|
||||||
|
vertical-align: -0.125em;
|
||||||
|
line-height: 1;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
button .mdi, a .mdi { font-size: 15px; }
|
||||||
|
.nav-icon .mdi { font-size: 15px; line-height: 1; }
|
||||||
|
.section-icon .mdi { font-size: 22px; line-height: 1; }
|
||||||
|
.llm-card-icon .mdi { font-size: inherit; }
|
||||||
|
.llm-local-icon.mdi { font-size: 20px; }
|
||||||
|
.backend-metric-tag .mdi, .llm-metric-chip .mdi, .dc-metric-chip .mdi { font-size: 11px; vertical-align: -0.05em; }
|
||||||
|
.link-icon { font-size: 11px; opacity: .7; margin-left: 1px; }
|
||||||
|
|
||||||
/* ── Theme tokens (warm cream light theme) ──────────────────────────────── */
|
/* ── Theme tokens (warm cream light theme) ──────────────────────────────── */
|
||||||
:root {
|
:root {
|
||||||
--bg: #F5F3EE;
|
--bg: #F5F3EE;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user