summaryrefslogtreecommitdiffstats
path: root/ui/renderer.js
diff options
context:
space:
mode:
Diffstat (limited to 'ui/renderer.js')
-rw-r--r--ui/renderer.js249
1 files changed, 249 insertions, 0 deletions
diff --git a/ui/renderer.js b/ui/renderer.js
new file mode 100644
index 0000000..83c652c
--- /dev/null
+++ b/ui/renderer.js
@@ -0,0 +1,249 @@
+// Handle status messages
+function showStatus(message, type = 'info') {
+ const statusEl = document.getElementById('status-message');
+ statusEl.textContent = message;
+ statusEl.classList.remove('hidden', 'bg-green-100', 'bg-red-100', 'bg-blue-100', 'text-green-800', 'text-red-800', 'text-blue-800');
+
+ if (type === 'success') {
+ statusEl.classList.add('bg-green-100', 'text-green-800');
+ } else if (type === 'error') {
+ statusEl.classList.add('bg-red-100', 'text-red-800');
+ } else {
+ statusEl.classList.add('bg-blue-100', 'text-blue-800');
+ }
+
+ // Also log to console
+ appendToConsole(message, type === 'error' ? 'stderr' : 'info');
+
+ setTimeout(() => {
+ statusEl.classList.add('hidden');
+ }, 5000);
+}
+
+// Get form values
+function getFormValues() {
+ return {
+ compute_type: document.getElementById('compute_type').value,
+ enable_debug_mode: document.getElementById('enable_debug_mode').checked ? 1 : 0,
+ enable_previews: document.getElementById('enable_previews').checked ? 1 : 0,
+ language: document.getElementById('language').value,
+ gpu_idx: parseInt(document.getElementById('gpu_idx').value),
+ max_speech_duration_s: parseInt(document.getElementById('max_speech_duration_s').value),
+ min_silence_duration_ms: parseInt(document.getElementById('min_silence_duration_ms').value),
+ microphone: document.getElementById('microphone').value,
+ model: document.getElementById('model').value,
+ reset_after_silence_s: parseInt(document.getElementById('reset_after_silence_s').value),
+ transcription_loop_delay_ms: parseInt(document.getElementById('transcription_loop_delay_ms').value),
+ use_cpu: document.getElementById('use_cpu').checked ? 1 : 0,
+ block_width: parseInt(document.getElementById('block_width').value),
+ num_blocks: parseInt(document.getElementById('num_blocks').value),
+ rows: parseInt(document.getElementById('rows').value),
+ cols: parseInt(document.getElementById('cols').value)
+ };
+}
+
+// Add a flag to prevent auto-save during programmatic updates
+let isSettingValues = false;
+
+// Set form values
+function setFormValues(config) {
+ isSettingValues = true; // Disable auto-save temporarily
+
+ document.getElementById('compute_type').value = config.compute_type || 'int8';
+ document.getElementById('enable_debug_mode').checked = config.enable_debug_mode === 1;
+ document.getElementById('enable_previews').checked = config.enable_previews === 1;
+ document.getElementById('language').value = config.language || 'english';
+ document.getElementById('gpu_idx').value = config.gpu_idx || 0;
+ document.getElementById('max_speech_duration_s').value = config.max_speech_duration_s || 10;
+ document.getElementById('min_silence_duration_ms').value = config.min_silence_duration_ms || 250;
+ document.getElementById('microphone').value = config.microphone || 'motu';
+ document.getElementById('model').value = config.model || 'turbo';
+ document.getElementById('reset_after_silence_s').value = config.reset_after_silence_s || 15;
+ document.getElementById('transcription_loop_delay_ms').value = config.transcription_loop_delay_ms || 100;
+ document.getElementById('use_cpu').checked = config.use_cpu === 1;
+ document.getElementById('block_width').value = config.block_width || 2;
+ document.getElementById('num_blocks').value = config.num_blocks || 40;
+ document.getElementById('rows').value = config.rows || 10;
+ document.getElementById('cols').value = config.cols || 24;
+
+ isSettingValues = false; // Re-enable auto-save
+}
+
+// Toggle advanced settings
+document.getElementById('toggle-advanced').addEventListener('click', () => {
+ const advancedSettings = document.getElementById('advanced-settings');
+ const chevron = document.getElementById('chevron');
+
+ if (advancedSettings.classList.contains('hidden')) {
+ advancedSettings.classList.remove('hidden');
+ chevron.classList.add('rotate-90');
+ } else {
+ advancedSettings.classList.add('hidden');
+ chevron.classList.remove('rotate-90');
+ }
+});
+
+// Simplify button handlers by extracting common patterns
+async function handleAsyncAction(actionName, actionFn) {
+ try {
+ const result = await actionFn();
+ if (result && result.message) {
+ showStatus(result.message, 'success');
+ }
+ return result;
+ } catch (error) {
+ showStatus(`${actionName} failed: ${error.message}`, 'error');
+ throw error;
+ }
+}
+
+// Auto-save functionality with debouncing
+let saveTimeout;
+const SAVE_DELAY = 500; // milliseconds
+
+async function autoSaveConfig() {
+ if (isSettingValues) return; // Don't save during programmatic updates
+
+ clearTimeout(saveTimeout);
+ saveTimeout = setTimeout(async () => {
+ try {
+ const config = getFormValues();
+ await window.electronAPI.saveConfig(config);
+ showStatus('Configuration saved', 'success');
+ } catch (error) {
+ showStatus(`Failed to save configuration: ${error.message}`, 'error');
+ }
+ }, SAVE_DELAY);
+}
+
+// Add event listeners to all form inputs for auto-save
+function setupAutoSave() {
+ // Get all form inputs
+ const form = document.getElementById('config-form');
+ const inputs = form.querySelectorAll('input, select');
+
+ // Add change listener to each input
+ inputs.forEach(input => {
+ if (input.type === 'checkbox') {
+ input.addEventListener('change', autoSaveConfig);
+ } else if (input.type === 'number' || input.type === 'text') {
+ input.addEventListener('input', autoSaveConfig);
+ } else if (input.tagName === 'SELECT') {
+ input.addEventListener('change', autoSaveConfig);
+ }
+ });
+}
+
+// Update the setup-venv handler
+document.getElementById('setup-venv').addEventListener('click', async () => {
+ const setupButton = document.getElementById('setup-venv');
+ setupButton.disabled = true;
+ setupButton.classList.add('opacity-50', 'cursor-not-allowed');
+
+ try {
+ await handleAsyncAction('Install requirements', async () => {
+ return await window.electronAPI.installRequirements();
+ });
+ // Reload microphones after successful installation
+ await loadMicrophones();
+ } finally {
+ setupButton.disabled = false;
+ setupButton.classList.remove('opacity-50', 'cursor-not-allowed');
+ }
+});
+
+// Simplified microphone loading
+async function loadMicrophones() {
+ const microphoneSelect = document.getElementById('microphone');
+
+ try {
+ appendToConsole('Loading available microphones...', 'info');
+ const microphones = await window.electronAPI.getMicrophones();
+
+ microphoneSelect.innerHTML = '';
+
+ if (microphones.length === 0) {
+ microphoneSelect.innerHTML = '<option value="" disabled>No microphones found</option>';
+ appendToConsole('No microphones found', 'stderr');
+ return;
+ }
+
+ appendToConsole(`Found ${microphones.length} microphone(s)`, 'info');
+ microphones.forEach(mic => {
+ const option = document.createElement('option');
+ option.value = mic.index.toString();
+ option.textContent = mic.name;
+ microphoneSelect.appendChild(option);
+ appendToConsole(` - ${mic.name} (Device ${mic.index})`, 'stdout');
+ });
+
+ // Restore previously selected microphone if possible
+ try {
+ const config = await window.electronAPI.loadConfig();
+ if (config.microphone) {
+ microphoneSelect.value = config.microphone;
+ }
+ } catch (error) {
+ // Ignore config load errors here
+ }
+
+ } catch (error) {
+ appendToConsole(`Failed to load microphones: ${error.message}`, 'stderr');
+ microphoneSelect.innerHTML = '<option value="" disabled>Error loading microphones</option>';
+ }
+}
+
+// Update window load to include auto-save setup
+window.addEventListener('load', async () => {
+ appendToConsole('TaSTT Configuration UI initialized', 'info');
+
+ // Load config first
+ try {
+ const config = await window.electronAPI.loadConfig();
+ setFormValues(config);
+ appendToConsole('Configuration loaded', 'info');
+ } catch (error) {
+ appendToConsole(`Failed to load configuration: ${error.message}`, 'stderr');
+ }
+
+ // Load microphones
+ await loadMicrophones();
+
+ // Set up auto-save after everything is loaded
+ setupAutoSave();
+});
+
+// Console management
+const consoleContent = document.getElementById('console-content');
+
+function appendToConsole(message, type = 'stdout') {
+ const timestamp = new Date().toLocaleTimeString();
+ const timestampSpan = document.createElement('span');
+ timestampSpan.className = 'console-timestamp';
+ timestampSpan.textContent = `[${timestamp}] `;
+
+ const messageSpan = document.createElement('span');
+ messageSpan.className = `console-${type}`;
+ messageSpan.textContent = message;
+
+ const lineDiv = document.createElement('div');
+ lineDiv.appendChild(timestampSpan);
+ lineDiv.appendChild(messageSpan);
+
+ consoleContent.appendChild(lineDiv);
+
+ // Auto-scroll to bottom
+ const pythonConsole = document.getElementById('python-console');
+ pythonConsole.scrollTop = pythonConsole.scrollHeight;
+}
+
+// Clear console button
+document.getElementById('clear-console').addEventListener('click', () => {
+ consoleContent.innerHTML = '';
+ appendToConsole('Console cleared', 'info');
+});
+
+// Listen for Python output
+window.electronAPI.onPythonOutput((data) => {
+ appendToConsole(data.message, data.type);
+}); \ No newline at end of file