Introduces the new Studio section (Source -> Characters -> Voices -> Perform & Export) that reuses the existing Read Aloud/Library/Script Rehearsal code via DOM reparenting instead of duplicating it, and rolls up a long tail of bugs found while producing a real audiobook through it: umlaut-eating name sanitizers, a voice picker that mispositioned itself and capped results at 60, PDF pagination silently breaking on trimmed \f markers, a race letting stale audio keep playing after a new line was clicked, an alias-overlap bug that could silently redirect a voice/image save onto the wrong character, voice design failing outright during brief TTS backend restarts instead of retrying, sparse cast entries defaulting to English/wrong gender, and a reassigned voice never reaching an already-open Stage session or invalidating its cached audio. Also adds a persistent per-line audio cache, audiobook export browsing/download, and an inline voice-design prompt editor. Full details in CHANGELOG.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
832 lines
47 KiB
HTML
832 lines
47 KiB
HTML
<style>
|
|
.header-cat-block { display: none !important; }
|
|
.header-cat-block.is-active { display: flex !important; }
|
|
</style>
|
|
<div class="section-head engines-page-head">
|
|
<div class="engines-head-main">
|
|
<span class="section-icon"><span class="mdi mdi-brain"></span></span>
|
|
<div class="section-title">
|
|
<h2>Engines</h2>
|
|
<p>Local and cloud services for language generation, speech recognition, and synthesis.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="engines-head-context header-cat-block" data-engines-cat="llm">
|
|
<div class="engines-context-copy">
|
|
<h3 class="llm-sec-title">Language Models</h3>
|
|
<p class="llm-sec-note">Pick the <strong>Active Language Model</strong> below. Cards connect engines and apply a URL with <strong>Use as LLM</strong>.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="engines-head-context header-cat-block" data-engines-cat="stt">
|
|
<div class="engines-context-copy">
|
|
<h3 class="llm-sec-title">Speech Recognition</h3>
|
|
<p class="llm-sec-note">Docker stack containers appear first, with local runners and cloud APIs below.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="engines-head-context header-cat-block" data-engines-cat="tts">
|
|
<div class="engines-context-copy">
|
|
<h3 class="llm-sec-title">Text to Speech</h3>
|
|
<p class="llm-sec-note">Docker-detected engines and local presets are listed together. Apply a URL with <strong>Use as TTS</strong>.</p>
|
|
</div>
|
|
<div class="engines-context-actions">
|
|
<button class="btn-secondary" type="button" onclick="openAddEngineDialog('tts')"><span class="mdi mdi-plus"></span> Add Custom</button>
|
|
<button class="btn-secondary dc-refresh-btn" type="button"><span class="mdi mdi-refresh"></span> Refresh</button>
|
|
<span class="llm-free-badge llm-free-local">100% Local</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="engines-head-context header-cat-block" data-engines-cat="image">
|
|
<div class="engines-context-copy">
|
|
<h3 class="llm-sec-title">Image Generation</h3>
|
|
<p class="llm-sec-note">Used to generate character profile pictures. Pick the active provider below and add its API key.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ── Language Models sub-page ──────────────────────────── -->
|
|
<div class="s-engines-page" data-page="llm">
|
|
|
|
<!-- ── Active Language Model (global picker) ──────────────── -->
|
|
<div class="llm-active-panel card">
|
|
<div class="llm-active-head">
|
|
<span class="llm-active-icon"><span class="mdi mdi-star-four-points"></span></span>
|
|
<div>
|
|
<h4 class="llm-active-title">Active Language Model</h4>
|
|
<p class="llm-active-sub">Default endpoint & model for all LLM tasks. Per-task dropdowns can still override it.</p>
|
|
</div>
|
|
</div>
|
|
<div class="llm-active-controls">
|
|
<label class="llm-active-lbl"><span class="mdi mdi-server-network"></span> Endpoint</label>
|
|
<select id="llm-active-url" class="llm-active-url-inp" style="appearance: auto; cursor: pointer; padding-right: 25px;">
|
|
<option value="">— Select an endpoint —</option>
|
|
</select>
|
|
<label class="llm-active-lbl"><span class="mdi mdi-brain"></span> Model</label>
|
|
<select id="llm-active-model" aria-label="Active language model"><option value="">— fetch models —</option></select>
|
|
<button id="llm-active-refresh" class="btn-secondary" type="button" title="Fetch models"><span class="mdi mdi-refresh"></span></button>
|
|
<span id="llm-active-status" class="llm-active-status"></span>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<div style="display:flex;justify-content:space-between;align-items:center;margin-top:20px;margin-bottom:12px">
|
|
<h4 class="llm-local-cat" style="margin:0;text-transform:uppercase">Local</h4>
|
|
<button class="btn-primary" type="button" onclick="openAddEngineDialog('llm')"><span class="mdi mdi-plus"></span> Add Custom</button>
|
|
</div>
|
|
<div id="dc-grid-llm" class="dc-grid" style="margin-bottom:14px"></div>
|
|
|
|
<div style="display:flex;justify-content:space-between;align-items:center;margin-top:28px;margin-bottom:12px">
|
|
<h4 class="llm-local-cat" style="margin:0;text-transform:uppercase"><span class="mdi mdi-earth"></span> Cloud APIs <span class="llm-free-badge" style="margin-left:8px;vertical-align:middle;text-transform:none">Free tiers</span></h4>
|
|
<button class="btn-primary" type="button" onclick="openAddEngineDialog('llm')"><span class="mdi mdi-plus"></span> Add Custom</button>
|
|
</div>
|
|
<div class="llm-service-grid">
|
|
|
|
<div class="llm-card llm-card-highlight">
|
|
<div class="llm-card-head">
|
|
<span class="llm-card-icon"><span class="mdi mdi-lightning-bolt"></span></span>
|
|
<div>
|
|
<div class="llm-card-name">Groq</div>
|
|
<div class="llm-card-sub">Ultra-fast inference · OpenAI-compatible</div>
|
|
</div>
|
|
<span class="llm-tier-badge llm-tier-free">Free</span>
|
|
</div>
|
|
<div class="llm-card-stats">
|
|
<span>30 000 tokens / min</span>
|
|
<span>14 400 req / day</span>
|
|
<span>Lowest latency</span>
|
|
</div>
|
|
<div class="llm-field-row">
|
|
<label class="llm-label">API key</label>
|
|
<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 <span class="mdi mdi-open-in-new link-icon"></span></a>
|
|
</div>
|
|
<div class="llm-endpoint">
|
|
<span class="llm-endpoint-label">Endpoint</span>
|
|
<code>https://api.groq.com/openai/v1</code>
|
|
</div>
|
|
<div class="llm-models">
|
|
<span class="llm-model-tag">llama-3.3-70b-versatile</span>
|
|
<span class="llm-model-tag">qwen-qwq-32b</span>
|
|
<span class="llm-model-tag">deepseek-r1-distill-llama-70b</span>
|
|
</div>
|
|
<div class="dc-controls-row" style="margin-top:10px">
|
|
<button class="llm-local-ping llm-cloud-connect-btn" data-llm-key="groq_llm" data-llm-endpoint="https://api.groq.com/openai/v1"><span class="mdi mdi-lan-connect"></span> Connect</button>
|
|
<button class="llm-use-btn dc-use-btn llm-cloud-use-btn" data-llm-key="groq_llm" data-llm-endpoint="https://api.groq.com/openai/v1" data-llm-model="llama-3.3-70b-versatile" style="margin-left:auto"><span class="mdi mdi-arrow-right-circle-outline"></span> Use as LLM</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="llm-card">
|
|
<div class="llm-card-head">
|
|
<span class="llm-card-icon"><span class="mdi mdi-earth"></span></span>
|
|
<div>
|
|
<div class="llm-card-name">OpenRouter</div>
|
|
<div class="llm-card-sub">50+ free models · OpenAI-compatible</div>
|
|
</div>
|
|
<span class="llm-tier-badge llm-tier-free">Free</span>
|
|
</div>
|
|
<div class="llm-card-stats">
|
|
<span>20 req / min</span>
|
|
<span>200 req / day (free models)</span>
|
|
<span>Single API for all models</span>
|
|
</div>
|
|
<div class="llm-field-row">
|
|
<label class="llm-label">API key</label>
|
|
<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 <span class="mdi mdi-open-in-new link-icon"></span></a>
|
|
</div>
|
|
<div class="llm-endpoint">
|
|
<span class="llm-endpoint-label">Endpoint</span>
|
|
<code>https://openrouter.ai/api/v1</code>
|
|
</div>
|
|
<div class="llm-models">
|
|
<span class="llm-model-tag">qwen/qwen3-235b-a22b:free</span>
|
|
<span class="llm-model-tag">deepseek/deepseek-r1-0528:free</span>
|
|
<span class="llm-model-tag">mistralai/mistral-7b-instruct:free</span>
|
|
</div>
|
|
<div class="dc-controls-row" style="margin-top:10px">
|
|
<button class="llm-local-ping llm-cloud-connect-btn" data-llm-key="openrouter" data-llm-endpoint="https://openrouter.ai/api/v1"><span class="mdi mdi-lan-connect"></span> Connect</button>
|
|
<button class="llm-use-btn dc-use-btn llm-cloud-use-btn" data-llm-key="openrouter" data-llm-endpoint="https://openrouter.ai/api/v1" data-llm-model="qwen/qwen3-235b-a22b:free" style="margin-left:auto"><span class="mdi mdi-arrow-right-circle-outline"></span> Use as LLM</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="llm-card">
|
|
<div class="llm-card-head">
|
|
<span class="llm-card-icon"><span class="mdi mdi-diamond-outline"></span></span>
|
|
<div>
|
|
<div class="llm-card-name">Google Gemini</div>
|
|
<div class="llm-card-sub">Gemini 2.5 Flash · Generous free tier</div>
|
|
</div>
|
|
<span class="llm-tier-badge llm-tier-free">Free</span>
|
|
</div>
|
|
<div class="llm-card-stats">
|
|
<span>250 000 tokens / min</span>
|
|
<span>15 req / min free</span>
|
|
<span>1M context window</span>
|
|
</div>
|
|
<div class="llm-field-row">
|
|
<label class="llm-label">API key</label>
|
|
<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 <span class="mdi mdi-open-in-new link-icon"></span></a>
|
|
</div>
|
|
<div class="llm-endpoint">
|
|
<span class="llm-endpoint-label">Endpoint</span>
|
|
<code>https://generativelanguage.googleapis.com/v1beta/openai/</code>
|
|
</div>
|
|
<div class="llm-info-note">Uses OpenAI-compat wrapper — use model <code>gemini-2.5-flash</code></div>
|
|
<div class="dc-controls-row" style="margin-top:10px">
|
|
<button class="llm-local-ping llm-cloud-connect-btn" data-llm-key="gemini" data-llm-endpoint="https://generativelanguage.googleapis.com/v1beta/openai/"><span class="mdi mdi-lan-connect"></span> Connect</button>
|
|
<button class="llm-use-btn dc-use-btn llm-cloud-use-btn" data-llm-key="gemini" data-llm-endpoint="https://generativelanguage.googleapis.com/v1beta/openai/" data-llm-model="gemini-2.5-flash" style="margin-left:auto"><span class="mdi mdi-arrow-right-circle-outline"></span> Use as LLM</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="llm-card">
|
|
<div class="llm-card-head">
|
|
<span class="llm-card-icon"><span class="mdi mdi-alpha-m-circle-outline"></span></span>
|
|
<div>
|
|
<div class="llm-card-name">Mistral AI</div>
|
|
<div class="llm-card-sub">OpenAI-compatible · EU-based</div>
|
|
</div>
|
|
<span class="llm-tier-badge llm-tier-free">Free</span>
|
|
</div>
|
|
<div class="llm-card-stats">
|
|
<span>1B tokens / month</span>
|
|
<span>2 req / min free</span>
|
|
<span>GDPR-compliant</span>
|
|
</div>
|
|
<div class="llm-field-row">
|
|
<label class="llm-label">API key</label>
|
|
<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 <span class="mdi mdi-open-in-new link-icon"></span></a>
|
|
</div>
|
|
<div class="llm-endpoint">
|
|
<span class="llm-endpoint-label">Endpoint</span>
|
|
<code>https://api.mistral.ai/v1</code>
|
|
</div>
|
|
<div class="llm-models">
|
|
<span class="llm-model-tag">mistral-small-latest</span>
|
|
<span class="llm-model-tag">mistral-large-latest</span>
|
|
</div>
|
|
<div class="dc-controls-row" style="margin-top:10px">
|
|
<button class="llm-local-ping llm-cloud-connect-btn" data-llm-key="mistral" data-llm-endpoint="https://api.mistral.ai/v1"><span class="mdi mdi-lan-connect"></span> Connect</button>
|
|
<button class="llm-use-btn dc-use-btn llm-cloud-use-btn" data-llm-key="mistral" data-llm-endpoint="https://api.mistral.ai/v1" data-llm-model="mistral-small-latest" style="margin-left:auto"><span class="mdi mdi-arrow-right-circle-outline"></span> Use as LLM</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="llm-card">
|
|
<div class="llm-card-head">
|
|
<span class="llm-card-icon"><span class="mdi mdi-robot-outline"></span></span>
|
|
<div>
|
|
<div class="llm-card-name">OpenAI</div>
|
|
<div class="llm-card-sub">GPT models · pay-as-you-go</div>
|
|
</div>
|
|
<span class="llm-tier-badge llm-tier-paid">Paid</span>
|
|
</div>
|
|
<div class="llm-field-row">
|
|
<label class="llm-label">API key</label>
|
|
<input type="password" class="llm-input" placeholder="sk-…" data-llm-key="openai_llm">
|
|
<a class="llm-link" href="https://platform.openai.com/api-keys" target="_blank" rel="noopener">Get key <span class="mdi mdi-open-in-new link-icon"></span></a>
|
|
</div>
|
|
<div class="llm-endpoint">
|
|
<span class="llm-endpoint-label">Endpoint</span>
|
|
<code>https://api.openai.com/v1</code>
|
|
</div>
|
|
<div class="llm-models">
|
|
<span class="llm-model-tag">gpt-4o</span>
|
|
<span class="llm-model-tag">gpt-4o-mini</span>
|
|
<span class="llm-model-tag">o3-mini</span>
|
|
</div>
|
|
<div class="dc-controls-row" style="margin-top:10px">
|
|
<button class="llm-local-ping llm-cloud-connect-btn" data-llm-key="openai_llm" data-llm-endpoint="https://api.openai.com/v1"><span class="mdi mdi-lan-connect"></span> Connect</button>
|
|
<button class="llm-use-btn dc-use-btn llm-cloud-use-btn" data-llm-key="openai_llm" data-llm-endpoint="https://api.openai.com/v1" data-llm-model="gpt-4o-mini" style="margin-left:auto"><span class="mdi mdi-arrow-right-circle-outline"></span> Use as LLM</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="llm-card">
|
|
<div class="llm-card-head">
|
|
<span class="llm-card-icon"><span class="mdi mdi-triangle-outline"></span></span>
|
|
<div>
|
|
<div class="llm-card-name">Anthropic</div>
|
|
<div class="llm-card-sub">Claude models · pay-as-you-go</div>
|
|
</div>
|
|
<span class="llm-tier-badge llm-tier-paid">Paid</span>
|
|
</div>
|
|
<div class="llm-field-row">
|
|
<label class="llm-label">API key</label>
|
|
<input type="password" class="llm-input" placeholder="sk-ant-…" data-llm-key="anthropic_llm">
|
|
<a class="llm-link" href="https://console.anthropic.com/settings/keys" target="_blank" rel="noopener">Get key <span class="mdi mdi-open-in-new link-icon"></span></a>
|
|
</div>
|
|
<div class="llm-endpoint">
|
|
<span class="llm-endpoint-label">Endpoint</span>
|
|
<code>https://api.anthropic.com/v1</code>
|
|
</div>
|
|
<div class="llm-info-note">Not OpenAI-compatible — needs the Anthropic Messages API, not <code>/chat/completions</code></div>
|
|
<div class="dc-controls-row" style="margin-top:10px">
|
|
<button class="llm-local-ping llm-cloud-connect-btn" data-llm-key="anthropic_llm" data-llm-endpoint="https://api.anthropic.com/v1" data-llm-probe-type="anthropic"><span class="mdi mdi-lan-connect"></span> Connect</button>
|
|
<button class="llm-use-btn dc-use-btn llm-cloud-use-btn" data-llm-key="anthropic_llm" data-llm-endpoint="https://api.anthropic.com/v1" data-llm-model="claude-3-5-haiku-latest" data-llm-incompatible="1" style="margin-left:auto"><span class="mdi mdi-arrow-right-circle-outline"></span> Use as LLM</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<h4 class="llm-local-cat" style="margin-top: 40px; text-transform: uppercase;">Additional Settings</h4>
|
|
<div class="card" style="margin-bottom:24px;">
|
|
<div class="s-page-head">
|
|
<h3>LLM Refinement Defaults</h3>
|
|
<p>Automatic text cleanup after transcription</p>
|
|
</div>
|
|
<div style="display: flex; flex-direction: column; gap: 16px; margin-bottom: 24px;">
|
|
<div class="s-field">
|
|
<label>Auto-refine after transcription</label>
|
|
<select id="s-auto-refine">
|
|
<option value="off">Off — show raw transcription</option>
|
|
<option value="on">On — refine automatically</option>
|
|
</select>
|
|
<span class="s-hint">When on, the LLM refinement runs immediately after each capture.</span>
|
|
</div>
|
|
<div class="s-field">
|
|
<label>Refinement model</label>
|
|
<input type="text" id="s-refine-model" placeholder="e.g. llama3.2, qwen3:8b, gpt-4o-mini">
|
|
<span class="s-hint">Model name sent to the active LLM URL. Leave empty to use the app's current default.</span>
|
|
</div>
|
|
</div>
|
|
<div class="s-toggle-grid">
|
|
<label class="s-toggle-row"><input type="checkbox" id="s-refine-fillers"><div><span class="s-toggle-title">Remove filler words</span><span class="s-hint">Strips um, uh, like, you know, sort of.</span></div></label>
|
|
<label class="s-toggle-row"><input type="checkbox" id="s-refine-repetitions"><div><span class="s-toggle-title">Remove repetitions</span><span class="s-hint">Collapses repeated words and phrases.</span></div></label>
|
|
<label class="s-toggle-row"><input type="checkbox" id="s-refine-corrections"><div><span class="s-toggle-title">Fix grammar & spelling</span><span class="s-hint">Light corrections without changing meaning.</span></div></label>
|
|
<label class="s-toggle-row"><input type="checkbox" id="s-refine-punctuation"><div><span class="s-toggle-title">Fix punctuation</span><span class="s-hint">Adds missing commas, periods, and sentence casing.</span></div></label>
|
|
</div>
|
|
<div class="s-page-actions" style="margin-top: 15px;">
|
|
<button class="btn-primary s-save-btn">Save settings</button>
|
|
</div>
|
|
</div>
|
|
|
|
</div><!-- /llm -->
|
|
|
|
<!-- ── Speech to Text sub-page ───────────────────────────── -->
|
|
<div class="s-engines-page" data-page="stt">
|
|
|
|
|
|
<div style="display:flex;justify-content:space-between;align-items:center;margin-top:20px;margin-bottom:12px">
|
|
<h4 class="llm-local-cat" style="margin:0;text-transform:uppercase">Local</h4>
|
|
<button class="btn-primary" type="button" onclick="openAddEngineDialog('stt')"><span class="mdi mdi-plus"></span> Add Custom</button>
|
|
</div>
|
|
<div id="dc-grid-stt" class="dc-grid" style="margin-bottom:14px"></div>
|
|
|
|
<div style="display:flex;justify-content:space-between;align-items:center;margin-top:28px;margin-bottom:12px">
|
|
<div style="display:flex;gap:8px;align-items:center">
|
|
<span class="mdi mdi-cloud-outline"></span>
|
|
<h4 class="llm-local-cat" style="margin:0;text-transform:uppercase">Cloud APIs</h4>
|
|
<span class="llm-free-badge">Free tiers</span>
|
|
</div>
|
|
<button class="btn-primary" type="button" onclick="openAddEngineDialog('stt')"><span class="mdi mdi-plus"></span> Add Custom</button>
|
|
</div>
|
|
<div class="llm-service-grid">
|
|
|
|
<div class="llm-card llm-card-highlight">
|
|
<div class="llm-card-head">
|
|
<span class="llm-card-icon"><span class="mdi mdi-lightning-bolt"></span></span>
|
|
<div>
|
|
<div class="llm-card-name">Groq Whisper</div>
|
|
<div class="llm-card-sub">whisper-large-v3-turbo · OpenAI-compatible</div>
|
|
</div>
|
|
<span class="llm-tier-badge llm-tier-free">Free</span>
|
|
</div>
|
|
<div class="llm-card-stats">
|
|
<span>2 000 req / day</span>
|
|
<span><span class="mdi mdi-lightning-bolt"></span> Fastest cloud STT</span>
|
|
<span><span class="mdi mdi-memory"></span> Cloud · 0 VRAM</span>
|
|
</div>
|
|
<div class="llm-field-row">
|
|
<label class="llm-label">API key</label>
|
|
<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 <span class="mdi mdi-open-in-new link-icon"></span></a>
|
|
</div>
|
|
<div class="llm-endpoint">
|
|
<span class="llm-endpoint-label">Endpoint</span>
|
|
<code>https://api.groq.com/openai/v1</code>
|
|
</div>
|
|
<div class="llm-models">
|
|
<span class="llm-model-tag">whisper-large-v3-turbo</span>
|
|
<span class="llm-model-tag">whisper-large-v3</span>
|
|
<span class="llm-model-tag">distil-whisper-large-v3-en</span>
|
|
</div>
|
|
<div class="llm-info-note">Save key in <strong>Settings → Groq API key</strong> to enable <em>Groq Whisper</em> in the STT dropdown.</div>
|
|
</div>
|
|
|
|
<div class="llm-card">
|
|
<div class="llm-card-head">
|
|
<span class="llm-card-icon"><span class="mdi mdi-emoticon-happy-outline"></span></span>
|
|
<div>
|
|
<div class="llm-card-name">HuggingFace Inference</div>
|
|
<div class="llm-card-sub">Serverless Whisper models</div>
|
|
</div>
|
|
<span class="llm-tier-badge llm-tier-free">Free</span>
|
|
</div>
|
|
<div class="llm-card-stats">
|
|
<span>~1 000 req / day</span>
|
|
<span>Slower cold starts</span>
|
|
<span>Many model variants</span>
|
|
</div>
|
|
<div class="llm-field-row">
|
|
<label class="llm-label">API key</label>
|
|
<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 <span class="mdi mdi-open-in-new link-icon"></span></a>
|
|
</div>
|
|
<div class="llm-endpoint">
|
|
<span class="llm-endpoint-label">Endpoint</span>
|
|
<code>https://api-inference.huggingface.co/models/openai/whisper-large-v3</code>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="llm-card">
|
|
<div class="llm-card-head">
|
|
<span class="llm-card-icon"><span class="mdi mdi-content-copy"></span></span>
|
|
<div>
|
|
<div class="llm-card-name">AssemblyAI</div>
|
|
<div class="llm-card-sub">High-accuracy transcription + speaker diarization</div>
|
|
</div>
|
|
<span class="llm-tier-badge llm-tier-free">Free</span>
|
|
</div>
|
|
<div class="llm-card-stats">
|
|
<span>100 h lifetime</span>
|
|
<span>Speaker labels</span>
|
|
<span>Auto-chapters</span>
|
|
</div>
|
|
<div class="llm-field-row">
|
|
<label class="llm-label">API key</label>
|
|
<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 <span class="mdi mdi-open-in-new link-icon"></span></a>
|
|
</div>
|
|
<div class="llm-endpoint">
|
|
<span class="llm-endpoint-label">Endpoint</span>
|
|
<code>https://api.assemblyai.com/v2/transcript</code>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
<div style="display:flex;justify-content:space-between;align-items:center;margin-top:28px;margin-bottom:12px">
|
|
<h4 class="llm-local-cat" style="margin:0;text-transform:uppercase">Additional Settings</h4>
|
|
</div>
|
|
<div class="card" style="margin-bottom: 24px;">
|
|
<div class="s-page-head">
|
|
<h3>STT Connections</h3>
|
|
<p>Set up endpoints for Speech-to-Text backends.</p>
|
|
</div>
|
|
<div class="settings-grid stt-settings-grid">
|
|
<div class="s-field stt-url-field">
|
|
<label>Active STT URL</label>
|
|
<input type="text" id="s-whisper-url" placeholder="http://host.docker.internal:8010">
|
|
<span class="s-hint">Default recognition endpoint. Expected: <code>POST /v1/audio/transcriptions</code>.</span>
|
|
<div class="btn-row settings-mini-actions">
|
|
<button class="btn-secondary" id="s-use-parakeet-asr" type="button">Use Parakeet</button>
|
|
<button class="btn-secondary" id="s-use-nvidia-router" type="button">Use router</button>
|
|
<button class="btn-secondary" id="s-use-faster-whisper" type="button">Use faster-whisper</button>
|
|
<button class="btn-secondary" id="s-use-whisper-cpp" type="button">Use whisper.cpp</button>
|
|
</div>
|
|
</div>
|
|
<div class="s-field">
|
|
<label>faster-whisper-server URL <span class="s-label-note">(CTranslate2 GPU)</span></label>
|
|
<input type="text" id="s-faster-whisper-url" placeholder="http://host.docker.internal:8000">
|
|
<span class="s-hint">GPU-accelerated Whisper via CTranslate2. ~70× RT · 1.5 GB VRAM.</span>
|
|
</div>
|
|
<div class="s-field">
|
|
<label>whisper.cpp URL <span class="s-label-note">(CPU/CUDA)</span></label>
|
|
<input type="text" id="s-whisper-cpp-url" placeholder="http://host.docker.internal:8080">
|
|
<span class="s-hint">Lightweight C++ Whisper server. ~8–15× RT CPU · ~1 GB RAM.</span>
|
|
</div>
|
|
<div class="s-field">
|
|
<label>Groq API key <span class="s-label-note">(Groq Whisper & LLM)</span></label>
|
|
<div class="s-key-row">
|
|
<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" aria-label="Toggle API key visibility"><span class="mdi mdi-eye-outline"></span></button>
|
|
</div>
|
|
<span class="s-hint">Used for Groq Whisper STT and LLM. Free: 2 000 req/day.</span>
|
|
</div>
|
|
</div>
|
|
<div class="s-page-actions" style="margin-top: 15px;">
|
|
<button class="btn-primary s-save-btn">Save connections</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Inline STT test panel -->
|
|
<div class="stt-test-panel" id="stt-test-panel">
|
|
<div class="stt-test-row">
|
|
<span class="stt-test-label">Quick test</span>
|
|
<select id="stt-test-backend" class="stt-test-select"></select>
|
|
<button id="stt-test-mic-btn" class="stt-test-mic" title="Hold to record, click again to stop">
|
|
<span class="mdi mdi-microphone"></span>
|
|
</button>
|
|
<span id="stt-test-status" class="stt-test-status">Ready</span>
|
|
</div>
|
|
<div id="stt-test-result" class="stt-test-result" style="display:none"></div>
|
|
</div>
|
|
<div class="card" style="margin-top: 24px;">
|
|
<div class="s-page-head">
|
|
<h3>STT Transcription Defaults</h3>
|
|
<p>Default language and preferred STT backend for captures.</p>
|
|
</div>
|
|
<div class="settings-grid settings-behavior-grid">
|
|
<div class="s-field">
|
|
<label>Default language</label>
|
|
<select id="s-stt-language">
|
|
<option value="">Auto-detect</option>
|
|
<option value="en">English</option>
|
|
<option value="de">German</option>
|
|
<option value="fr">French</option>
|
|
<option value="es">Spanish</option>
|
|
<option value="it">Italian</option>
|
|
<option value="pt">Portuguese</option>
|
|
<option value="nl">Dutch</option>
|
|
<option value="ja">Japanese</option>
|
|
<option value="zh">Chinese</option>
|
|
</select>
|
|
</div>
|
|
<div class="s-field">
|
|
<label>Preferred STT backend</label>
|
|
<select id="s-stt-preferred-backend">
|
|
<option value="">Use active STT URL (from Connections)</option>
|
|
<option value="groq">Groq Whisper — cloud, fastest</option>
|
|
<option value="faster-whisper">faster-whisper-server — GPU local</option>
|
|
<option value="whisper-cpp">whisper.cpp — CPU/CUDA local</option>
|
|
<option value="parakeet">NVIDIA Parakeet — GPU local</option>
|
|
</select>
|
|
<span class="s-hint">Overrides the active STT URL for STT-TTS panel captures.</span>
|
|
</div>
|
|
</div>
|
|
<div class="s-page-actions" style="margin-top: 15px;">
|
|
<button class="btn-primary s-save-btn">Save settings</button>
|
|
</div>
|
|
</div>
|
|
|
|
</div><!-- /stt -->
|
|
|
|
<!-- ── Text to Speech sub-page ───────────────────────────── -->
|
|
<div class="s-engines-page" data-page="tts">
|
|
|
|
<div style="display:flex;justify-content:space-between;align-items:center;margin-top:20px;margin-bottom:12px">
|
|
<h4 class="llm-local-cat" style="margin:0;text-transform:uppercase">Local</h4>
|
|
<button class="btn-primary" type="button" onclick="openAddEngineDialog('tts')"><span class="mdi mdi-plus"></span> Add Custom</button>
|
|
</div>
|
|
<div id="dc-grid-tts" class="dc-grid" style="margin-bottom:14px"></div>
|
|
|
|
<div style="display:flex;justify-content:space-between;align-items:center;margin-top:28px;margin-bottom:12px">
|
|
<div style="display:flex;gap:8px;align-items:center">
|
|
<span class="mdi mdi-cloud-outline"></span>
|
|
<h4 class="llm-local-cat" style="margin:0;text-transform:uppercase">Cloud APIs</h4>
|
|
<span class="llm-free-badge">Free tiers</span>
|
|
</div>
|
|
<button class="btn-primary" type="button" onclick="openAddEngineDialog('tts')"><span class="mdi mdi-plus"></span> Add Custom</button>
|
|
</div>
|
|
<div class="llm-service-grid">
|
|
|
|
<div class="llm-card llm-card-highlight">
|
|
<div class="llm-card-head">
|
|
<span class="llm-card-icon"><span class="mdi mdi-waveform"></span></span>
|
|
<div>
|
|
<div class="llm-card-name">ElevenLabs</div>
|
|
<div class="llm-card-sub">High-quality voice cloning & synthesis</div>
|
|
</div>
|
|
<span class="llm-tier-badge llm-tier-free">Free</span>
|
|
</div>
|
|
<div class="llm-card-stats">
|
|
<span>10 000 chars / month</span>
|
|
<span>2 500 char / request max</span>
|
|
<span>Voice cloning supported</span>
|
|
</div>
|
|
<div class="llm-field-row">
|
|
<label class="llm-label">API key</label>
|
|
<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 <span class="mdi mdi-open-in-new link-icon"></span></a>
|
|
</div>
|
|
<div class="llm-endpoint">
|
|
<span class="llm-endpoint-label">Endpoint</span>
|
|
<code>https://api.elevenlabs.io/v1/text-to-speech</code>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="llm-card">
|
|
<div class="llm-card-head">
|
|
<span class="llm-card-icon"><span class="mdi mdi-fish"></span></span>
|
|
<div>
|
|
<div class="llm-card-name">Fish Audio</div>
|
|
<div class="llm-card-sub">Voice cloning & multilingual TTS</div>
|
|
</div>
|
|
<span class="llm-tier-badge llm-tier-free">Free</span>
|
|
</div>
|
|
<div class="llm-card-stats">
|
|
<span>1 h audio / month</span>
|
|
<span>100 req / min</span>
|
|
<span>30+ languages</span>
|
|
</div>
|
|
<div class="llm-field-row">
|
|
<label class="llm-label">API key</label>
|
|
<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 <span class="mdi mdi-open-in-new link-icon"></span></a>
|
|
</div>
|
|
<div class="llm-endpoint">
|
|
<span class="llm-endpoint-label">Endpoint</span>
|
|
<code>https://api.fish.audio/v1/tts</code>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="llm-card">
|
|
<div class="llm-card-head">
|
|
<span class="llm-card-icon"><span class="mdi mdi-music-note"></span></span>
|
|
<div>
|
|
<div class="llm-card-name">Kokoro TTS</div>
|
|
<div class="llm-card-sub">82M model · HuggingFace Spaces demo</div>
|
|
</div>
|
|
<span class="llm-tier-badge llm-tier-demo">Demo</span>
|
|
</div>
|
|
<div class="llm-card-stats">
|
|
<span>Free web demo</span>
|
|
<span><$1 per 1M chars (paid)</span>
|
|
<span>High naturalness</span>
|
|
</div>
|
|
<div class="llm-info-box">
|
|
Use the HF Spaces web demo for quick tests, or run Kokoro locally via Docker for production use.
|
|
<br>
|
|
<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> <!-- CLOSE llm-service-grid -->
|
|
|
|
<h4 class="llm-local-cat" style="margin-top: 40px; text-transform: uppercase;">Additional Settings</h4>
|
|
<div class="card" style="margin-bottom: 24px;">
|
|
<div class="s-page-head">
|
|
<h3>TTS Playback & API Behavior</h3>
|
|
<p>Controls how previews play and how OpenAI-compatible requests are shaped.</p>
|
|
</div>
|
|
<div class="settings-grid settings-behavior-grid">
|
|
<div class="s-field">
|
|
<label>TTS preview playback</label>
|
|
<select id="s-tts-stream-mode">
|
|
<option value="auto">Stream when available, then fall back</option>
|
|
<option value="streaming">Streaming only</option>
|
|
<option value="buffered">Buffered WAV only</option>
|
|
</select>
|
|
<span class="s-hint">Buffered keeps Save WAV available. Streaming starts sooner.</span>
|
|
</div>
|
|
<div class="s-field">
|
|
<label>OpenAI-compatible request style</label>
|
|
<select id="s-tts-backend">
|
|
<option value="openai">Qwen3-TTS / OpenAI</option>
|
|
<option value="localai">LocalAI (/tts)</option>
|
|
<option value="pocket">Pocket-TTS</option>
|
|
<option value="nvidia_magpie">NVIDIA Magpie / OpenAI</option>
|
|
</select>
|
|
<span class="s-hint">Controls payload shape for the normal TTS API URL.</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card" style="margin-bottom: 24px;">
|
|
<div class="s-page-head">
|
|
<h3>Default Playback Voice</h3>
|
|
<p>Pre-selected voice in the STT-TTS panel</p>
|
|
</div>
|
|
<div class="settings-grid settings-behavior-grid">
|
|
<div class="s-field">
|
|
<label>Default voice</label>
|
|
<select id="s-captures-default-voice">
|
|
<option value="">None — select manually</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="s-page-actions" style="margin-top: 15px;">
|
|
<button class="btn-primary s-save-btn">Save settings</button>
|
|
<button class="btn-secondary s-reload-btn">Reload settings</button>
|
|
</div>
|
|
|
|
</div><!-- /tts -->
|
|
|
|
<!-- ── Image Generation sub-page ─────────────────────────── -->
|
|
<div class="s-engines-page" data-page="image">
|
|
|
|
<div class="llm-active-panel card">
|
|
<div class="llm-active-head">
|
|
<span class="llm-active-icon"><span class="mdi mdi-star-four-points"></span></span>
|
|
<div>
|
|
<h4 class="llm-active-title">Active Image Generation Provider</h4>
|
|
<p class="llm-active-sub">Used for the "Generate Image" action on a character's profile picture.</p>
|
|
</div>
|
|
</div>
|
|
<div class="llm-active-controls">
|
|
<label class="llm-active-lbl"><span class="mdi mdi-cloud-outline"></span> Provider</label>
|
|
<select id="image-gen-provider">
|
|
<option value="">— None selected —</option>
|
|
<option value="openai">OpenAI (gpt-image-1)</option>
|
|
<option value="google">Google (Gemini / Imagen)</option>
|
|
<option value="openrouter">OpenRouter (image-capable models)</option>
|
|
<option value="pollinations">Pollinations.ai (free, no key)</option>
|
|
<option value="comfyui">Local ComfyUI</option>
|
|
</select>
|
|
<label class="llm-active-lbl"><span class="mdi mdi-image-outline"></span> Model</label>
|
|
<select id="image-gen-model-select" style="max-width:240px"></select>
|
|
<input type="text" id="image-gen-model" placeholder="Custom model ID" style="max-width:200px;display:none">
|
|
<span id="image-gen-status" class="llm-active-status"></span>
|
|
</div>
|
|
<p class="llm-active-sub" style="margin-top:8px">OpenRouter reuses the same API key as its LLM card below — no separate key needed here.</p>
|
|
</div>
|
|
|
|
<div class="card" id="comfyui-config-panel" hidden style="margin-top:16px">
|
|
<div class="s-page-head">
|
|
<h3><span class="mdi mdi-graph-outline"></span> Local ComfyUI Workflow</h3>
|
|
<p>Runs a workflow on your own GPU instead of a paid API. Export it from ComfyUI's own UI first — Workflow menu → Export (API Format) — then paste the result below. The same workflow is used for both single portraits and character concept sheets; only the prompt text sent to it differs.</p>
|
|
</div>
|
|
<div style="display:flex; flex-direction:column; gap:14px">
|
|
<div class="s-field">
|
|
<label>ComfyUI URL</label>
|
|
<input type="text" id="comfyui-url" placeholder="http://host.docker.internal:8188">
|
|
<span class="s-hint">Base URL of your running ComfyUI instance (container port 8188 by default).</span>
|
|
</div>
|
|
<div class="s-field">
|
|
<label>Workflow JSON (API format)</label>
|
|
<textarea id="comfyui-workflow" rows="6" spellcheck="false" placeholder='{"3": {"class_type": "KSampler", "inputs": {...}}, "6": {"class_type": "CLIPTextEncode", "inputs": {"text": "", ...}}, ...}' style="font-family:monospace; font-size:12px"></textarea>
|
|
<span class="s-hint">Paste the raw JSON exported from ComfyUI's "Export (API Format)" action — not a regular saved workflow (that format has "nodes"/"links" keys and won't work here).</span>
|
|
</div>
|
|
<div style="display:flex; gap:14px; flex-wrap:wrap">
|
|
<div class="s-field" style="flex:1; min-width:160px">
|
|
<label>Prompt node ID</label>
|
|
<input type="text" id="comfyui-prompt-node-id" placeholder="e.g. 6">
|
|
<span class="s-hint">The node whose text input gets replaced with the character prompt each run.</span>
|
|
</div>
|
|
<div class="s-field" style="flex:1; min-width:160px">
|
|
<label>Prompt input field name</label>
|
|
<input type="text" id="comfyui-prompt-field" placeholder="text">
|
|
<span class="s-hint">Usually "text" (CLIPTextEncode) — check the node's "inputs" key in the JSON above.</span>
|
|
</div>
|
|
<div class="s-field" style="flex:1; min-width:160px">
|
|
<label>Output node ID</label>
|
|
<input type="text" id="comfyui-output-node-id" placeholder="e.g. 22">
|
|
<span class="s-hint">The SaveImage/PreviewImage node whose result to fetch.</span>
|
|
</div>
|
|
</div>
|
|
<div style="display:flex; gap:10px; align-items:center; flex-wrap:wrap">
|
|
<button type="button" class="btn-secondary" id="comfyui-test-btn"><span class="mdi mdi-lan-connect"></span> Test connection</button>
|
|
<button type="button" class="btn-secondary" id="comfyui-test-gen-btn"><span class="mdi mdi-image-outline"></span> Test generate</button>
|
|
<span id="comfyui-test-status" class="llm-active-status"></span>
|
|
</div>
|
|
<div id="comfyui-test-preview" hidden><img id="comfyui-test-img" style="max-width:220px; border-radius:8px; border:1px solid var(--border)"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div style="display:flex;gap:8px;align-items:center;margin-top:28px;margin-bottom:12px">
|
|
<span class="mdi mdi-cloud-outline"></span>
|
|
<h4 class="llm-local-cat" style="margin:0;text-transform:uppercase">Cloud APIs</h4>
|
|
</div>
|
|
<div class="llm-service-grid">
|
|
|
|
<div class="llm-card llm-card-highlight">
|
|
<div class="llm-card-head">
|
|
<span class="llm-card-icon"><span class="mdi mdi-robot-outline"></span></span>
|
|
<div>
|
|
<div class="llm-card-name">OpenAI</div>
|
|
<div class="llm-card-sub">gpt-image-1 · Images API</div>
|
|
</div>
|
|
</div>
|
|
<div class="llm-field-row">
|
|
<label class="llm-label">API key</label>
|
|
<input type="password" class="llm-input" placeholder="sk-…" data-llm-key="openai_image">
|
|
<a class="llm-link" href="https://platform.openai.com/api-keys" target="_blank" rel="noopener">Get key <span class="mdi mdi-open-in-new link-icon"></span></a>
|
|
</div>
|
|
<div class="llm-endpoint">
|
|
<span class="llm-endpoint-label">Endpoint</span>
|
|
<code>https://api.openai.com/v1/images/generations</code>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="llm-card">
|
|
<div class="llm-card-head">
|
|
<span class="llm-card-icon"><span class="mdi mdi-google"></span></span>
|
|
<div>
|
|
<div class="llm-card-name">Google</div>
|
|
<div class="llm-card-sub">Gemini 2.5 Flash Image / Imagen</div>
|
|
</div>
|
|
</div>
|
|
<div class="llm-field-row">
|
|
<label class="llm-label">API key</label>
|
|
<input type="password" class="llm-input" placeholder="AIza…" data-llm-key="google_image">
|
|
<a class="llm-link" href="https://aistudio.google.com/apikey" target="_blank" rel="noopener">Get key <span class="mdi mdi-open-in-new link-icon"></span></a>
|
|
</div>
|
|
<div class="llm-endpoint">
|
|
<span class="llm-endpoint-label">Endpoint</span>
|
|
<code>generativelanguage.googleapis.com</code>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="llm-card">
|
|
<div class="llm-card-head">
|
|
<span class="llm-card-icon"><span class="mdi mdi-flower-outline"></span></span>
|
|
<div>
|
|
<div class="llm-card-name">Pollinations.ai</div>
|
|
<div class="llm-card-sub">Free · no API key · no account</div>
|
|
</div>
|
|
<span class="llm-tier-badge llm-tier-free">Free</span>
|
|
</div>
|
|
<div class="llm-card-stats">
|
|
<span>No signup</span>
|
|
<span>Shared free queue</span>
|
|
<span>Third-party public service</span>
|
|
</div>
|
|
<div class="llm-endpoint">
|
|
<span class="llm-endpoint-label">Endpoint</span>
|
|
<code>image.pollinations.ai</code>
|
|
</div>
|
|
<div class="llm-models">
|
|
<span class="llm-model-tag">flux</span>
|
|
</div>
|
|
<div class="llm-info-note">No key or setup needed — just pick it as the Active Provider above. No SLA/rate-limit guarantee since it's a shared free public queue; retries automatically on "queue full" errors.</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="llm-info-note" style="margin-top:16px;">Local ComfyUI (no API key, runs on your own GPU) is configured separately above, in the Active Image Generation Provider panel.</div>
|
|
|
|
<div class="s-page-actions" style="margin-top: 15px;">
|
|
<button class="btn-primary s-save-btn">Save settings</button>
|
|
<button class="btn-secondary s-reload-btn">Reload settings</button>
|
|
</div>
|
|
|
|
</div><!-- /image -->
|
|
|
|
<!-- ── Add Custom Engine dialog ─────────────────────────────── -->
|
|
<dialog id="add-engine-dlg" class="add-engine-dlg">
|
|
<div class="add-engine-form">
|
|
<h3 class="add-engine-title"><span class="mdi mdi-plus-circle-outline"></span> Add Custom Engine</h3>
|
|
<div class="add-engine-field">
|
|
<label for="aed-name">Name</label>
|
|
<input id="aed-name" type="text" placeholder="My TTS Server" spellcheck="false">
|
|
</div>
|
|
<div class="add-engine-field">
|
|
<label for="aed-role">Role</label>
|
|
<select id="aed-role">
|
|
<option value="tts">Text to Speech (TTS)</option>
|
|
<option value="stt">Speech to Text (STT)</option>
|
|
<option value="llm">Language Model (LLM)</option>
|
|
</select>
|
|
</div>
|
|
<div class="add-engine-field">
|
|
<label for="aed-url">URL</label>
|
|
<input id="aed-url" type="text" placeholder="http://192.168.1.100:8080" spellcheck="false">
|
|
</div>
|
|
<div class="add-engine-field">
|
|
<label for="aed-container">Docker container name <span class="add-engine-opt">(optional — enables Stop / Start / Restart)</span></label>
|
|
<input id="aed-container" type="text" placeholder="my-tts-container" spellcheck="false">
|
|
</div>
|
|
<div class="add-engine-field">
|
|
<label for="aed-desc">Description <span class="add-engine-opt">(optional)</span></label>
|
|
<input id="aed-desc" type="text" placeholder="Short description">
|
|
</div>
|
|
<div class="add-engine-actions">
|
|
<button type="button" class="btn-secondary" id="aed-cancel">Cancel</button>
|
|
<button type="button" class="btn-primary" id="aed-save">Add Card</button>
|
|
</div>
|
|
</div>
|
|
</dialog>
|