Cross-platform distribution, UI improvements, and performance optimizations
- PyInstaller frozen sidecar: spec file, build script, and ffmpeg path resolver for self-contained distribution without Python prerequisites - Dual-mode sidecar launcher: frozen binary (production) with dev mode fallback - Parallel transcription + diarization pipeline (~30-40% faster) - GPU auto-detection for diarization (CUDA when available) - Async run_pipeline command for real-time progress event delivery - Web Audio API backend for instant playback and seeking - OpenAI-compatible provider replacing LiteLLM client-side routing - Cross-platform RAM detection (Linux/macOS/Windows) - Settings: speaker count hint, token reveal toggles, dark dropdown styling - Loading splash screen, flexbox layout fix for viewport overflow - Gitea Actions CI/CD pipeline (Linux, Windows, macOS ARM) - Updated README and CLAUDE.md documentation Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
let activeTab = $state<'transcription' | 'speakers' | 'ai' | 'local'>('transcription');
|
||||
let modelStatus = $state<'idle' | 'downloading' | 'success' | 'error'>('idle');
|
||||
let modelError = $state('');
|
||||
let revealedFields = $state<Set<string>>(new Set());
|
||||
|
||||
async function testAndDownloadModel() {
|
||||
if (!localSettings.hf_token) {
|
||||
@@ -111,7 +112,10 @@
|
||||
{:else if activeTab === 'speakers'}
|
||||
<div class="field">
|
||||
<label for="hf-token">HuggingFace Token</label>
|
||||
<input id="hf-token" type="password" bind:value={localSettings.hf_token} placeholder="hf_..." />
|
||||
<div class="input-reveal">
|
||||
<input id="hf-token" type={revealedFields.has('hf-token') ? 'text' : 'password'} bind:value={localSettings.hf_token} placeholder="hf_..." />
|
||||
<button type="button" class="reveal-btn" onclick={() => { const s = new Set(revealedFields); s.has('hf-token') ? s.delete('hf-token') : s.add('hf-token'); revealedFields = s; }}>{revealedFields.has('hf-token') ? 'Hide' : 'Show'}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-box">
|
||||
<p class="info-title">Setup (one-time)</p>
|
||||
@@ -150,6 +154,23 @@
|
||||
{#if modelStatus === 'error'}
|
||||
<p class="status-error">{modelError}</p>
|
||||
{/if}
|
||||
<div class="field" style="margin-top: 1rem;">
|
||||
<label for="num-speakers">Number of speakers</label>
|
||||
<select
|
||||
id="num-speakers"
|
||||
value={localSettings.num_speakers === null || localSettings.num_speakers === 0 ? '0' : String(localSettings.num_speakers)}
|
||||
onchange={(e) => {
|
||||
const v = parseInt((e.target as HTMLSelectElement).value, 10);
|
||||
localSettings.num_speakers = v === 0 ? null : v;
|
||||
}}
|
||||
>
|
||||
<option value="0">Auto-detect</option>
|
||||
{#each Array.from({ length: 20 }, (_, i) => i + 1) as n}
|
||||
<option value={String(n)}>{n}</option>
|
||||
{/each}
|
||||
</select>
|
||||
<p class="hint">Hint the expected number of speakers to speed up diarization clustering.</p>
|
||||
</div>
|
||||
<div class="field checkbox" style="margin-top: 1rem;">
|
||||
<label>
|
||||
<input type="checkbox" bind:checked={localSettings.skip_diarization} />
|
||||
@@ -163,14 +184,17 @@
|
||||
<option value="local">Local (llama-server)</option>
|
||||
<option value="openai">OpenAI</option>
|
||||
<option value="anthropic">Anthropic</option>
|
||||
<option value="litellm">LiteLLM</option>
|
||||
<option value="litellm">OpenAI Compatible</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{#if localSettings.ai_provider === 'openai'}
|
||||
<div class="field">
|
||||
<label for="openai-key">OpenAI API Key</label>
|
||||
<input id="openai-key" type="password" bind:value={localSettings.openai_api_key} placeholder="sk-..." />
|
||||
<div class="input-reveal">
|
||||
<input id="openai-key" type={revealedFields.has('openai-key') ? 'text' : 'password'} bind:value={localSettings.openai_api_key} placeholder="sk-..." />
|
||||
<button type="button" class="reveal-btn" onclick={() => { const s = new Set(revealedFields); s.has('openai-key') ? s.delete('openai-key') : s.add('openai-key'); revealedFields = s; }}>{revealedFields.has('openai-key') ? 'Hide' : 'Show'}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="openai-model">Model</label>
|
||||
@@ -179,13 +203,27 @@
|
||||
{:else if localSettings.ai_provider === 'anthropic'}
|
||||
<div class="field">
|
||||
<label for="anthropic-key">Anthropic API Key</label>
|
||||
<input id="anthropic-key" type="password" bind:value={localSettings.anthropic_api_key} placeholder="sk-ant-..." />
|
||||
<div class="input-reveal">
|
||||
<input id="anthropic-key" type={revealedFields.has('anthropic-key') ? 'text' : 'password'} bind:value={localSettings.anthropic_api_key} placeholder="sk-ant-..." />
|
||||
<button type="button" class="reveal-btn" onclick={() => { const s = new Set(revealedFields); s.has('anthropic-key') ? s.delete('anthropic-key') : s.add('anthropic-key'); revealedFields = s; }}>{revealedFields.has('anthropic-key') ? 'Hide' : 'Show'}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="anthropic-model">Model</label>
|
||||
<input id="anthropic-model" type="text" bind:value={localSettings.anthropic_model} />
|
||||
</div>
|
||||
{:else if localSettings.ai_provider === 'litellm'}
|
||||
<div class="field">
|
||||
<label for="litellm-base">API Base URL</label>
|
||||
<input id="litellm-base" type="text" bind:value={localSettings.litellm_api_base} placeholder="https://your-litellm-proxy.example.com" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="litellm-key">API Key</label>
|
||||
<div class="input-reveal">
|
||||
<input id="litellm-key" type={revealedFields.has('litellm-key') ? 'text' : 'password'} bind:value={localSettings.litellm_api_key} placeholder="sk-..." />
|
||||
<button type="button" class="reveal-btn" onclick={() => { const s = new Set(revealedFields); s.has('litellm-key') ? s.delete('litellm-key') : s.add('litellm-key'); revealedFields = s; }}>{revealedFields.has('litellm-key') ? 'Hide' : 'Show'}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="litellm-model">Model</label>
|
||||
<input id="litellm-model" type="text" bind:value={localSettings.litellm_model} placeholder="provider/model-name" />
|
||||
@@ -293,11 +331,36 @@
|
||||
color: #aaa;
|
||||
margin-bottom: 0.3rem;
|
||||
}
|
||||
.input-reveal {
|
||||
display: flex;
|
||||
gap: 0;
|
||||
}
|
||||
.input-reveal input {
|
||||
flex: 1;
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
.reveal-btn {
|
||||
background: #0f3460;
|
||||
border: 1px solid #4a5568;
|
||||
border-left: none;
|
||||
color: #aaa;
|
||||
padding: 0.5rem 0.6rem;
|
||||
border-radius: 0 4px 4px 0;
|
||||
cursor: pointer;
|
||||
font-size: 0.75rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.reveal-btn:hover {
|
||||
color: #e0e0e0;
|
||||
background: #1a4a7a;
|
||||
}
|
||||
.field input,
|
||||
.field select {
|
||||
width: 100%;
|
||||
background: #1a1a2e;
|
||||
color: #e0e0e0;
|
||||
color-scheme: dark;
|
||||
border: 1px solid #4a5568;
|
||||
border-radius: 4px;
|
||||
padding: 0.5rem;
|
||||
|
||||
Reference in New Issue
Block a user