summaryrefslogtreecommitdiffstats
path: root/ui/index.js
diff options
context:
space:
mode:
authoryum <yum.food.vr@gmail.com>2025-05-29 17:23:09 -0700
committeryum <yum.food.vr@gmail.com>2025-05-29 17:23:09 -0700
commit82a5b3805b2a54faea501ee362419330664c277a (patch)
tree235ac6540d1a25e4fcf4107c1ea93f69e43b563b /ui/index.js
parent1ede199387c072a85e8757a6aaec04d2c7cdeba4 (diff)
Begin roughing out STT UI
HEAVILY VIBE CODED!
Diffstat (limited to 'ui/index.js')
-rw-r--r--ui/index.js155
1 files changed, 152 insertions, 3 deletions
diff --git a/ui/index.js b/ui/index.js
index 9751fb2..0a7fdf9 100644
--- a/ui/index.js
+++ b/ui/index.js
@@ -1,10 +1,71 @@
const { app, BrowserWindow, ipcMain } = require('electron');
const path = require('node:path');
+const fs = require('node:fs').promises;
+const yaml = require('js-yaml');
+const { spawn } = require('child_process');
+
+let mainWindow;
+
+// Helper function to get the correct Python executable from venv
+function getVenvPython() {
+ const venvPath = path.join(__dirname, '..', 'venv');
+ const isWindows = process.platform === 'win32';
+ const pythonExecutable = isWindows ? 'python.exe' : 'python';
+ const pythonPath = path.join(venvPath, isWindows ? 'Scripts' : 'bin', pythonExecutable);
+ return pythonPath;
+}
+
+// Helper function to send Python output to renderer
+function sendPythonOutput(message, type = 'stdout') {
+ if (mainWindow && !mainWindow.isDestroyed()) {
+ mainWindow.webContents.send('python-output', { message, type });
+ }
+}
+
+// Helper function to execute Python commands using venv
+function executePythonCommand(args, options = {}) {
+ return new Promise((resolve, reject) => {
+ const pythonPath = getVenvPython();
+ const commandStr = `${path.basename(pythonPath)} ${args.join(' ')}`;
+ sendPythonOutput(`> ${commandStr}`, 'info');
+
+ const pythonProcess = spawn(pythonPath, args, options);
+
+ let stdout = '';
+ let stderr = '';
+
+ pythonProcess.stdout.on('data', (data) => {
+ const text = data.toString();
+ stdout += text;
+ sendPythonOutput(text.trimEnd(), 'stdout');
+ });
+
+ pythonProcess.stderr.on('data', (data) => {
+ const text = data.toString();
+ stderr += text;
+ sendPythonOutput(text.trimEnd(), 'stderr');
+ });
+
+ pythonProcess.on('error', (error) => {
+ sendPythonOutput(`Failed to start Python process: ${error.message}`, 'stderr');
+ reject({ error: error.message, stdout, stderr });
+ });
+
+ pythonProcess.on('close', (code) => {
+ if (code !== 0) {
+ sendPythonOutput(`Process exited with code ${code}`, 'stderr');
+ reject({ code, stdout, stderr });
+ } else {
+ resolve({ stdout, stderr });
+ }
+ });
+ });
+}
function createWindow () {
- const mainWindow = new BrowserWindow({
- width: 800,
- height: 600,
+ mainWindow = new BrowserWindow({
+ width: 1000,
+ height: 800,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
@@ -15,6 +76,94 @@ function createWindow () {
mainWindow.loadFile('index.html');
}
+// Path to config.yaml (one level up from ui directory)
+const configPath = path.join(__dirname, '..', 'config.yaml');
+
+// IPC handlers
+ipcMain.handle('load-config', async () => {
+ try {
+ const fileContent = await fs.readFile(configPath, 'utf8');
+ return yaml.load(fileContent);
+ } catch (error) {
+ console.error('Error loading config:', error);
+ throw error;
+ }
+});
+
+ipcMain.handle('save-config', async (event, config) => {
+ try {
+ const yamlContent = yaml.dump(config, { lineWidth: -1 });
+ await fs.writeFile(configPath, yamlContent, 'utf8');
+ return { success: true };
+ } catch (error) {
+ console.error('Error saving config:', error);
+ throw error;
+ }
+});
+
+ipcMain.handle('restart-app', () => {
+ app.relaunch();
+ app.exit();
+});
+
+ipcMain.handle('install-requirements', async (event) => {
+ const requirementsPath = path.join(__dirname, '..', 'app', 'requirements.txt');
+
+ try {
+ // Check if requirements.txt exists
+ await fs.access(requirementsPath);
+
+ const result = await executePythonCommand(['-m', 'pip', 'install', '-r', requirementsPath]);
+
+ return { success: true, message: 'Requirements installed successfully' };
+ } catch (error) {
+ console.error('Error installing requirements:', error);
+ if (error.code === 'ENOENT') {
+ throw new Error('requirements.txt not found');
+ }
+ throw new Error(`Installation failed: ${error.stderr || error.error || 'Unknown error'}`);
+ }
+});
+
+ipcMain.handle('get-microphones', async () => {
+ const pythonScript = `
+import pyaudio
+import json
+import sys
+
+try:
+ p = pyaudio.PyAudio()
+ info = p.get_host_api_info_by_index(0)
+ numdevices = info.get('deviceCount')
+
+ microphones = []
+ for i in range(0, numdevices):
+ device_info = p.get_device_info_by_host_api_device_index(0, i)
+ if device_info.get('maxInputChannels') > 0:
+ microphones.append({
+ 'index': i,
+ 'name': device_info.get('name'),
+ 'defaultSampleRate': device_info.get('defaultSampleRate')
+ })
+
+ print(json.dumps(microphones))
+ p.terminate()
+except Exception as e:
+ print(json.dumps({'error': str(e)}), file=sys.stderr)
+ sys.exit(1)
+`;
+
+ try {
+ const result = await executePythonCommand(['-c', pythonScript]);
+ const microphones = JSON.parse(result.stdout.trim());
+ console.log('Successfully retrieved microphones:', microphones);
+ return microphones;
+ } catch (error) {
+ console.error('Failed to get microphones:', error);
+ throw new Error(`Failed to get microphones: ${error.stderr || error.error || 'Unknown error'}`);
+ }
+});
+
app.whenReady().then(() => {
createWindow();