fix(kon): rename ramble refs, fix export extensions, use shared constants in DictationPage

- Rename custom event from ramble:toggle-recording to kon:toggle-recording
- Change download filename prefix from ramble- to kon-
- Fix export extension mapping: csv and html formats now produce correct file extensions instead of falling through to .txt
- Add console.warn when PCM buffer exceeds 5-minute cap and gets truncated
- Replace magic numbers with shared constants (MAX_PCM_SAMPLES, MIN_CHUNK_SAMPLES, CHUNK_INTERVAL_MS, FEEDBACK_TIMEOUT_MS)
- Use shared pad() utility from time.js instead of inline reimplementation
- Remove unused saveTasks import
- Simplify redundant 3-branch append logic to 2 branches with clarifying comment
- Use $derived.by to avoid double transcript.trim() in wordCount

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
jake
2026-03-17 00:16:55 +00:00
parent c5b2767ad8
commit 4fa20255ae

View File

@@ -2,11 +2,13 @@
import { onMount, onDestroy } from "svelte"; import { onMount, onDestroy } from "svelte";
import { invoke } from "@tauri-apps/api/core"; import { invoke } from "@tauri-apps/api/core";
import { listen } from "@tauri-apps/api/event"; import { listen } from "@tauri-apps/api/event";
import { page, settings, templates, addToHistory, addTask, tasks, saveTasks } from "$lib/stores/page.svelte.js"; import { page, settings, templates, addToHistory, addTask, tasks } from "$lib/stores/page.svelte.js";
import Card from "$lib/components/Card.svelte"; import Card from "$lib/components/Card.svelte";
import ModelDownloader from "$lib/components/ModelDownloader.svelte"; import ModelDownloader from "$lib/components/ModelDownloader.svelte";
import { exportTranscript } from "$lib/utils/export.js"; import { exportTranscript } from "$lib/utils/export.js";
import { extractTasks } from "$lib/utils/taskExtractor.js"; import { extractTasks } from "$lib/utils/taskExtractor.js";
import { pad } from "$lib/utils/time.js";
import { MAX_PCM_SAMPLES, MIN_CHUNK_SAMPLES, CHUNK_INTERVAL_MS, FEEDBACK_TIMEOUT_MS } from "$lib/utils/constants.js";
let transcript = $state(""); let transcript = $state("");
let segments = $state([]); let segments = $state([]);
@@ -62,14 +64,14 @@
handleResult(result); handleResult(result);
}); });
window.addEventListener("ramble:toggle-recording", hotkeyHandler); window.addEventListener("kon:toggle-recording", hotkeyHandler);
await checkModelState(); await checkModelState();
}); });
onDestroy(() => { onDestroy(() => {
if (unlisten) unlisten(); if (unlisten) unlisten();
window.removeEventListener("ramble:toggle-recording", hotkeyHandler); window.removeEventListener("kon:toggle-recording", hotkeyHandler);
clearInterval(timerInterval); clearInterval(timerInterval);
clearInterval(chunkTimer); clearInterval(chunkTimer);
if (page.recording) { if (page.recording) {
@@ -106,9 +108,9 @@
}); });
} else { } else {
// Append mode // Append mode
if (transcript && result.chunk_id > 1) { // First chunk (id=1) starts the transcript; subsequent chunks append to it.
transcript += " " + text; // When transcript is empty, assign directly; otherwise prepend a space separator.
} else if (!transcript) { if (!transcript) {
transcript = text; transcript = text;
} else { } else {
transcript += " " + text; transcript += " " + text;
@@ -247,7 +249,7 @@
page.status = "Recording..."; page.status = "Recording...";
page.statusColor = "#e87171"; page.statusColor = "#e87171";
timerInterval = setInterval(updateTimer, 1000); timerInterval = setInterval(updateTimer, 1000);
chunkTimer = setInterval(sendChunk, 3000); chunkTimer = setInterval(sendChunk, CHUNK_INTERVAL_MS);
} catch (err) { } catch (err) {
error = `Microphone access denied: ${err.message}`; error = `Microphone access denied: ${err.message}`;
page.status = "Error"; page.status = "Error";
@@ -294,15 +296,16 @@
} }
async function sendChunk() { async function sendChunk() {
if (pcmBuffer.length < 8000) return; if (pcmBuffer.length < MIN_CHUNK_SAMPLES) return;
if (transcribing) return; if (transcribing) return;
chunkId++; chunkId++;
const currentChunkId = chunkId; const currentChunkId = chunkId;
const samples = [...pcmBuffer]; const samples = [...pcmBuffer];
pcmBuffer = []; pcmBuffer = [];
if (samples.length > 4_800_000) { if (samples.length > MAX_PCM_SAMPLES) {
samples.length = 4_800_000; console.warn(`PCM buffer truncated from ${samples.length} to ${MAX_PCM_SAMPLES} samples (~5 min at 16 kHz)`);
samples.length = MAX_PCM_SAMPLES;
} }
transcribing = true; transcribing = true;
@@ -404,9 +407,7 @@
function updateTimer() { function updateTimer() {
const elapsed = Math.floor((Date.now() - startTime) / 1000); const elapsed = Math.floor((Date.now() - startTime) / 1000);
const mins = Math.floor(elapsed / 60).toString().padStart(2, "0"); page.timerText = `${pad(Math.floor(elapsed / 60))}:${pad(elapsed % 60)}`;
const secs = (elapsed % 60).toString().padStart(2, "0");
page.timerText = `${mins}:${secs}`;
} }
function copyAll() { function copyAll() {
@@ -427,12 +428,13 @@
if (!transcript) return; if (!transcript) return;
showExportMenu = false; showExportMenu = false;
const content = exportTranscript(transcript, segments, format); const content = exportTranscript(transcript, segments, format);
const ext = format === "vtt" ? "vtt" : format === "srt" ? "srt" : format === "md" ? "md" : "txt"; const extMap = { vtt: "vtt", srt: "srt", md: "md", csv: "csv", html: "html" };
const ext = extMap[format] || "txt";
const blob = new Blob([content], { type: "text/plain" }); const blob = new Blob([content], { type: "text/plain" });
const url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
const a = document.createElement("a"); const a = document.createElement("a");
a.href = url; a.href = url;
a.download = `ramble-${new Date().toISOString().slice(0, 10)}.${ext}`; a.download = `kon-${new Date().toISOString().slice(0, 10)}.${ext}`;
a.click(); a.click();
URL.revokeObjectURL(url); URL.revokeObjectURL(url);
} }
@@ -484,14 +486,15 @@
language: settings.language, language: settings.language,
}); });
saved = true; saved = true;
setTimeout(() => { saved = false; }, 3000); setTimeout(() => { saved = false; }, FEEDBACK_TIMEOUT_MS);
} }
let taskCount = $derived(tasks.filter((t) => !t.done).length); let taskCount = $derived(tasks.filter((t) => !t.done).length);
let wordCount = $derived( let wordCount = $derived.by(() => {
transcript.trim() ? transcript.trim().split(/\s+/).length : 0 const trimmed = transcript.trim();
); return trimmed ? trimmed.split(/\s+/).length : 0;
});
</script> </script>
<div class="flex flex-col h-full animate-fade-in"> <div class="flex flex-col h-full animate-fade-in">