Files
local-transcription/src/lib/stores/transcriptions.svelte.ts
Developer a6c7eb5d5e
All checks were successful
Release / Bump version and tag (push) Successful in 7s
Fix blank screen: rename stores to .svelte.ts for rune support
Svelte 5 runes ($state, $derived, $effect) are only compiled in
.svelte and .svelte.ts files. The stores used runes in plain .ts
files, which meant $state was treated as an undefined function at
runtime, crashing the JS before anything rendered.

- Renamed backend.ts -> backend.svelte.ts
- Renamed config.ts -> config.svelte.ts
- Renamed transcriptions.ts -> transcriptions.svelte.ts
- Added .svelte.ts to Vite resolve extensions
- Added missing obsUrl/syncUrl getters to backend store

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 19:15:52 -07:00

110 lines
2.5 KiB
TypeScript

/**
* Transcriptions store - manages the list of transcription items
* received from the backend via WebSocket.
*/
export interface TranscriptionItem {
id: string;
text: string;
userName: string;
timestamp: string;
isPreview: boolean;
}
let items = $state<TranscriptionItem[]>([]);
let nextId = 0;
function generateId(): string {
return `t-${Date.now()}-${nextId++}`;
}
function addTranscription(data: {
text?: string;
user_name?: string;
timestamp?: string;
}) {
// When a final transcription arrives, remove any existing preview
const previewIndex = items.findIndex((item) => item.isPreview);
if (previewIndex !== -1) {
items.splice(previewIndex, 1);
}
items.push({
id: generateId(),
text: data.text ?? "",
userName: data.user_name ?? "",
timestamp: data.timestamp ?? "",
isPreview: false,
});
// Keep a reasonable limit
if (items.length > 500) {
items.splice(0, items.length - 500);
}
}
function setPreview(data: {
text?: string;
user_name?: string;
timestamp?: string;
}) {
const existingIndex = items.findIndex((item) => item.isPreview);
const previewItem: TranscriptionItem = {
id: existingIndex !== -1 ? items[existingIndex].id : generateId(),
text: data.text ?? "",
userName: data.user_name ?? "",
timestamp: data.timestamp ?? "",
isPreview: true,
};
if (existingIndex !== -1) {
items[existingIndex] = previewItem;
} else {
items.push(previewItem);
}
}
function clearAll() {
items.length = 0;
}
function getPlainText(): string {
return items
.filter((item) => !item.isPreview)
.map((item) => {
let line = "";
if (item.timestamp) line += `[${item.timestamp}] `;
if (item.userName) line += `${item.userName}: `;
line += item.text;
return line;
})
.join("\n");
}
// Listen for backend events
if (typeof window !== "undefined") {
window.addEventListener("backend:transcription", ((e: CustomEvent) => {
addTranscription(e.detail);
}) as EventListener);
window.addEventListener("backend:preview", ((e: CustomEvent) => {
setPreview(e.detail);
}) as EventListener);
}
export const transcriptionStore = {
get items() {
return items;
},
get currentPreview(): TranscriptionItem | null {
return items.find((item) => item.isPreview) ?? null;
},
get transcriptions(): TranscriptionItem[] {
return items.filter((item) => !item.isPreview);
},
addTranscription,
setPreview,
clearAll,
getPlainText,
};