2026-02-26 15:53:09 -08:00
|
|
|
<script lang="ts">
|
|
|
|
|
import { onMount, onDestroy } from 'svelte';
|
|
|
|
|
import WaveSurfer from 'wavesurfer.js';
|
|
|
|
|
import { isPlaying, currentTimeMs, durationMs } from '$lib/stores/playback';
|
|
|
|
|
|
|
|
|
|
interface Props {
|
|
|
|
|
audioUrl?: string;
|
|
|
|
|
onSeek?: (timeMs: number) => void;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let { audioUrl = '', onSeek }: Props = $props();
|
|
|
|
|
|
|
|
|
|
let container: HTMLDivElement;
|
|
|
|
|
let wavesurfer: WaveSurfer | null = $state(null);
|
2026-02-26 18:02:48 -08:00
|
|
|
let isReady = $state(false);
|
2026-02-26 15:53:09 -08:00
|
|
|
let currentTime = $state('0:00');
|
|
|
|
|
let totalTime = $state('0:00');
|
|
|
|
|
|
|
|
|
|
function formatTime(seconds: number): string {
|
|
|
|
|
const m = Math.floor(seconds / 60);
|
|
|
|
|
const s = Math.floor(seconds % 60);
|
|
|
|
|
return `${m}:${s.toString().padStart(2, '0')}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onMount(() => {
|
|
|
|
|
wavesurfer = WaveSurfer.create({
|
|
|
|
|
container,
|
|
|
|
|
waveColor: '#4a5568',
|
|
|
|
|
progressColor: '#e94560',
|
|
|
|
|
cursorColor: '#e94560',
|
|
|
|
|
height: 80,
|
|
|
|
|
barWidth: 2,
|
|
|
|
|
barGap: 1,
|
|
|
|
|
barRadius: 2,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
wavesurfer.on('timeupdate', (time: number) => {
|
|
|
|
|
currentTimeMs.set(Math.round(time * 1000));
|
|
|
|
|
currentTime = formatTime(time);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
wavesurfer.on('ready', () => {
|
2026-02-26 18:02:48 -08:00
|
|
|
isReady = true;
|
2026-02-26 15:53:09 -08:00
|
|
|
const dur = wavesurfer!.getDuration();
|
|
|
|
|
durationMs.set(Math.round(dur * 1000));
|
|
|
|
|
totalTime = formatTime(dur);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
wavesurfer.on('play', () => isPlaying.set(true));
|
|
|
|
|
wavesurfer.on('pause', () => isPlaying.set(false));
|
|
|
|
|
wavesurfer.on('finish', () => isPlaying.set(false));
|
|
|
|
|
|
2026-02-26 18:02:48 -08:00
|
|
|
wavesurfer.on('loading', () => {
|
|
|
|
|
isReady = false;
|
|
|
|
|
});
|
|
|
|
|
|
2026-02-26 15:53:09 -08:00
|
|
|
if (audioUrl) {
|
|
|
|
|
wavesurfer.load(audioUrl);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
onDestroy(() => {
|
|
|
|
|
wavesurfer?.destroy();
|
|
|
|
|
});
|
|
|
|
|
|
2026-02-26 18:02:48 -08:00
|
|
|
/** Toggle play/pause from current position. Exposed for keyboard shortcuts. */
|
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>
2026-02-26 16:38:23 -08:00
|
|
|
export function togglePlayPause() {
|
2026-02-26 18:02:48 -08:00
|
|
|
if (!wavesurfer || !isReady) return;
|
|
|
|
|
wavesurfer.playPause();
|
2026-02-26 15:53:09 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function skipBack() {
|
2026-02-26 18:02:48 -08:00
|
|
|
if (wavesurfer && isReady) {
|
2026-02-26 15:53:09 -08:00
|
|
|
const time = Math.max(0, wavesurfer.getCurrentTime() - 5);
|
|
|
|
|
wavesurfer.setTime(time);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function skipForward() {
|
2026-02-26 18:02:48 -08:00
|
|
|
if (wavesurfer && isReady) {
|
2026-02-26 15:53:09 -08:00
|
|
|
const time = Math.min(wavesurfer.getDuration(), wavesurfer.getCurrentTime() + 5);
|
|
|
|
|
wavesurfer.setTime(time);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Seek to a specific time in milliseconds. Called from transcript click-to-seek. */
|
|
|
|
|
export function seekTo(timeMs: number) {
|
2026-02-26 18:02:48 -08:00
|
|
|
if (!wavesurfer || !isReady) {
|
|
|
|
|
console.warn('[voice-to-notes] seekTo ignored — audio not ready yet');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const timeSec = timeMs / 1000;
|
|
|
|
|
wavesurfer.setTime(timeSec);
|
|
|
|
|
if (!wavesurfer.isPlaying()) {
|
|
|
|
|
wavesurfer.play();
|
2026-02-26 15:53:09 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Load a new audio file. */
|
|
|
|
|
export function loadAudio(url: string) {
|
2026-02-26 18:02:48 -08:00
|
|
|
isReady = false;
|
2026-02-26 15:53:09 -08:00
|
|
|
wavesurfer?.load(url);
|
|
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
|
Phase 1 foundation: Tauri shell, Python sidecar, SQLite database
Tauri v2 + Svelte + TypeScript frontend:
- App shell with workspace layout (waveform, transcript, speakers, AI chat)
- Placeholder components for all major UI areas
- Typed stores (project, transcript, playback, AI)
- TypeScript interfaces matching the database schema
- Tauri bridge service with typed invoke wrappers
- svelte-check passes with 0 errors
Rust backend:
- Tauri v2 app entry point with command registration
- SQLite database layer (rusqlite with bundled SQLite)
- Full schema: projects, media_files, speakers, segments, words,
ai_outputs, annotations (with indexes)
- Model structs with serde serialization
- CRUD queries for projects, speakers, segments, words
- Segment text editing preserves original text
- Schema versioning for future migrations
- 6 tests passing
- Command stubs for project, transcribe, export, AI, settings, system
- App state management
Python sidecar:
- JSON-line IPC protocol (stdin/stdout)
- Message types: IPCMessage, progress, error, ready
- Handler registry with routing and error handling
- Ping/pong handler for connectivity testing
- Service stubs: transcribe, diarize, pipeline, AI, export
- Provider stubs: local (llama-server), OpenAI, Anthropic, LiteLLM
- Hardware detection stubs
- 14 tests passing, ruff clean
Also adds:
- Testing strategy document (docs/TESTING.md)
- Validation script (scripts/validate.sh)
- Updated .gitignore for Svelte, Rust, Python artifacts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 15:16:06 -08:00
|
|
|
<div class="waveform-player">
|
2026-02-26 15:53:09 -08:00
|
|
|
<div class="waveform-container" bind:this={container}></div>
|
|
|
|
|
<div class="controls">
|
2026-02-26 18:02:48 -08:00
|
|
|
<button class="control-btn" onclick={skipBack} title="Back 5s" disabled={!isReady}>⏪</button>
|
|
|
|
|
<button class="control-btn play-btn" onclick={togglePlayPause} title="Play/Pause" disabled={!isReady}>
|
|
|
|
|
{#if !isReady}
|
|
|
|
|
⏳
|
|
|
|
|
{:else if $isPlaying}
|
|
|
|
|
⏸
|
|
|
|
|
{:else}
|
|
|
|
|
▶
|
|
|
|
|
{/if}
|
2026-02-26 15:53:09 -08:00
|
|
|
</button>
|
2026-02-26 18:02:48 -08:00
|
|
|
<button class="control-btn" onclick={skipForward} title="Forward 5s" disabled={!isReady}>⏩</button>
|
2026-02-26 15:53:09 -08:00
|
|
|
<span class="time">{currentTime} / {totalTime}</span>
|
|
|
|
|
</div>
|
Phase 1 foundation: Tauri shell, Python sidecar, SQLite database
Tauri v2 + Svelte + TypeScript frontend:
- App shell with workspace layout (waveform, transcript, speakers, AI chat)
- Placeholder components for all major UI areas
- Typed stores (project, transcript, playback, AI)
- TypeScript interfaces matching the database schema
- Tauri bridge service with typed invoke wrappers
- svelte-check passes with 0 errors
Rust backend:
- Tauri v2 app entry point with command registration
- SQLite database layer (rusqlite with bundled SQLite)
- Full schema: projects, media_files, speakers, segments, words,
ai_outputs, annotations (with indexes)
- Model structs with serde serialization
- CRUD queries for projects, speakers, segments, words
- Segment text editing preserves original text
- Schema versioning for future migrations
- 6 tests passing
- Command stubs for project, transcribe, export, AI, settings, system
- App state management
Python sidecar:
- JSON-line IPC protocol (stdin/stdout)
- Message types: IPCMessage, progress, error, ready
- Handler registry with routing and error handling
- Ping/pong handler for connectivity testing
- Service stubs: transcribe, diarize, pipeline, AI, export
- Provider stubs: local (llama-server), OpenAI, Anthropic, LiteLLM
- Hardware detection stubs
- 14 tests passing, ruff clean
Also adds:
- Testing strategy document (docs/TESTING.md)
- Validation script (scripts/validate.sh)
- Updated .gitignore for Svelte, Rust, Python artifacts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 15:16:06 -08:00
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<style>
|
|
|
|
|
.waveform-player {
|
|
|
|
|
background: #1a1a2e;
|
|
|
|
|
border-radius: 8px;
|
2026-02-26 15:53:09 -08:00
|
|
|
padding: 0.75rem;
|
|
|
|
|
}
|
|
|
|
|
.waveform-container {
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
}
|
|
|
|
|
.controls {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 0.5rem;
|
|
|
|
|
margin-top: 0.5rem;
|
|
|
|
|
}
|
|
|
|
|
.control-btn {
|
|
|
|
|
background: #0f3460;
|
|
|
|
|
border: none;
|
Phase 1 foundation: Tauri shell, Python sidecar, SQLite database
Tauri v2 + Svelte + TypeScript frontend:
- App shell with workspace layout (waveform, transcript, speakers, AI chat)
- Placeholder components for all major UI areas
- Typed stores (project, transcript, playback, AI)
- TypeScript interfaces matching the database schema
- Tauri bridge service with typed invoke wrappers
- svelte-check passes with 0 errors
Rust backend:
- Tauri v2 app entry point with command registration
- SQLite database layer (rusqlite with bundled SQLite)
- Full schema: projects, media_files, speakers, segments, words,
ai_outputs, annotations (with indexes)
- Model structs with serde serialization
- CRUD queries for projects, speakers, segments, words
- Segment text editing preserves original text
- Schema versioning for future migrations
- 6 tests passing
- Command stubs for project, transcribe, export, AI, settings, system
- App state management
Python sidecar:
- JSON-line IPC protocol (stdin/stdout)
- Message types: IPCMessage, progress, error, ready
- Handler registry with routing and error handling
- Ping/pong handler for connectivity testing
- Service stubs: transcribe, diarize, pipeline, AI, export
- Provider stubs: local (llama-server), OpenAI, Anthropic, LiteLLM
- Hardware detection stubs
- 14 tests passing, ruff clean
Also adds:
- Testing strategy document (docs/TESTING.md)
- Validation script (scripts/validate.sh)
- Updated .gitignore for Svelte, Rust, Python artifacts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 15:16:06 -08:00
|
|
|
color: #e0e0e0;
|
2026-02-26 15:53:09 -08:00
|
|
|
padding: 0.4rem 0.8rem;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
font-size: 1rem;
|
|
|
|
|
}
|
2026-02-26 18:02:48 -08:00
|
|
|
.control-btn:hover:not(:disabled) {
|
2026-02-26 15:53:09 -08:00
|
|
|
background: #1a4a7a;
|
|
|
|
|
}
|
2026-02-26 18:02:48 -08:00
|
|
|
.control-btn:disabled {
|
|
|
|
|
opacity: 0.4;
|
|
|
|
|
cursor: not-allowed;
|
|
|
|
|
}
|
2026-02-26 15:53:09 -08:00
|
|
|
.play-btn {
|
|
|
|
|
padding: 0.4rem 1rem;
|
|
|
|
|
font-size: 1.2rem;
|
Phase 1 foundation: Tauri shell, Python sidecar, SQLite database
Tauri v2 + Svelte + TypeScript frontend:
- App shell with workspace layout (waveform, transcript, speakers, AI chat)
- Placeholder components for all major UI areas
- Typed stores (project, transcript, playback, AI)
- TypeScript interfaces matching the database schema
- Tauri bridge service with typed invoke wrappers
- svelte-check passes with 0 errors
Rust backend:
- Tauri v2 app entry point with command registration
- SQLite database layer (rusqlite with bundled SQLite)
- Full schema: projects, media_files, speakers, segments, words,
ai_outputs, annotations (with indexes)
- Model structs with serde serialization
- CRUD queries for projects, speakers, segments, words
- Segment text editing preserves original text
- Schema versioning for future migrations
- 6 tests passing
- Command stubs for project, transcribe, export, AI, settings, system
- App state management
Python sidecar:
- JSON-line IPC protocol (stdin/stdout)
- Message types: IPCMessage, progress, error, ready
- Handler registry with routing and error handling
- Ping/pong handler for connectivity testing
- Service stubs: transcribe, diarize, pipeline, AI, export
- Provider stubs: local (llama-server), OpenAI, Anthropic, LiteLLM
- Hardware detection stubs
- 14 tests passing, ruff clean
Also adds:
- Testing strategy document (docs/TESTING.md)
- Validation script (scripts/validate.sh)
- Updated .gitignore for Svelte, Rust, Python artifacts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 15:16:06 -08:00
|
|
|
}
|
2026-02-26 15:53:09 -08:00
|
|
|
.time {
|
|
|
|
|
color: #999;
|
Phase 1 foundation: Tauri shell, Python sidecar, SQLite database
Tauri v2 + Svelte + TypeScript frontend:
- App shell with workspace layout (waveform, transcript, speakers, AI chat)
- Placeholder components for all major UI areas
- Typed stores (project, transcript, playback, AI)
- TypeScript interfaces matching the database schema
- Tauri bridge service with typed invoke wrappers
- svelte-check passes with 0 errors
Rust backend:
- Tauri v2 app entry point with command registration
- SQLite database layer (rusqlite with bundled SQLite)
- Full schema: projects, media_files, speakers, segments, words,
ai_outputs, annotations (with indexes)
- Model structs with serde serialization
- CRUD queries for projects, speakers, segments, words
- Segment text editing preserves original text
- Schema versioning for future migrations
- 6 tests passing
- Command stubs for project, transcribe, export, AI, settings, system
- App state management
Python sidecar:
- JSON-line IPC protocol (stdin/stdout)
- Message types: IPCMessage, progress, error, ready
- Handler registry with routing and error handling
- Ping/pong handler for connectivity testing
- Service stubs: transcribe, diarize, pipeline, AI, export
- Provider stubs: local (llama-server), OpenAI, Anthropic, LiteLLM
- Hardware detection stubs
- 14 tests passing, ruff clean
Also adds:
- Testing strategy document (docs/TESTING.md)
- Validation script (scripts/validate.sh)
- Updated .gitignore for Svelte, Rust, Python artifacts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 15:16:06 -08:00
|
|
|
font-size: 0.875rem;
|
2026-02-26 15:53:09 -08:00
|
|
|
margin-left: auto;
|
|
|
|
|
font-variant-numeric: tabular-nums;
|
Phase 1 foundation: Tauri shell, Python sidecar, SQLite database
Tauri v2 + Svelte + TypeScript frontend:
- App shell with workspace layout (waveform, transcript, speakers, AI chat)
- Placeholder components for all major UI areas
- Typed stores (project, transcript, playback, AI)
- TypeScript interfaces matching the database schema
- Tauri bridge service with typed invoke wrappers
- svelte-check passes with 0 errors
Rust backend:
- Tauri v2 app entry point with command registration
- SQLite database layer (rusqlite with bundled SQLite)
- Full schema: projects, media_files, speakers, segments, words,
ai_outputs, annotations (with indexes)
- Model structs with serde serialization
- CRUD queries for projects, speakers, segments, words
- Segment text editing preserves original text
- Schema versioning for future migrations
- 6 tests passing
- Command stubs for project, transcribe, export, AI, settings, system
- App state management
Python sidecar:
- JSON-line IPC protocol (stdin/stdout)
- Message types: IPCMessage, progress, error, ready
- Handler registry with routing and error handling
- Ping/pong handler for connectivity testing
- Service stubs: transcribe, diarize, pipeline, AI, export
- Provider stubs: local (llama-server), OpenAI, Anthropic, LiteLLM
- Hardware detection stubs
- 14 tests passing, ruff clean
Also adds:
- Testing strategy document (docs/TESTING.md)
- Validation script (scripts/validate.sh)
- Updated .gitignore for Svelte, Rust, Python artifacts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 15:16:06 -08:00
|
|
|
}
|
|
|
|
|
</style>
|