From 212c8426e1e3269d2f704ff86adbd927b310eda1 Mon Sep 17 00:00:00 2001 From: remsky Date: Wed, 5 Feb 2025 22:53:21 -0700 Subject: [PATCH] Update Dockerfile for GPU compatibility, enhance button loading state, and improve responsive styles --- docker/gpu/Dockerfile | 2 +- web/index.html | 91 +++--- web/src/App.js | 4 +- web/src/components/VoiceSelector.js | 85 +++-- web/src/components/WaveVisualizer.js | 8 +- web/styles/badges.css | 57 +++- web/styles/base.css | 38 ++- web/styles/controls.css | 358 +++++++++++++++++++++ web/styles/forms.css | 450 +++------------------------ web/styles/header.css | 86 ++--- web/styles/layout.css | 163 +++++----- web/styles/player.css | 225 +++++++++++++- web/styles/responsive.css | 111 ++++--- 13 files changed, 980 insertions(+), 698 deletions(-) create mode 100644 web/styles/controls.css diff --git a/docker/gpu/Dockerfile b/docker/gpu/Dockerfile index 737d811..99f3276 100644 --- a/docker/gpu/Dockerfile +++ b/docker/gpu/Dockerfile @@ -1,4 +1,4 @@ -FROM --platform=$BUILDPLATFORM nvidia/cuda:12.3.2-cudnn9-runtime-ubuntu22.04 +FROM --platform=$BUILDPLATFORM nvidia/cuda:12.1.0-runtime-ubuntu22.04 # Set non-interactive frontend ENV DEBIAN_FRONTEND=noninteractive diff --git a/web/index.html b/web/index.html index 3bb9a36..ecc0f88 100644 --- a/web/index.html +++ b/web/index.html @@ -15,6 +15,7 @@ + @@ -28,36 +29,60 @@ HexGrad/Kokoro-82M on Hugging Face +
+

FastKoko

+
+
+
+
+
Kokoro-FastAPI
-
-
-

FastKoko

-
-
-
+
+
+
+
+
+ + +
+ + + + +
+ 0:00 +
+
+
+
+
+
+ + + + +
+
-

Kokoro-FastAPI TTS System

-
- -
-
- -
-
- +
+ +
+
+ +
@@ -92,30 +117,6 @@
-
-
- - -
- - - - -
- 0:00 -
-
-
-
-
-
- - - - -
-
-
diff --git a/web/src/App.js b/web/src/App.js index 4b6c3ed..7ab3d7f 100644 --- a/web/src/App.js +++ b/web/src/App.js @@ -138,7 +138,7 @@ export class App { setGenerating(isGenerating) { this.playerState.setGenerating(isGenerating); this.elements.generateBtn.disabled = isGenerating; - this.elements.generateBtn.className = isGenerating ? 'loading' : ''; + this.elements.generateBtn.classList.toggle('loading', isGenerating); this.elements.generateBtnLoader.style.display = isGenerating ? 'block' : 'none'; this.elements.generateBtnText.style.visibility = isGenerating ? 'hidden' : 'visible'; this.elements.cancelBtn.style.display = isGenerating ? 'block' : 'none'; @@ -227,4 +227,4 @@ export class App { // Initialize app when DOM is loaded document.addEventListener('DOMContentLoaded', () => { new App(); -}); \ No newline at end of file +}); diff --git a/web/src/components/VoiceSelector.js b/web/src/components/VoiceSelector.js index 4499d2f..65ce3b6 100644 --- a/web/src/components/VoiceSelector.js +++ b/web/src/components/VoiceSelector.js @@ -12,25 +12,45 @@ export class VoiceSelector { } setupEventListeners() { + // Voice search focus + this.elements.voiceSearch.addEventListener('focus', () => { + this.elements.voiceDropdown.classList.add('show'); + }); + // Voice search this.elements.voiceSearch.addEventListener('input', (e) => { const filteredVoices = this.voiceService.filterVoices(e.target.value); this.renderVoiceOptions(filteredVoices); }); - // Voice selection - this.elements.voiceOptions.addEventListener('change', (e) => { - if (e.target.type === 'checkbox') { - if (e.target.checked) { - this.voiceService.addVoice(e.target.value); - } else { - this.voiceService.removeVoice(e.target.value); - } - this.updateSelectedVoicesDisplay(); + // Voice selection - handle clicks on the entire voice option + this.elements.voiceOptions.addEventListener('mousedown', (e) => { + e.preventDefault(); // Prevent blur on search input + + const voiceOption = e.target.closest('.voice-option'); + if (!voiceOption) return; + + const voice = voiceOption.dataset.voice; + if (!voice) return; + + const isSelected = voiceOption.classList.contains('selected'); + + if (!isSelected) { + this.voiceService.addVoice(voice); + } else { + this.voiceService.removeVoice(voice); } + + voiceOption.classList.toggle('selected'); + this.updateSelectedVoicesDisplay(); + + // Keep focus on search input + requestAnimationFrame(() => { + this.elements.voiceSearch.focus(); + }); }); - // Weight adjustment and voice removal + // Weight adjustment this.elements.selectedVoices.addEventListener('input', (e) => { if (e.target.type === 'number') { const voice = e.target.dataset.voice; @@ -47,24 +67,30 @@ export class VoiceSelector { // Remove selected voice this.elements.selectedVoices.addEventListener('click', (e) => { if (e.target.classList.contains('remove-voice')) { + e.preventDefault(); + e.stopPropagation(); const voice = e.target.dataset.voice; this.voiceService.removeVoice(voice); - this.updateVoiceCheckbox(voice, false); + this.updateVoiceOptionState(voice, false); this.updateSelectedVoicesDisplay(); } }); - // Dropdown visibility - this.elements.voiceSearch.addEventListener('focus', () => { - this.elements.voiceDropdown.style.display = 'block'; - this.updateSearchPlaceholder(); - }); - - document.addEventListener('click', (e) => { - if (!this.elements.voiceSearch.contains(e.target) && - !this.elements.voiceDropdown.contains(e.target)) { - this.elements.voiceDropdown.style.display = 'none'; + // Handle clicks outside to close dropdown + document.addEventListener('mousedown', (e) => { + // Don't handle clicks in selected voices area + if (this.elements.selectedVoices.contains(e.target)) { + return; } + + // Don't close if clicking in search or dropdown + if (this.elements.voiceSearch.contains(e.target) || + this.elements.voiceDropdown.contains(e.target)) { + return; + } + + this.elements.voiceDropdown.classList.remove('show'); + this.elements.voiceSearch.blur(); }); this.elements.voiceSearch.addEventListener('blur', () => { @@ -77,11 +103,10 @@ export class VoiceSelector { renderVoiceOptions(voices) { this.elements.voiceOptions.innerHTML = voices .map(voice => ` -