Refactor project name from "Voice KB" to "Faerro KB" across all files and update related configurations

This commit is contained in:
2026-09-26 16:54:29 +02:00
parent 3034d76682
commit 13fcb70a27
10 changed files with 102 additions and 50 deletions
+50 -22
View File
@@ -1,9 +1,9 @@
const DB_NAME = 'voice-kb-local';
const DB_NAME = 'faerro-kb-local';
const STORE_NAME = 'recordings';
const MAX_RECORDING_MS = 30 * 60 * 1000;
const elements = Object.fromEntries([
'record-button', 'record-title', 'record-help', 'record-time', 'record-meter-fill', 'capture-message',
'queue-list', 'queue-empty', 'queue-count', 'sync-button', 'archive-list', 'archive-empty', 'search-input',
'queue-list', 'queue-empty', 'queue-count', 'sync-button', 'archive-list', 'archive-empty',
'network-state', 'date-label', 'storage-status',
].map((id) => [id, document.getElementById(id)]));
@@ -13,7 +13,6 @@ let activeStream;
let recordingChunks = [];
let recordingStartedAt = 0;
let recordingTimer;
let searchTimer;
let syncing = false;
function openDatabase() {
@@ -138,6 +137,7 @@ async function syncQueue() {
if (syncing || !navigator.onLine) return;
syncing = true;
elements['sync-button'].disabled = true;
elements['queue-list'].querySelectorAll('.delete-note').forEach((button) => { button.disabled = true; });
try {
const recordings = await getQueued();
for (const recording of recordings.sort((a, b) => a.createdAtMs - b.createdAtMs)) {
@@ -159,7 +159,7 @@ async function syncQueue() {
syncing = false;
elements['sync-button'].disabled = false;
await renderQueue();
await loadArchive(elements['search-input'].value.trim());
await loadAwaitingNotes();
}
}
@@ -191,18 +191,41 @@ async function renderQueue() {
const meta = document.createElement('div');
meta.className = 'item-meta';
meta.textContent = `${prettyDate(recording.createdAt)} · ${formatDuration(recording.durationMs)}`;
const remove = createDeleteButton(`Delete queued note ${recording.filename}`);
remove.disabled = syncing;
remove.addEventListener('click', async () => {
if (syncing) return;
remove.disabled = true;
try {
await deleteQueued(recording.id);
await renderQueue();
} catch {
setMessage('Could not remove the queued recording.', true);
remove.disabled = false;
}
});
top.append(name, status);
body.append(top, meta);
item.append(icon, body);
item.append(icon, body, remove);
elements['queue-list'].append(item);
}
}
function createDeleteButton(label) {
const button = document.createElement('button');
button.className = 'delete-note';
button.type = 'button';
button.textContent = '×';
button.title = label;
button.setAttribute('aria-label', label);
return button;
}
function renderArchive(notes) {
elements['archive-list'].replaceChildren();
elements['archive-empty'].hidden = notes.length > 0;
if (!navigator.onLine) elements['archive-empty'].textContent = 'Reconnect to browse or search stored notes.';
else elements['archive-empty'].textContent = elements['search-input'].value.trim() ? 'No matching transcripts.' : 'Your stored notes will appear here.';
if (!navigator.onLine) elements['archive-empty'].textContent = 'Reconnect to view notes awaiting transcription.';
else elements['archive-empty'].textContent = 'No notes awaiting transcription.';
for (const note of notes) {
const item = document.createElement('article');
item.className = 'archive-item';
@@ -230,8 +253,7 @@ function renderArchive(notes) {
method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ transcript: transcript.value }),
});
if (!response.ok) throw new Error('Save failed');
save.textContent = 'SAVED';
await loadArchive(elements['search-input'].value.trim());
await loadAwaitingNotes();
} catch {
save.textContent = 'RETRY SAVE';
} finally {
@@ -240,22 +262,32 @@ function renderArchive(notes) {
});
const status = document.createElement('div');
status.className = 'archive-status';
status.textContent = note.status.replaceAll('_', ' ').toUpperCase();
status.textContent = 'AWAITING TRANSCRIPTION';
const remove = createDeleteButton(`Delete note ${note.filename}`);
remove.addEventListener('click', async () => {
remove.disabled = true;
try {
const response = await fetch(`/api/notes/${encodeURIComponent(note.id)}`, { method: 'DELETE' });
if (!response.ok) throw new Error('Delete failed');
await loadAwaitingNotes();
} catch {
remove.disabled = false;
setMessage('Could not delete this note. Please try again.', true);
}
});
body.append(name, transcript, save, status);
item.append(date, body);
item.append(date, body, remove);
elements['archive-list'].append(item);
}
}
async function loadArchive(query = '') {
async function loadAwaitingNotes() {
if (!navigator.onLine) {
renderArchive([]);
return;
}
try {
const url = new URL('/api/notes', window.location.origin);
if (query) url.searchParams.set('q', query);
const response = await fetch(url);
const response = await fetch('/api/notes?status=awaiting_transcription');
if (!response.ok) throw new Error('Archive unavailable');
renderArchive(await response.json());
} catch {
@@ -267,17 +299,13 @@ async function loadArchive(query = '') {
elements['record-button'].addEventListener('click', () => mediaRecorder?.state === 'recording' ? stopRecording() : startRecording());
elements['sync-button'].addEventListener('click', syncQueue);
elements['search-input'].addEventListener('input', () => {
clearTimeout(searchTimer);
searchTimer = setTimeout(() => loadArchive(elements['search-input'].value.trim()), 250);
});
window.addEventListener('online', () => { updateNetwork(); syncQueue(); loadArchive(elements['search-input'].value.trim()); });
window.addEventListener('online', () => { updateNetwork(); syncQueue(); loadAwaitingNotes(); });
window.addEventListener('offline', updateNetwork);
window.addEventListener('focus', () => { updateNetwork(); syncQueue(); loadArchive(elements['search-input'].value.trim()); });
window.addEventListener('focus', () => { updateNetwork(); syncQueue(); loadAwaitingNotes(); });
document.addEventListener('visibilitychange', () => { if (document.visibilityState === 'visible') syncQueue(); });
elements['date-label'].textContent = new Intl.DateTimeFormat(undefined, { weekday: 'short', month: 'short', day: 'numeric' }).format(new Date()).toUpperCase();
updateNetwork();
renderQueue().then(syncQueue).catch(() => setMessage('Browser storage is unavailable. Check Safari storage settings.', true));
loadArchive();
loadAwaitingNotes();
if ('serviceWorker' in navigator) navigator.serviceWorker.register('/sw.js').catch(() => {});