UX: faster load, skeleton fix, About cleanup, name hint, Connect Apps docs
Loader: - Post-init modules (engines, ai-backends, generation, conversation) now load AFTER the skeleton is removed instead of before. The UI is visible ~500 ms sooner on average; those four modules load while the user is already browsing Voices / Clone / Design. - Removed the sttReady event approach that was triggering a duplicate /api/stt-backends call; init.js already populates all STT selects once on startup. Skeleton: - Replaced the card-grid placeholder with a two-column workbench skeleton (voice list rows on the left + inspector placeholder on the right) that matches the real My Voices layout. Connect Apps: - MCP Server, /speak REST endpoint, and Global hotkey daemon sections moved from Settings → About to Connect Apps, where they belong. - About page now has GitHub + Releases links instead. Clone section: - Added a hint note beneath the sample-text textarea reminding the user to replace the placeholder name (Sam / Alex / Marco …) with their own. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
b3c1e4cc7f
commit
0b3109cbf4
@ -51,11 +51,15 @@
|
||||
.sk-icon { width: 32px; height: 32px; border-radius: 8px; flex-shrink: 0; }
|
||||
.sk-title { display: flex; flex-direction: column; gap: 7px; }
|
||||
.sk-toolbar { display: flex; gap: 10px; align-items: center; }
|
||||
.sk-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 12px; }
|
||||
.sk-card { border-radius: 10px; overflow: hidden; }
|
||||
.sk-card-img { height: 56px; border-radius: 0; }
|
||||
.sk-card-body { padding: 10px 12px; display: flex; flex-direction: column; gap: 6px; }
|
||||
.sk-row { display: flex; gap: 10px; margin-top: 4px; }
|
||||
.sk-row { display: flex; gap: 10px; align-items: center; }
|
||||
.sk-workbench { display: grid; grid-template-columns: 300px 1fr; gap: 0; border: 1px solid var(--border,#e5e7eb); border-radius: 10px; overflow: hidden; min-height: 480px; }
|
||||
.sk-list { border-right: 1px solid var(--border,#e5e7eb); display: flex; flex-direction: column; }
|
||||
.sk-list-row { display: flex; align-items: center; gap: 10px; padding: 10px 12px; border-bottom: 1px solid var(--border,#e5e7eb); }
|
||||
.sk-avatar { width: 36px; height: 36px; border-radius: 8px; flex-shrink: 0; }
|
||||
.sk-list-body { display: flex; flex-direction: column; gap: 5px; flex: 1; min-width: 0; }
|
||||
.sk-insp { padding: 20px; display: flex; flex-direction: column; gap: 16px; }
|
||||
.sk-insp-head { display: flex; flex-direction: column; gap: 8px; }
|
||||
@media (max-width: 700px) { .sk-workbench { grid-template-columns: 1fr; } .sk-insp { display: none; } }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@ -74,6 +78,15 @@
|
||||
<div class="tab" data-tab="settings" tabindex="-1"><span class="tab-title">Settings</span></div>
|
||||
</nav>
|
||||
|
||||
<!-- Mobile top bar (hidden on desktop via CSS) -->
|
||||
<div id="mobile-topbar">
|
||||
<button id="sidebar-toggle" aria-label="Open navigation"><span class="mdi mdi-menu"></span></button>
|
||||
<span class="mobile-brand"><span class="mdi mdi-microphone-variant"></span> Voice Creator</span>
|
||||
</div>
|
||||
|
||||
<!-- Backdrop for sidebar drawer on mobile -->
|
||||
<div id="sidebar-backdrop"></div>
|
||||
|
||||
<!-- App shell -->
|
||||
<div id="app-shell">
|
||||
|
||||
@ -168,98 +181,38 @@
|
||||
<div class="sk" style="width:80px;height:30px;border-radius:6px"></div>
|
||||
</div>
|
||||
|
||||
<!-- Voice card grid (matches the voice library layout) -->
|
||||
<div class="sk-grid">
|
||||
<!-- 8 placeholder voice cards -->
|
||||
<div class="sk-card" style="border:1px solid var(--border,#e5e7eb)">
|
||||
<div class="sk sk-card-img"></div>
|
||||
<div class="sk-card-body">
|
||||
<div class="sk" style="width:70%;height:14px"></div>
|
||||
<div class="sk" style="width:45%;height:12px"></div>
|
||||
<div class="sk-row">
|
||||
<div class="sk" style="width:28px;height:28px;border-radius:50%"></div>
|
||||
<div class="sk" style="width:28px;height:28px;border-radius:50%"></div>
|
||||
<div class="sk" style="width:28px;height:28px;border-radius:50%"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Voices workbench skeleton (list + inspector, matches real My Voices layout) -->
|
||||
<div class="sk-workbench">
|
||||
<div class="sk-list">
|
||||
<div class="sk-list-row"><div class="sk sk-avatar"></div><div class="sk-list-body"><div class="sk" style="width:75%;height:13px"></div><div class="sk" style="width:50%;height:11px"></div></div><div class="sk" style="width:28px;height:28px;border-radius:50%;flex-shrink:0"></div><div class="sk" style="width:28px;height:28px;border-radius:50%;flex-shrink:0"></div></div>
|
||||
<div class="sk-list-row"><div class="sk sk-avatar"></div><div class="sk-list-body"><div class="sk" style="width:65%;height:13px"></div><div class="sk" style="width:40%;height:11px"></div></div><div class="sk" style="width:28px;height:28px;border-radius:50%;flex-shrink:0"></div><div class="sk" style="width:28px;height:28px;border-radius:50%;flex-shrink:0"></div></div>
|
||||
<div class="sk-list-row"><div class="sk sk-avatar"></div><div class="sk-list-body"><div class="sk" style="width:80%;height:13px"></div><div class="sk" style="width:45%;height:11px"></div></div><div class="sk" style="width:28px;height:28px;border-radius:50%;flex-shrink:0"></div><div class="sk" style="width:28px;height:28px;border-radius:50%;flex-shrink:0"></div></div>
|
||||
<div class="sk-list-row"><div class="sk sk-avatar"></div><div class="sk-list-body"><div class="sk" style="width:55%;height:13px"></div><div class="sk" style="width:35%;height:11px"></div></div><div class="sk" style="width:28px;height:28px;border-radius:50%;flex-shrink:0"></div><div class="sk" style="width:28px;height:28px;border-radius:50%;flex-shrink:0"></div></div>
|
||||
<div class="sk-list-row"><div class="sk sk-avatar"></div><div class="sk-list-body"><div class="sk" style="width:70%;height:13px"></div><div class="sk" style="width:48%;height:11px"></div></div><div class="sk" style="width:28px;height:28px;border-radius:50%;flex-shrink:0"></div><div class="sk" style="width:28px;height:28px;border-radius:50%;flex-shrink:0"></div></div>
|
||||
<div class="sk-list-row"><div class="sk sk-avatar"></div><div class="sk-list-body"><div class="sk" style="width:60%;height:13px"></div><div class="sk" style="width:42%;height:11px"></div></div><div class="sk" style="width:28px;height:28px;border-radius:50%;flex-shrink:0"></div><div class="sk" style="width:28px;height:28px;border-radius:50%;flex-shrink:0"></div></div>
|
||||
<div class="sk-list-row"><div class="sk sk-avatar"></div><div class="sk-list-body"><div class="sk" style="width:85%;height:13px"></div><div class="sk" style="width:38%;height:11px"></div></div><div class="sk" style="width:28px;height:28px;border-radius:50%;flex-shrink:0"></div><div class="sk" style="width:28px;height:28px;border-radius:50%;flex-shrink:0"></div></div>
|
||||
<div class="sk-list-row"><div class="sk sk-avatar"></div><div class="sk-list-body"><div class="sk" style="width:72%;height:13px"></div><div class="sk" style="width:44%;height:11px"></div></div><div class="sk" style="width:28px;height:28px;border-radius:50%;flex-shrink:0"></div><div class="sk" style="width:28px;height:28px;border-radius:50%;flex-shrink:0"></div></div>
|
||||
<div class="sk-list-row"><div class="sk sk-avatar"></div><div class="sk-list-body"><div class="sk" style="width:58%;height:13px"></div><div class="sk" style="width:36%;height:11px"></div></div><div class="sk" style="width:28px;height:28px;border-radius:50%;flex-shrink:0"></div><div class="sk" style="width:28px;height:28px;border-radius:50%;flex-shrink:0"></div></div>
|
||||
<div class="sk-list-row"><div class="sk sk-avatar"></div><div class="sk-list-body"><div class="sk" style="width:78%;height:13px"></div><div class="sk" style="width:52%;height:11px"></div></div><div class="sk" style="width:28px;height:28px;border-radius:50%;flex-shrink:0"></div><div class="sk" style="width:28px;height:28px;border-radius:50%;flex-shrink:0"></div></div>
|
||||
</div>
|
||||
<div class="sk-card" style="border:1px solid var(--border,#e5e7eb)">
|
||||
<div class="sk sk-card-img"></div>
|
||||
<div class="sk-card-body">
|
||||
<div class="sk" style="width:80%;height:14px"></div>
|
||||
<div class="sk" style="width:55%;height:12px"></div>
|
||||
<div class="sk-row">
|
||||
<div class="sk" style="width:28px;height:28px;border-radius:50%"></div>
|
||||
<div class="sk" style="width:28px;height:28px;border-radius:50%"></div>
|
||||
</div>
|
||||
<div class="sk-insp">
|
||||
<div class="sk-insp-head">
|
||||
<div class="sk" style="width:55%;height:20px"></div>
|
||||
<div class="sk" style="width:35%;height:13px"></div>
|
||||
<div class="sk-row" style="margin-top:4px"><div class="sk" style="width:60px;height:22px;border-radius:4px"></div><div class="sk" style="width:50px;height:22px;border-radius:4px"></div><div class="sk" style="width:80px;height:22px;border-radius:4px;margin-left:auto"></div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sk-card" style="border:1px solid var(--border,#e5e7eb)">
|
||||
<div class="sk sk-card-img"></div>
|
||||
<div class="sk-card-body">
|
||||
<div class="sk" style="width:60%;height:14px"></div>
|
||||
<div class="sk" style="width:40%;height:12px"></div>
|
||||
<div class="sk-row">
|
||||
<div class="sk" style="width:28px;height:28px;border-radius:50%"></div>
|
||||
<div class="sk" style="width:28px;height:28px;border-radius:50%"></div>
|
||||
<div class="sk" style="width:28px;height:28px;border-radius:50%"></div>
|
||||
</div>
|
||||
<div class="sk" style="height:1px;border-radius:0"></div>
|
||||
<div class="sk-insp-head">
|
||||
<div class="sk" style="width:40%;height:14px"></div>
|
||||
<div class="sk" style="width:100%;height:56px;border-radius:6px"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sk-card" style="border:1px solid var(--border,#e5e7eb)">
|
||||
<div class="sk sk-card-img"></div>
|
||||
<div class="sk-card-body">
|
||||
<div class="sk" style="width:75%;height:14px"></div>
|
||||
<div class="sk" style="width:50%;height:12px"></div>
|
||||
<div class="sk-row">
|
||||
<div class="sk" style="width:28px;height:28px;border-radius:50%"></div>
|
||||
<div class="sk" style="width:28px;height:28px;border-radius:50%"></div>
|
||||
</div>
|
||||
<div class="sk-insp-head">
|
||||
<div class="sk" style="width:30%;height:14px"></div>
|
||||
<div class="sk" style="width:100%;height:36px;border-radius:6px"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sk-card" style="border:1px solid var(--border,#e5e7eb)">
|
||||
<div class="sk sk-card-img"></div>
|
||||
<div class="sk-card-body">
|
||||
<div class="sk" style="width:65%;height:14px"></div>
|
||||
<div class="sk" style="width:42%;height:12px"></div>
|
||||
<div class="sk-row">
|
||||
<div class="sk" style="width:28px;height:28px;border-radius:50%"></div>
|
||||
<div class="sk" style="width:28px;height:28px;border-radius:50%"></div>
|
||||
<div class="sk" style="width:28px;height:28px;border-radius:50%"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sk-card" style="border:1px solid var(--border,#e5e7eb)">
|
||||
<div class="sk sk-card-img"></div>
|
||||
<div class="sk-card-body">
|
||||
<div class="sk" style="width:85%;height:14px"></div>
|
||||
<div class="sk" style="width:48%;height:12px"></div>
|
||||
<div class="sk-row">
|
||||
<div class="sk" style="width:28px;height:28px;border-radius:50%"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sk-card" style="border:1px solid var(--border,#e5e7eb)">
|
||||
<div class="sk sk-card-img"></div>
|
||||
<div class="sk-card-body">
|
||||
<div class="sk" style="width:72%;height:14px"></div>
|
||||
<div class="sk" style="width:38%;height:12px"></div>
|
||||
<div class="sk-row">
|
||||
<div class="sk" style="width:28px;height:28px;border-radius:50%"></div>
|
||||
<div class="sk" style="width:28px;height:28px;border-radius:50%"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sk-card" style="border:1px solid var(--border,#e5e7eb)">
|
||||
<div class="sk sk-card-img"></div>
|
||||
<div class="sk-card-body">
|
||||
<div class="sk" style="width:58%;height:14px"></div>
|
||||
<div class="sk" style="width:44%;height:12px"></div>
|
||||
<div class="sk-row">
|
||||
<div class="sk" style="width:28px;height:28px;border-radius:50%"></div>
|
||||
<div class="sk" style="width:28px;height:28px;border-radius:50%"></div>
|
||||
<div class="sk" style="width:28px;height:28px;border-radius:50%"></div>
|
||||
</div>
|
||||
<div class="sk-insp-head">
|
||||
<div class="sk" style="width:45%;height:14px"></div>
|
||||
<div class="sk" style="width:100%;height:36px;border-radius:6px"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -286,5 +286,4 @@ $('stt-tts-save-wav-btn')?.addEventListener('click', () => {
|
||||
sttTtsDownload(sttTtsOutputBlob, ($('stt-tts-voice-select').value || 'stt_tts') + '_stt_tts.wav');
|
||||
});
|
||||
|
||||
window.dispatchEvent(new Event('sttReady'));
|
||||
|
||||
|
||||
@ -903,9 +903,8 @@ $('clone-refresh-stt-btn')?.addEventListener('click', async () => {
|
||||
try { await refreshSttBackends($('clone-stt-backend')?.value); }
|
||||
finally { $('clone-refresh-stt-btn').disabled = false; }
|
||||
});
|
||||
// Populate STT dropdown on load (refreshSttBackends is defined in stt.js which loads after us)
|
||||
if (typeof refreshSttBackends === 'function') refreshSttBackends().catch(() => {});
|
||||
else window.addEventListener('sttReady', () => refreshSttBackends().catch(() => {}));
|
||||
// STT dropdown is populated by init.js → refreshSttBackends() which runs after all
|
||||
// feature modules (including stt.js) are loaded. No extra call needed here.
|
||||
|
||||
$('transcribe-btn').addEventListener('click', async () => {
|
||||
const id = trimmedFileId||designedFileId||currentFileId;
|
||||
|
||||
@ -108,18 +108,10 @@
|
||||
// D — init (needs everything above to be defined)
|
||||
await _load('/static/js/init.js');
|
||||
|
||||
// E — post-init modules (4 files fetched in parallel, executed in order)
|
||||
await _loadBatch([
|
||||
'/static/js/engines.js',
|
||||
'/static/js/ai-backends.js',
|
||||
'/static/js/generation.js',
|
||||
'/static/js/conversation.js',
|
||||
]);
|
||||
|
||||
// F — navigation
|
||||
// F — navigation (must run before revealing UI so the correct section shows)
|
||||
await _load('/static/nav.js');
|
||||
|
||||
// ── Remove skeleton, reveal sections ────────────────────────────────────
|
||||
// ── Remove skeleton, reveal sections — as soon as nav is ready ──────────
|
||||
var sk = document.getElementById('app-skeleton');
|
||||
if (sk) {
|
||||
sk.classList.add('sk-hidden');
|
||||
@ -128,4 +120,14 @@
|
||||
document.querySelectorAll('.page-section').forEach(function (s) {
|
||||
s.style.display = '';
|
||||
});
|
||||
|
||||
// E — post-init modules load in background AFTER the UI is already visible.
|
||||
// Engines, AI backends, generation, and conversation sections load while
|
||||
// the user browses Voices / Clone / Design, not before.
|
||||
_loadBatch([
|
||||
'/static/js/engines.js',
|
||||
'/static/js/ai-backends.js',
|
||||
'/static/js/generation.js',
|
||||
'/static/js/conversation.js',
|
||||
]);
|
||||
})();
|
||||
|
||||
@ -45,6 +45,7 @@
|
||||
</select>
|
||||
</div>
|
||||
<textarea class="sample-sentence" id="clone-sample-text" spellcheck="true">Hello! My name is Sam, and this is my voice. I can speak softly or with great strength. The crisp winter air, warm firelight, and the gentle sound of rain — these are the things I love. Can you hear how clearly I speak?</textarea>
|
||||
<p class="note" style="margin-top:6px"><span class="mdi mdi-pencil-outline"></span> Replace <strong>the name</strong> (Sam / Alex / Marco …) with your own name before recording.</p>
|
||||
</div>
|
||||
<div class="btn-row">
|
||||
<button class="btn-secondary" id="clone-monitor-btn"><span class="mdi mdi-waveform"></span> Check level</button>
|
||||
|
||||
@ -7,6 +7,64 @@
|
||||
</div>
|
||||
|
||||
<div class="tab-content" id="tab-integrations">
|
||||
|
||||
<!-- ── MCP Server ─────────────────────────────────────────────── -->
|
||||
<div class="card">
|
||||
<h2><span class="mdi mdi-connection"></span> MCP Server</h2>
|
||||
<p class="card-subtitle">Expose voice tools to Claude Code, Cursor, and other MCP-aware agents via Streamable HTTP (JSON-RPC 2.0). Tools: <code>speak</code>, <code>transcribe</code>, <code>list_captures</code>, <code>list_profiles</code>.</p>
|
||||
<div class="settings-grid compact">
|
||||
<div class="s-field">
|
||||
<label>HTTP transport URL</label>
|
||||
<code class="s-code-block" id="s-mcp-url">http://localhost:7890/mcp</code>
|
||||
</div>
|
||||
<div class="s-field">
|
||||
<label>Claude Code one-liner</label>
|
||||
<code class="s-code-block">claude mcp add voice-creator --transport http --url http://localhost:7890/mcp --header "X-Voice-Creator-Client-Id: claude-code"</code>
|
||||
</div>
|
||||
<div class="s-field">
|
||||
<label>Any HTTP MCP client (JSON)</label>
|
||||
<pre class="s-code-block" style="white-space:pre-wrap">{"mcpServers":{"voice-creator":{"url":"http://localhost:7890/mcp","headers":{"X-Voice-Creator-Client-Id":"my-agent"}}}}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── /speak REST endpoint ───────────────────────────────────── -->
|
||||
<div class="card">
|
||||
<h2><span class="mdi mdi-bullhorn-outline"></span> /speak REST endpoint</h2>
|
||||
<p class="card-subtitle">Generate audio from any app or script without routing rules.</p>
|
||||
<div class="settings-grid compact">
|
||||
<div class="s-field">
|
||||
<label>Generate speech (example)</label>
|
||||
<pre class="s-code-block" style="white-space:pre-wrap">curl -X POST http://localhost:7890/speak \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-Voice-Creator-Client-Id: my-script" \
|
||||
-d '{"text":"Hello world","voice":"EN_F_Anna"}' \
|
||||
--output speech.wav</pre>
|
||||
</div>
|
||||
<div class="s-field">
|
||||
<label>Per-client voice binding</label>
|
||||
<pre class="s-code-block" style="white-space:pre-wrap">curl -X PUT http://localhost:7890/speak/bindings/my-script \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"voice":"EN_F_Anna"}'</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Global hotkey daemon ───────────────────────────────────── -->
|
||||
<div class="card">
|
||||
<h2><span class="mdi mdi-keyboard-outline"></span> Global hotkey daemon</h2>
|
||||
<p class="card-subtitle">Push-to-talk transcription that types the result into any focused window on the host machine.</p>
|
||||
<div class="settings-grid compact">
|
||||
<div class="s-field">
|
||||
<label>Install & run (host machine)</label>
|
||||
<pre class="s-code-block" style="white-space:pre-wrap">pip install pynput sounddevice soundfile pyperclip requests
|
||||
python hotkey_daemon.py --server http://localhost:7890</pre>
|
||||
<span class="s-hint">Hold <kbd>Ctrl+Shift+Space</kbd> to record, release to transcribe and type. Linux: install <code>xdotool</code> for direct key injection.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── App integrations ───────────────────────────────────────── -->
|
||||
<div class="card">
|
||||
<h2>Use voices in other apps</h2>
|
||||
<p class="card-subtitle">The editor creates and manages the voice files. External apps should connect to the Creator proxy or a reachable TTS backend, then use one of the active voice names.</p>
|
||||
|
||||
@ -523,68 +523,12 @@
|
||||
<span class="s-about-chip"><span class="mdi mdi-vector-square"></span> MDI v7.4.47</span>
|
||||
</div>
|
||||
<div class="s-about-backends" id="s-about-backends"></div>
|
||||
<div style="margin-top:14px;display:flex;gap:10px;flex-wrap:wrap">
|
||||
<a href="https://github.com/mARTin-B78/tts-voice-creator-clone-and-design-2/releases" target="_blank" rel="noopener" class="btn-secondary" style="text-decoration:none;display:inline-flex;align-items:center;gap:6px"><span class="mdi mdi-tag-outline"></span> Releases & Changelog</a>
|
||||
<a href="https://github.com/mARTin-B78/tts-voice-creator-clone-and-design-2" target="_blank" rel="noopener" class="btn-secondary" style="text-decoration:none;display:inline-flex;align-items:center;gap:6px"><span class="mdi mdi-github"></span> GitHub</a>
|
||||
</div>
|
||||
<p class="note" style="margin-top:10px">MCP server, REST /speak endpoint, and global hotkey daemon are documented under <strong>Connect Apps</strong>.</p>
|
||||
</div>
|
||||
|
||||
<div class="s-group" style="margin-top:16px">
|
||||
<div class="s-group-head">
|
||||
<strong><span class="mdi mdi-connection"></span> MCP Server</strong>
|
||||
<span>Expose voice tools to Claude Code, Cursor, and other MCP-aware agents</span>
|
||||
</div>
|
||||
<div class="settings-grid compact">
|
||||
<div class="s-field">
|
||||
<label>HTTP transport URL</label>
|
||||
<code class="s-code-block" id="s-mcp-url">http://localhost:7890/mcp</code>
|
||||
<span class="s-hint">Streamable HTTP (JSON-RPC 2.0). Tools: <code>speak</code>, <code>transcribe</code>, <code>list_captures</code>, <code>list_profiles</code>.</span>
|
||||
</div>
|
||||
<div class="s-field">
|
||||
<label>Claude Code one-liner</label>
|
||||
<code class="s-code-block">claude mcp add voice-creator --transport http --url http://localhost:7890/mcp --header "X-Voice-Creator-Client-Id: claude-code"</code>
|
||||
</div>
|
||||
<div class="s-field">
|
||||
<label>Any HTTP MCP client (JSON)</label>
|
||||
<pre class="s-code-block" style="white-space:pre-wrap">{"mcpServers":{"voice-creator":{"url":"http://localhost:7890/mcp","headers":{"X-Voice-Creator-Client-Id":"my-agent"}}}}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="s-group" style="margin-top:16px">
|
||||
<div class="s-group-head">
|
||||
<strong><span class="mdi mdi-bullhorn-outline"></span> /speak REST endpoint</strong>
|
||||
<span>Generate audio from any app or script without routing rules</span>
|
||||
</div>
|
||||
<div class="settings-grid compact">
|
||||
<div class="s-field">
|
||||
<label>Generate speech (example)</label>
|
||||
<pre class="s-code-block" style="white-space:pre-wrap">curl -X POST http://localhost:7890/speak \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-Voice-Creator-Client-Id: my-script" \
|
||||
-d '{"text":"Hello world","voice":"EN_F_Anna"}' \
|
||||
--output speech.wav</pre>
|
||||
</div>
|
||||
<div class="s-field">
|
||||
<label>Per-client voice binding</label>
|
||||
<pre class="s-code-block" style="white-space:pre-wrap">curl -X PUT http://localhost:7890/speak/bindings/my-script \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"voice":"EN_F_Anna"}'</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="s-group" style="margin-top:16px">
|
||||
<div class="s-group-head">
|
||||
<strong><span class="mdi mdi-keyboard-outline"></span> Global hotkey daemon</strong>
|
||||
<span>Push-to-talk transcription that types into any focused window</span>
|
||||
</div>
|
||||
<div class="settings-grid compact">
|
||||
<div class="s-field">
|
||||
<label>Install & run (host machine)</label>
|
||||
<pre class="s-code-block" style="white-space:pre-wrap">pip install pynput sounddevice soundfile pyperclip requests
|
||||
python hotkey_daemon.py --server http://localhost:7890</pre>
|
||||
<span class="s-hint">Hold <kbd>Ctrl+Shift+Space</kbd> to record, release to transcribe and type. Linux: install <code>xdotool</code> for direct key injection.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user