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:
@@ -13,6 +13,7 @@
|
||||
import type { Segment, Speaker } from '$lib/types/transcript';
|
||||
import { onMount, tick } from 'svelte';
|
||||
|
||||
let appReady = $state(false);
|
||||
let waveformPlayer: WaveformPlayer;
|
||||
let audioUrl = $state('');
|
||||
let showSettings = $state(false);
|
||||
@@ -54,6 +55,8 @@
|
||||
document.addEventListener('keydown', handleKeyDown);
|
||||
document.addEventListener('click', handleClickOutside);
|
||||
|
||||
appReady = true;
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handleKeyDown);
|
||||
document.removeEventListener('click', handleClickOutside);
|
||||
@@ -200,6 +203,7 @@
|
||||
language: $settings.transcription_language || undefined,
|
||||
skipDiarization: $settings.skip_diarization || undefined,
|
||||
hfToken: $settings.hf_token || undefined,
|
||||
numSpeakers: $settings.num_speakers && $settings.num_speakers > 0 ? $settings.num_speakers : undefined,
|
||||
});
|
||||
|
||||
// Create speaker entries from pipeline result
|
||||
@@ -303,60 +307,70 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="app-header">
|
||||
<h1>Voice to Notes</h1>
|
||||
<div class="header-actions">
|
||||
<button class="import-btn" onclick={handleFileImport} disabled={isTranscribing}>
|
||||
{#if isTranscribing}
|
||||
Processing...
|
||||
{:else}
|
||||
Import Audio/Video
|
||||
{/if}
|
||||
</button>
|
||||
<button class="settings-btn" onclick={() => showSettings = true} title="Settings">
|
||||
Settings
|
||||
</button>
|
||||
{#if $segments.length > 0}
|
||||
<div class="export-dropdown">
|
||||
<button class="export-btn" onclick={() => showExportMenu = !showExportMenu}>
|
||||
Export
|
||||
</button>
|
||||
{#if showExportMenu}
|
||||
<div class="export-menu">
|
||||
{#each exportFormats as fmt}
|
||||
<button class="export-option" onclick={() => handleExport(fmt.format, fmt.ext, fmt.name)}>
|
||||
{fmt.name} (.{fmt.ext})
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
{#if !appReady}
|
||||
<div class="splash-screen">
|
||||
<h1 class="splash-title">Voice to Notes</h1>
|
||||
<p class="splash-subtitle">Loading...</p>
|
||||
<div class="splash-spinner"></div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="app-shell">
|
||||
<div class="app-header">
|
||||
<h1>Voice to Notes</h1>
|
||||
<div class="header-actions">
|
||||
<button class="import-btn" onclick={handleFileImport} disabled={isTranscribing}>
|
||||
{#if isTranscribing}
|
||||
Processing...
|
||||
{:else}
|
||||
Import Audio/Video
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</button>
|
||||
<button class="settings-btn" onclick={() => showSettings = true} title="Settings">
|
||||
Settings
|
||||
</button>
|
||||
{#if $segments.length > 0}
|
||||
<div class="export-dropdown">
|
||||
<button class="export-btn" onclick={() => showExportMenu = !showExportMenu}>
|
||||
Export
|
||||
</button>
|
||||
{#if showExportMenu}
|
||||
<div class="export-menu">
|
||||
{#each exportFormats as fmt}
|
||||
<button class="export-option" onclick={() => handleExport(fmt.format, fmt.ext, fmt.name)}>
|
||||
{fmt.name} (.{fmt.ext})
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="workspace">
|
||||
<div class="main-content">
|
||||
<WaveformPlayer bind:this={waveformPlayer} {audioUrl} />
|
||||
<TranscriptEditor onWordClick={handleWordClick} />
|
||||
<div class="workspace">
|
||||
<div class="main-content">
|
||||
<WaveformPlayer bind:this={waveformPlayer} {audioUrl} />
|
||||
<TranscriptEditor onWordClick={handleWordClick} />
|
||||
</div>
|
||||
<div class="sidebar-right">
|
||||
<SpeakerManager />
|
||||
<AIChatPanel />
|
||||
</div>
|
||||
</div>
|
||||
<div class="sidebar-right">
|
||||
<SpeakerManager />
|
||||
<AIChatPanel />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ProgressOverlay
|
||||
visible={isTranscribing}
|
||||
percent={transcriptionProgress}
|
||||
stage={transcriptionStage}
|
||||
message={transcriptionMessage}
|
||||
/>
|
||||
<ProgressOverlay
|
||||
visible={isTranscribing}
|
||||
percent={transcriptionProgress}
|
||||
stage={transcriptionStage}
|
||||
message={transcriptionMessage}
|
||||
/>
|
||||
|
||||
<SettingsModal
|
||||
visible={showSettings}
|
||||
onClose={() => showSettings = false}
|
||||
/>
|
||||
<SettingsModal
|
||||
visible={showSettings}
|
||||
onClose={() => showSettings = false}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.app-header {
|
||||
@@ -453,11 +467,18 @@
|
||||
.export-option:hover {
|
||||
background: rgba(233, 69, 96, 0.2);
|
||||
}
|
||||
.app-shell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
.workspace {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
padding: 1rem;
|
||||
height: calc(100vh - 3rem);
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
background: #0a0a23;
|
||||
}
|
||||
@@ -467,6 +488,8 @@
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.sidebar-right {
|
||||
width: 300px;
|
||||
@@ -474,5 +497,38 @@
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
flex-shrink: 0;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.splash-screen {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100vh;
|
||||
background: #0a0a23;
|
||||
color: #e0e0e0;
|
||||
gap: 1rem;
|
||||
}
|
||||
.splash-title {
|
||||
font-size: 2rem;
|
||||
margin: 0;
|
||||
color: #e94560;
|
||||
}
|
||||
.splash-subtitle {
|
||||
font-size: 1rem;
|
||||
color: #888;
|
||||
margin: 0;
|
||||
}
|
||||
.splash-spinner {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: 3px solid #2a3a5e;
|
||||
border-top-color: #e94560;
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user