2026-04-06 13:42:31 -07:00
|
|
|
<script lang="ts">
|
|
|
|
|
import { backendStore } from "$lib/stores/backend";
|
|
|
|
|
import { transcriptionStore } from "$lib/stores/transcriptions";
|
|
|
|
|
|
|
|
|
|
let isTranscribing = $derived(backendStore.appState === "transcribing");
|
|
|
|
|
let isReady = $derived(
|
|
|
|
|
backendStore.appState === "ready" || backendStore.appState === "transcribing"
|
|
|
|
|
);
|
|
|
|
|
let isLoading = $state(false);
|
|
|
|
|
|
|
|
|
|
async function toggleTranscription() {
|
|
|
|
|
if (isLoading) return;
|
|
|
|
|
isLoading = true;
|
|
|
|
|
try {
|
|
|
|
|
if (isTranscribing) {
|
|
|
|
|
await backendStore.apiPost("/api/stop");
|
|
|
|
|
} else {
|
|
|
|
|
await backendStore.apiPost("/api/start");
|
|
|
|
|
}
|
2026-04-08 07:25:55 -07:00
|
|
|
// Poll status to update UI immediately instead of waiting
|
|
|
|
|
// for WebSocket broadcast (which can be delayed or missed)
|
|
|
|
|
await backendStore.pollStatus();
|
2026-04-06 13:42:31 -07:00
|
|
|
} catch (err) {
|
|
|
|
|
console.error("Failed to toggle transcription:", err);
|
|
|
|
|
} finally {
|
|
|
|
|
isLoading = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function clearTranscriptions() {
|
|
|
|
|
try {
|
|
|
|
|
await backendStore.apiPost("/api/clear");
|
|
|
|
|
transcriptionStore.clearAll();
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error("Failed to clear:", err);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function saveTranscriptions() {
|
|
|
|
|
try {
|
|
|
|
|
// Get transcription text from backend or local store
|
|
|
|
|
let text: string;
|
|
|
|
|
try {
|
|
|
|
|
const data = await backendStore.apiGet<{ text: string }>("/api/transcriptions");
|
|
|
|
|
text = data.text || transcriptionStore.getPlainText();
|
|
|
|
|
} catch {
|
|
|
|
|
text = transcriptionStore.getPlainText();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!text.trim()) {
|
|
|
|
|
console.warn("No transcriptions to save");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Try Tauri dialog for native save, fall back to browser download
|
|
|
|
|
try {
|
|
|
|
|
const { save } = await import("@tauri-apps/plugin-dialog");
|
|
|
|
|
const filePath = await save({
|
|
|
|
|
defaultPath: "transcription.txt",
|
|
|
|
|
filters: [
|
|
|
|
|
{ name: "Text Files", extensions: ["txt"] },
|
|
|
|
|
{ name: "All Files", extensions: ["*"] },
|
|
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
if (filePath) {
|
|
|
|
|
// Write via backend API
|
|
|
|
|
await backendStore.apiPost("/api/save-file", { path: filePath, text });
|
|
|
|
|
}
|
|
|
|
|
} catch {
|
|
|
|
|
// Fallback: browser-style download
|
|
|
|
|
const blob = new Blob([text], { type: "text/plain" });
|
|
|
|
|
const url = URL.createObjectURL(blob);
|
|
|
|
|
const a = document.createElement("a");
|
|
|
|
|
a.href = url;
|
|
|
|
|
a.download = "transcription.txt";
|
|
|
|
|
a.click();
|
|
|
|
|
URL.revokeObjectURL(url);
|
|
|
|
|
}
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error("Failed to save:", err);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<div class="controls">
|
|
|
|
|
<button
|
|
|
|
|
class={isTranscribing ? "danger" : "primary"}
|
|
|
|
|
onclick={toggleTranscription}
|
|
|
|
|
disabled={!isReady || isLoading}
|
|
|
|
|
>
|
|
|
|
|
{#if isLoading}
|
|
|
|
|
...
|
|
|
|
|
{:else if isTranscribing}
|
|
|
|
|
Stop Transcription
|
|
|
|
|
{:else}
|
|
|
|
|
Start Transcription
|
|
|
|
|
{/if}
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
|
|
<button onclick={clearTranscriptions} disabled={!backendStore.connected}>
|
|
|
|
|
Clear
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
|
|
<button onclick={saveTranscriptions} disabled={!backendStore.connected}>
|
|
|
|
|
Save
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<style>
|
|
|
|
|
.controls {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
padding: 10px 20px;
|
|
|
|
|
background-color: var(--bg-secondary);
|
|
|
|
|
border-top: 1px solid var(--border-color);
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
}
|
|
|
|
|
</style>
|