Improve live recordings UI, upload flow, and default Flux theme.

Refresh list status over Reverb with polling fallback, clarify queued vs processing, streamline upload empty states, and seed an admin login.
This commit is contained in:
ben
2026-08-12 20:14:35 +02:00
parent 148ba91816
commit c17f8fb506
28 changed files with 523 additions and 333 deletions
+5 -70
View File
@@ -5,7 +5,7 @@
const BADGE_COLORS = {
done: 'teal',
processing: 'amber',
pending: 'amber',
pending: 'zinc',
failed: 'red',
cancelled: 'zinc',
};
@@ -55,12 +55,6 @@ export function formatTimestamp(value) {
+ ':' + pad(date.getMinutes());
}
function formatWordCount(count) {
const n = Number(count) || 0;
return n > 0 ? n.toLocaleString() : '—';
}
function subscribeToRecording(recordingId, handler) {
if (!window.Echo) {
return () => {};
@@ -76,14 +70,6 @@ function subscribeToRecording(recordingId, handler) {
};
}
function subscribeToRecordings(recordingIds, handler) {
const leaveFns = recordingIds.map((id) => subscribeToRecording(id, handler));
return () => {
leaveFns.forEach((leave) => leave());
};
}
/**
* Show-page Alpine component: Echo push + local elapsed tick + optional status hydrate.
*/
@@ -208,34 +194,19 @@ export function transcriptionMonitor({ statusUrl, initial }) {
}
/**
* Index-page Alpine component: patch row status from Reverb events.
* Index-page Alpine component: inline audio player only.
* Status/progress refresh via Livewire Echo + wire:poll.
*/
export function recordingsIndex({ recordings, pendingCount }) {
const byId = {};
for (const row of recordings) {
byId[row.id] = row;
}
export function recordingsIndex() {
return {
rows: byId,
pendingCount: Number(pendingCount) || 0,
leaveChannel: null,
playingId: null,
isPlaying: false,
start() {
this.leaveChannel = subscribeToRecordings(Object.keys(this.rows), (event) => {
this.applyPayload(event);
});
//
},
destroy() {
if (this.leaveChannel) {
this.leaveChannel();
this.leaveChannel = null;
}
const player = this.$refs.player;
if (player) {
@@ -282,41 +253,5 @@ export function recordingsIndex({ recordings, pendingCount }) {
this.isPlaying = false;
});
},
applyPayload(payload) {
const id = payload.id;
if (!this.rows[id]) {
return;
}
const previousStatus = this.rows[id].status;
const next = {
...this.rows[id],
status: payload.status,
status_label: payload.status_label,
progress: payload.progress,
percent: payload.percent,
is_active: payload.is_active,
word_count: payload.word_count ?? this.rows[id].word_count,
word_count_display: formatWordCount(payload.word_count ?? this.rows[id].word_count),
badge_color: badgeColorFor(payload.status),
};
this.rows[id] = next;
const wasQueueable = ['pending', 'failed', 'cancelled'].includes(previousStatus);
const isQueueable = ['pending', 'failed', 'cancelled'].includes(payload.status);
if (wasQueueable && !isQueueable) {
this.pendingCount = Math.max(0, this.pendingCount - 1);
} else if (!wasQueueable && isQueueable) {
this.pendingCount += 1;
}
},
row(id) {
return this.rows[id] || {};
},
};
}