feat/frontend-phase1 #1

Merged
stwhite merged 34 commits from feat/frontend-phase1 into main 2025-08-14 15:44:25 +00:00
3 changed files with 60 additions and 4 deletions
Showing only changes of commit c9593fe6cc - Show all commits

View File

@ -68,6 +68,14 @@ async def _start_model_reaper():
except Exception:
logger.exception("Model reaper encountered an error")
# Log eviction configuration at startup
logger.info(
"Model Eviction -> enabled: %s | idle_timeout: %ss | check_interval: %ss",
getattr(config, "MODEL_EVICTION_ENABLED", True),
getattr(config, "MODEL_IDLE_TIMEOUT_SECONDS", 0),
getattr(config, "MODEL_IDLE_CHECK_INTERVAL_SECONDS", 60),
)
app.state._model_reaper_task = asyncio.create_task(reaper())

View File

@ -14,6 +14,14 @@ if __name__ == "__main__":
print(f"CORS Origins: {config.CORS_ORIGINS}")
print(f"Project Root: {config.PROJECT_ROOT}")
print(f"Device: {config.DEVICE}")
# Idle eviction settings
print(
"Model Eviction -> enabled: {} | idle_timeout: {}s | check_interval: {}s".format(
getattr(config, "MODEL_EVICTION_ENABLED", True),
getattr(config, "MODEL_IDLE_TIMEOUT_SECONDS", 0),
getattr(config, "MODEL_IDLE_CHECK_INTERVAL_SECONDS", 60),
)
)
uvicorn.run(
"app.main:app",

View File

@ -1,6 +1,10 @@
import { getSpeakers, addSpeaker, deleteSpeaker, generateDialog } from './api.js';
import { API_BASE_URL, API_BASE_URL_FOR_FILES } from './config.js';
// Shared per-line audio playback state to prevent overlapping playback
let currentLineAudio = null;
let currentLineAudioBtn = null;
// --- Global Inline Notification Helpers --- //
const noticeEl = document.getElementById('global-notice');
const noticeContentEl = document.getElementById('global-notice-content');
@ -399,10 +403,46 @@ async function initializeDialogEditor() {
playBtn.disabled = !item.audioUrl;
playBtn.onclick = () => {
if (!item.audioUrl) return;
let audioUrl = item.audioUrl.startsWith('http') ? item.audioUrl : `${API_BASE_URL_FOR_FILES}${item.audioUrl}`;
// Use a shared audio element or create one per play
let audio = new window.Audio(audioUrl);
audio.play();
const audioUrl = item.audioUrl.startsWith('http') ? item.audioUrl : `${API_BASE_URL_FOR_FILES}${item.audioUrl}`;
// If something is already playing
if (currentLineAudio && !currentLineAudio.paused) {
if (currentLineAudioBtn === playBtn) {
// Same line: ignore click to prevent overlapping
return;
}
// Stop previous audio and re-enable its button
try {
currentLineAudio.pause();
currentLineAudio.currentTime = 0;
} catch (e) { /* noop */ }
if (currentLineAudioBtn) {
try { currentLineAudioBtn.disabled = false; } catch (e) { /* detached */ }
}
}
const audio = new window.Audio(audioUrl);
currentLineAudio = audio;
currentLineAudioBtn = playBtn;
// Disable this play button while playing
playBtn.disabled = true;
const clearState = () => {
if (currentLineAudio === audio) {
currentLineAudio = null;
currentLineAudioBtn = null;
}
try { playBtn.disabled = false; } catch (e) { /* detached */ }
};
audio.addEventListener('ended', clearState, { once: true });
audio.addEventListener('error', clearState, { once: true });
audio.play().catch(err => {
console.error('Audio play failed:', err);
clearState();
showNotice('Could not play audio.', 'error', { timeout: 2000 });
});
};
actionsTd.appendChild(playBtn);