Phase 6: Llama-server manager, settings UI, packaging, and polish

- Implement LlamaManager in Rust for llama-server lifecycle: spawn with
  port allocation, health check, clean shutdown on Drop, model listing
- Add llama_start/stop/status/list_models Tauri commands
- Add load_settings/save_settings commands with JSON persistence
- Build SettingsModal with tabs for Transcription, AI Provider, Local AI
  settings (model size, device, language, API keys, provider selection)
- Wire settings into pipeline calls (model, device, language, skip diarization)
- Configure Tauri packaging: asset protocol for local audio files,
  CSP policy, bundle metadata, Linux .deb/.AppImage and Windows .msi config
- Add keyboard shortcuts: Space (play/pause), Ctrl+O (import),
  Ctrl+, (settings), Escape (close menus/modals)
- Close export dropdown on outside click
- Tests: 30 Python, 6 Rust, 0 Svelte errors

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-26 16:38:23 -08:00
parent d67625cd5a
commit 97a1a15755
12 changed files with 860 additions and 10 deletions

View File

@@ -6,11 +6,58 @@
import SpeakerManager from '$lib/components/SpeakerManager.svelte';
import AIChatPanel from '$lib/components/AIChatPanel.svelte';
import ProgressOverlay from '$lib/components/ProgressOverlay.svelte';
import SettingsModal from '$lib/components/SettingsModal.svelte';
import { segments, speakers } from '$lib/stores/transcript';
import { settings, loadSettings } from '$lib/stores/settings';
import type { Segment, Speaker } from '$lib/types/transcript';
import { onMount } from 'svelte';
let waveformPlayer: WaveformPlayer;
let audioUrl = $state('');
let showSettings = $state(false);
onMount(() => {
loadSettings();
// Global keyboard shortcuts
function handleKeyDown(e: KeyboardEvent) {
// Don't trigger shortcuts when typing in inputs
const tag = (e.target as HTMLElement)?.tagName;
if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') return;
if (e.key === ' ' && !e.ctrlKey && !e.metaKey) {
e.preventDefault();
waveformPlayer?.togglePlayPause?.();
} else if (e.key === 'o' && (e.ctrlKey || e.metaKey)) {
e.preventDefault();
handleFileImport();
} else if (e.key === ',' && (e.ctrlKey || e.metaKey)) {
e.preventDefault();
showSettings = true;
} else if (e.key === 'Escape') {
showExportMenu = false;
showSettings = false;
}
}
// Close export dropdown on outside click
function handleClickOutside(e: MouseEvent) {
if (showExportMenu) {
const target = e.target as HTMLElement;
if (!target.closest('.export-dropdown')) {
showExportMenu = false;
}
}
}
document.addEventListener('keydown', handleKeyDown);
document.addEventListener('click', handleClickOutside);
return () => {
document.removeEventListener('keydown', handleKeyDown);
document.removeEventListener('click', handleClickOutside);
};
});
let isTranscribing = $state(false);
let transcriptionProgress = $state(0);
let transcriptionStage = $state('');
@@ -61,7 +108,13 @@
duration_ms: number;
speakers: string[];
num_speakers: number;
}>('run_pipeline', { filePath });
}>('run_pipeline', {
filePath,
model: $settings.transcription_model || undefined,
device: $settings.transcription_device || undefined,
language: $settings.transcription_language || undefined,
skipDiarization: $settings.skip_diarization || undefined,
});
// Create speaker entries from pipeline result
const newSpeakers: Speaker[] = (result.speakers || []).map((label, idx) => ({
@@ -167,6 +220,9 @@
<button class="import-btn" onclick={handleFileImport}>
Import Audio/Video
</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}>
@@ -204,6 +260,11 @@
message={transcriptionMessage}
/>
<SettingsModal
visible={showSettings}
onClose={() => showSettings = false}
/>
<style>
.app-header {
display: flex;
@@ -235,6 +296,19 @@
gap: 0.5rem;
align-items: center;
}
.settings-btn {
background: none;
border: 1px solid #4a5568;
color: #e0e0e0;
padding: 0.5rem 0.75rem;
border-radius: 6px;
cursor: pointer;
font-size: 0.875rem;
}
.settings-btn:hover {
background: rgba(255,255,255,0.05);
border-color: #e94560;
}
.export-dropdown {
position: relative;
}