summaryrefslogtreecommitdiffstats
path: root/ui/index.js
diff options
context:
space:
mode:
Diffstat (limited to 'ui/index.js')
-rw-r--r--ui/index.js382
1 files changed, 332 insertions, 50 deletions
diff --git a/ui/index.js b/ui/index.js
index a056156..2420ece 100644
--- a/ui/index.js
+++ b/ui/index.js
@@ -3,6 +3,7 @@ const path = require('node:path');
const fs = require('node:fs').promises;
const yaml = require('js-yaml');
const { spawn } = require('child_process');
+const https = require('https');
const APP_ROOT = path.join(__dirname, '..');
const CONFIG_PATH = path.join(APP_ROOT, 'config.yaml');
@@ -10,6 +11,20 @@ const CONFIG_PATH = path.join(APP_ROOT, 'config.yaml');
let mainWindow;
let runningProcess = null; // Track the running Python process
+// Required DLL files for CUDA/cuDNN support
+const REQUIRED_DLLS = [
+ 'cublas64_12.dll',
+ 'cublasLt64_12.dll',
+ 'cudnn64_9.dll',
+ 'cudnn_adv64_9.dll',
+ 'cudnn_cnn64_9.dll',
+ 'cudnn_engines_precompiled64_9.dll',
+ 'cudnn_engines_runtime_compiled64_9.dll',
+ 'cudnn_graph64_9.dll',
+ 'cudnn_heuristic64_9.dll',
+ 'cudnn_ops64_9.dll'
+];
+
// Helper function to get the correct Python executable from venv
function getVenvPython() {
const venvPath = path.join(APP_ROOT, 'venv');
@@ -24,6 +39,78 @@ function sendPythonOutput(message, type = 'stdout') {
}
}
+// Helper function to create environment with DLL path
+function createPythonEnvironment() {
+ const dllPath = path.join(APP_ROOT, 'dll');
+ const binPath = path.join(APP_ROOT, 'bin');
+ const env = { ...process.env };
+ env.PATH = `${dllPath};${binPath};${env.PATH}`;
+ env.HF_HUB_DISABLE_SYMLINKS_WARNING = '1';
+ return env;
+}
+
+// Helper function to download a file from URL
+function downloadFile(url, outputPath) {
+ return new Promise((resolve, reject) => {
+ const file = require('fs').createWriteStream(outputPath);
+
+ const request = https.get(url, (response) => {
+ if (response.statusCode === 200) {
+ response.pipe(file);
+
+ file.on('finish', () => {
+ file.close();
+ resolve();
+ });
+
+ file.on('error', (err) => {
+ fs.unlink(outputPath).catch(() => {}); // Clean up on error
+ reject(err);
+ });
+ } else {
+ file.close();
+ fs.unlink(outputPath).catch(() => {}); // Clean up on error
+ reject(new Error(`Failed to download: HTTP ${response.statusCode}`));
+ }
+ });
+
+ request.on('error', (err) => {
+ file.close();
+ fs.unlink(outputPath).catch(() => {}); // Clean up on error
+ reject(err);
+ });
+ });
+}
+
+// Helper function to setup process event handlers
+function setupProcessHandlers(process) {
+ process.stdout.on('data', (data) => {
+ const text = data.toString();
+ sendPythonOutput(text.trimEnd(), 'stdout');
+ });
+
+ process.stderr.on('data', (data) => {
+ const text = data.toString();
+ sendPythonOutput(text.trimEnd(), 'stderr');
+ });
+
+ process.on('error', (error) => {
+ sendPythonOutput(`Process error: ${error.message}`, 'stderr');
+ runningProcess = null;
+ if (mainWindow && !mainWindow.isDestroyed()) {
+ mainWindow.webContents.send('process-stopped');
+ }
+ });
+
+ process.on('close', (code) => {
+ sendPythonOutput(`Process exited with code ${code}`, 'info');
+ runningProcess = null;
+ if (mainWindow && !mainWindow.isDestroyed()) {
+ mainWindow.webContents.send('process-stopped');
+ }
+ });
+}
+
// Helper function to execute Python commands using venv
function executePythonCommand(args, options = {}) {
return new Promise((resolve, reject) => {
@@ -31,14 +118,9 @@ function executePythonCommand(args, options = {}) {
const commandStr = `${path.basename(pythonPath)} ${args.join(' ')}`;
sendPythonOutput(`> ${commandStr}`, 'info');
- // Add dll directory to PATH for Windows DLL loading
- const dllPath = path.join(APP_ROOT, 'dll');
- const env = { ...process.env };
- env.PATH = `${dllPath};${env.PATH}`;
-
const spawnOptions = {
...options,
- env
+ env: createPythonEnvironment()
};
const pythonProcess = spawn(pythonPath, args, spawnOptions);
@@ -78,6 +160,7 @@ function createWindow () {
mainWindow = new BrowserWindow({
width: 1000,
height: 800,
+ icon: path.join(APP_ROOT, 'Images', 'favicon.ico'),
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
@@ -93,6 +176,7 @@ const DEFAULT_CONFIG = {
compute_type: 'float16',
enable_debug_mode: 0,
enable_previews: 1,
+ user_prompt: 'Use proper punctuation and grammar. Prefer spelled out numbers like one, eleven, twenty, etc.',
save_audio: 0,
language: 'english',
gpu_idx: 0,
@@ -117,11 +201,11 @@ ipcMain.handle('load-config', async () => {
} catch (error) {
if (error.code === 'ENOENT') {
// Config file doesn't exist, create it with defaults
- console.log('Config file not found, creating with defaults...');
+ console.error('Config file not found, creating with defaults...');
try {
const yamlContent = yaml.dump(DEFAULT_CONFIG, { lineWidth: -1 });
await fs.writeFile(CONFIG_PATH, yamlContent, 'utf8');
- console.log('Created config.yaml with default values');
+ console.error('Created config.yaml with default values');
return DEFAULT_CONFIG;
} catch (writeError) {
console.error('Error creating default config:', writeError);
@@ -145,21 +229,138 @@ ipcMain.handle('save-config', async (event, config) => {
}
});
-ipcMain.handle('restart-app', () => {
- app.relaunch();
- app.exit();
+ipcMain.handle('reset-config', async () => {
+ try {
+ // Check if the file exists first
+ try {
+ await fs.access(CONFIG_PATH);
+ // File exists, delete it
+ await fs.unlink(CONFIG_PATH);
+ console.error('Config file deleted successfully');
+ return { success: true, message: 'Configuration reset to defaults' };
+ } catch (error) {
+ if (error.code === 'ENOENT') {
+ // Config file doesn't exist, that's fine
+ return { success: true, message: 'Configuration already at defaults' };
+ }
+ throw error;
+ }
+ } catch (error) {
+ console.error('Error resetting config:', error);
+ throw new Error(`Failed to reset configuration: ${error.message}`);
+ }
});
-ipcMain.handle('install-requirements', async (event) => {
+// Generic function to ensure required files are present
+async function ensureRequiredFiles(config) {
+ const {
+ directoryName,
+ requiredFiles,
+ downloadBaseUrl,
+ resourceType
+ } = config;
+
+ const targetPath = path.join(APP_ROOT, directoryName);
+
+ try {
+ // Check if target directory exists, create it if not
+ try {
+ await fs.access(targetPath);
+ sendPythonOutput(`${resourceType} directory exists`, 'info');
+ } catch (error) {
+ if (error.code === 'ENOENT') {
+ sendPythonOutput(`Creating ${resourceType} directory...`, 'info');
+ await fs.mkdir(targetPath, { recursive: true });
+ sendPythonOutput(`${resourceType} directory created`, 'info');
+ } else {
+ throw error;
+ }
+ }
+
+ // Check each required file
+ const missingFiles = [];
+ for (const fileName of requiredFiles) {
+ const filePath = path.join(targetPath, fileName);
+ try {
+ await fs.access(filePath);
+ sendPythonOutput(`✓ ${fileName} exists`, 'info');
+ } catch (error) {
+ if (error.code === 'ENOENT') {
+ missingFiles.push(fileName);
+ sendPythonOutput(`✗ ${fileName} missing`, 'info');
+ } else {
+ throw error;
+ }
+ }
+ }
+
+ // Download missing files
+ if (missingFiles.length > 0) {
+ sendPythonOutput(`Downloading ${missingFiles.length} missing ${resourceType} file${missingFiles.length > 1 ? 's' : ''}...`, 'info');
+
+ for (const fileName of missingFiles) {
+ const filePath = path.join(targetPath, fileName);
+ const downloadUrl = `${downloadBaseUrl}/${fileName}`;
+
+ try {
+ sendPythonOutput(`Downloading ${fileName}...`, 'info');
+ await downloadFile(downloadUrl, filePath);
+ sendPythonOutput(`✓ Downloaded ${fileName}`, 'info');
+ } catch (downloadError) {
+ sendPythonOutput(`✗ Failed to download ${fileName}: ${downloadError.message}`, 'stderr');
+ throw new Error(`Failed to download ${fileName}: ${downloadError.message}`);
+ }
+ }
+
+ sendPythonOutput(`All missing ${resourceType} files downloaded successfully`, 'info');
+ } else {
+ sendPythonOutput(`All required ${resourceType} files are present`, 'info');
+ }
+
+ return {
+ success: true,
+ message: `${resourceType} setup complete. ${missingFiles.length} file${missingFiles.length > 1 ? 's' : ''} downloaded.`,
+ downloadedFiles: missingFiles
+ };
+ } catch (error) {
+ console.error(`Error setting up ${resourceType} files:`, error);
+ throw new Error(`${resourceType} setup failed: ${error.message}`);
+ }
+}
+
+// Update the install-requirements handler
+ipcMain.handle('install-requirements', async () => {
const requirementsPath = path.join(APP_ROOT, 'app', 'requirements.txt');
+ const venvMarkerPath = path.join(APP_ROOT, '.venv_is_set_up');
try {
+ // Check if venv is already set up
+ try {
+ await fs.access(venvMarkerPath);
+ sendPythonOutput('Virtual environment already set up, skipping installation', 'info');
+ return { success: true, message: 'Virtual environment already set up' };
+ } catch (error) {
+ // Marker doesn't exist, proceed with setup
+ }
+
// Check if requirements.txt exists
await fs.access(requirementsPath);
- const result = await executePythonCommand(['-m', 'pip', 'install', '-r', requirementsPath]);
+ await executePythonCommand(['-m', 'pip', 'install', '-r', requirementsPath]);
+
+ await ensureRequiredFiles({
+ directoryName: 'dll',
+ requiredFiles: REQUIRED_DLLS,
+ downloadBaseUrl: 'https://yummers.dev/tastt/dll',
+ resourceType: 'DLL'
+ });
+
+ await fs.mkdir(path.join(APP_ROOT, 'Models'), { recursive: true });
+
+ await fs.writeFile(venvMarkerPath, new Date().toISOString(), 'utf8');
+ sendPythonOutput('Created .venv_is_set_up marker file', 'info');
- return { success: true, message: 'Requirements installed successfully' };
+ return { success: true, message: 'Requirements and dependencies installed successfully' };
} catch (error) {
console.error('Error installing requirements:', error);
if (error.code === 'ENOENT') {
@@ -175,7 +376,6 @@ ipcMain.handle('get-microphones', async () => {
try {
const result = await executePythonCommand([scriptPath]);
const microphones = JSON.parse(result.stdout.trim());
- console.log('Successfully retrieved microphones:', microphones);
return microphones;
} catch (error) {
console.error('Failed to get microphones:', error);
@@ -183,53 +383,135 @@ ipcMain.handle('get-microphones', async () => {
}
});
-// Add handlers for starting and stopping the process
-ipcMain.handle('start-process', async () => {
- if (runningProcess) {
- throw new Error('Process is already running');
+// Helper function to safely delete directory contents
+async function clearDirectory(dirPath, dirName) {
+ try {
+ await fs.access(dirPath);
+ sendPythonOutput(`Clearing ${dirName} directory...`, 'info');
+
+ const files = await fs.readdir(dirPath);
+ let deletedCount = 0;
+
+ for (const file of files) {
+ const filePath = path.join(dirPath, file);
+
+ try {
+ await fs.rm(filePath, { recursive: true, force: true });
+ sendPythonOutput(`✗ Deleted file ${file}`, 'info');
+
+ deletedCount++;
+ } catch (deleteError) {
+ sendPythonOutput(`Warning: Could not delete ${file}: ${deleteError.message}`, 'stderr');
+ // Continue with other files even if one fails
+ }
+ }
+
+ sendPythonOutput(`${dirName} directory cleared`, 'info');
+ return deletedCount;
+ } catch (error) {
+ if (error.code === 'ENOENT') {
+ sendPythonOutput(`${dirName} directory doesn't exist, skipping`, 'info');
+ return 0;
+ } else {
+ sendPythonOutput(`Error clearing ${dirName} directory: ${error.message}`, 'stderr');
+ throw error;
+ }
}
+}
- const scriptPath = path.join(APP_ROOT, 'app', 'hi.py');
- const configPath = CONFIG_PATH;
+ipcMain.handle('reset-venv', async () => {
+ const venvMarkerPath = path.join(APP_ROOT, '.venv_is_set_up');
try {
- const pythonPath = getVenvPython();
- const args = [scriptPath, '--config', configPath];
+ sendPythonOutput('Starting virtual environment reset...', 'info');
- sendPythonOutput(`Starting process: ${path.basename(pythonPath)} ${args.join(' ')}`, 'info');
+ // Delete the venv marker file first
+ try {
+ await fs.unlink(venvMarkerPath);
+ sendPythonOutput('Deleted .venv_is_set_up marker file', 'info');
+ } catch (error) {
+ if (error.code !== 'ENOENT') {
+ sendPythonOutput(`Warning: Could not delete marker file: ${error.message}`, 'stderr');
+ }
+ }
+
+ // Get list of installed packages
+ sendPythonOutput('Getting list of installed packages...', 'info');
+ const freezeResult = await executePythonCommand(['-m', 'pip', 'freeze']);
+ const installedPackages = freezeResult.stdout.trim();
+
+ let uninstalledPackages = [];
+
+ if (!installedPackages) {
+ sendPythonOutput('No packages found to uninstall', 'info');
+ } else {
+ // Parse package names and filter out core packages
+ const packageLines = installedPackages.split('\n').filter(line => line.trim());
+ const packageNames = packageLines
+ .map(line => line.split('==')[0].trim())
+ .filter(name => name && !name.startsWith('#'));
+
+ const corePackages = ['pip', 'setuptools', 'wheel'];
+ const packagesToUninstall = packageNames.filter(name => !corePackages.includes(name.toLowerCase()));
+
+ if (packagesToUninstall.length === 0) {
+ sendPythonOutput('Only core packages found, nothing to uninstall', 'info');
+ } else {
+ sendPythonOutput(`Uninstalling ${packagesToUninstall.length} packages...`, 'info');
+
+ const uninstallArgs = ['-m', 'pip', 'uninstall', '-y', ...packagesToUninstall];
+ await executePythonCommand(uninstallArgs);
+ uninstalledPackages = packagesToUninstall;
+ }
+ }
+
+ // Clear downloaded files
+ sendPythonOutput('Clearing downloaded files...', 'info');
- // Add dll directory to PATH for Windows DLL loading
const dllPath = path.join(APP_ROOT, 'dll');
- const env = { ...process.env };
- env.PATH = `${dllPath};${env.PATH}`;
+ const modelsPath = path.join(APP_ROOT, 'Models');
+ const binPath = path.join(APP_ROOT, 'bin');
- runningProcess = spawn(pythonPath, args, { env });
+ const deletedDlls = await clearDirectory(dllPath, 'DLL');
+ const deletedModels = await clearDirectory(modelsPath, 'Models');
+ const deletedBins = await clearDirectory(binPath, 'Binary');
- runningProcess.stdout.on('data', (data) => {
- const text = data.toString();
- sendPythonOutput(text.trimEnd(), 'stdout');
- });
+ const totalDeletedFiles = deletedDlls + deletedModels + deletedBins;
- runningProcess.stderr.on('data', (data) => {
- const text = data.toString();
- sendPythonOutput(text.trimEnd(), 'stderr');
- });
+ sendPythonOutput('Virtual environment reset successfully!', 'info');
- runningProcess.on('error', (error) => {
- sendPythonOutput(`Process error: ${error.message}`, 'stderr');
- runningProcess = null;
- if (mainWindow && !mainWindow.isDestroyed()) {
- mainWindow.webContents.send('process-stopped');
+ return {
+ success: true,
+ message: `Virtual environment reset complete. Uninstalled ${uninstalledPackages.length} packages and deleted ${totalDeletedFiles} downloaded files.`,
+ uninstalledPackages,
+ deletedFiles: {
+ dlls: deletedDlls,
+ models: deletedModels,
+ binaries: deletedBins,
+ total: totalDeletedFiles
}
- });
+ };
+ } catch (error) {
+ console.error('Error resetting virtual environment:', error);
+ throw new Error(`Virtual environment reset failed: ${error.message}`);
+ }
+});
+
+// Add handlers for starting and stopping the process
+ipcMain.handle('start-process', async () => {
+ if (runningProcess) {
+ throw new Error('Process is already running');
+ }
+
+ const scriptPath = path.join(APP_ROOT, 'app', 'hi.py');
+ const args = [scriptPath, '--config', CONFIG_PATH];
+
+ try {
+ const pythonPath = getVenvPython();
+ sendPythonOutput(`Starting process: ${path.basename(pythonPath)} ${args.join(' ')}`, 'info');
- runningProcess.on('close', (code) => {
- sendPythonOutput(`Process exited with code ${code}`, 'info');
- runningProcess = null;
- if (mainWindow && !mainWindow.isDestroyed()) {
- mainWindow.webContents.send('process-stopped');
- }
- });
+ runningProcess = spawn(pythonPath, args, { env: createPythonEnvironment() });
+ setupProcessHandlers(runningProcess);
return { success: true };
} catch (error) {
@@ -243,7 +525,7 @@ ipcMain.handle('stop-process', async () => {
throw new Error('No process is running');
}
- return new Promise((resolve, reject) => {
+ return new Promise((resolve) => {
let forcefullyKilled = false;
// Set up a timeout to force kill after 10 seconds