- Change default transcription mode from local to byok (cloud/Deepgram) - Move Transcription Mode selector to top of settings for visibility - Hide local-only settings (model, VAD, timing) when cloud mode selected - Disable Start button until API key (byok) or login (managed) is configured - Add room creation and share code flow to Shared Captions section - Add POST /api/create-room endpoint to Node.js sync server - Update default sync URL placeholder to caption.shadowdao.com Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
173 lines
4.7 KiB
Svelte
173 lines
4.7 KiB
Svelte
<script lang="ts">
|
|
import { backendStore } from "$lib/stores/backend";
|
|
import { configStore } from "$lib/stores/config";
|
|
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);
|
|
|
|
let remoteMode = $derived(configStore.config.remote.mode);
|
|
let byokApiKey = $derived(configStore.config.remote.byok_api_key);
|
|
let authToken = $derived(configStore.config.remote.auth_token);
|
|
|
|
let cloudConfigured = $derived(
|
|
remoteMode === "local" ||
|
|
(remoteMode === "byok" && byokApiKey.trim() !== "") ||
|
|
(remoteMode === "managed" && authToken.trim() !== "")
|
|
);
|
|
|
|
let errorMessage = $state("");
|
|
|
|
async function toggleTranscription() {
|
|
if (isLoading) return;
|
|
isLoading = true;
|
|
errorMessage = "";
|
|
try {
|
|
if (isTranscribing) {
|
|
await backendStore.apiPost("/api/stop");
|
|
} else {
|
|
await backendStore.apiPost("/api/start");
|
|
}
|
|
} catch (err: unknown) {
|
|
const msg = err instanceof Error ? err.message : String(err);
|
|
// Ignore "Already transcribing/not transcribing" -- just sync the state
|
|
if (!msg.includes("400")) {
|
|
console.error("Failed to toggle transcription:", msg);
|
|
errorMessage = msg;
|
|
}
|
|
} finally {
|
|
// Always poll status to sync UI with actual backend state,
|
|
// even if the API call failed (e.g. "Already transcribing")
|
|
await backendStore.pollStatus();
|
|
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 || !cloudConfigured}
|
|
>
|
|
{#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>
|
|
|
|
{#if errorMessage}
|
|
<span class="error-msg">{errorMessage}</span>
|
|
{/if}
|
|
|
|
{#if !cloudConfigured && isReady}
|
|
<div class="cloud-warning">
|
|
{#if remoteMode === "byok"}
|
|
<span>API key required. Get one at
|
|
<a href="https://console.deepgram.com" target="_blank" rel="noopener">console.deepgram.com</a>,
|
|
then enter it in Settings.</span>
|
|
{:else if remoteMode === "managed"}
|
|
<span>Login required. Open Settings to log in.</span>
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
|
|
<style>
|
|
.error-msg {
|
|
color: #f44336;
|
|
font-size: 12px;
|
|
margin-left: 8px;
|
|
}
|
|
|
|
.cloud-warning {
|
|
font-size: 12px;
|
|
color: #ff9800;
|
|
margin-left: 8px;
|
|
flex: 1;
|
|
}
|
|
|
|
.cloud-warning a {
|
|
color: #4fc3f7;
|
|
text-decoration: underline;
|
|
}
|
|
|
|
.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>
|