vtube-studio/src/electron/main/electron-main.js

104 lines
4.2 KiB
JavaScript

import { app, BrowserWindow, ipcMain } from 'electron';
import path from 'path';
import url from 'url';
import { fileURLToPath } from 'url';
// Keep an in-memory API key for the running session only. Renderer should still store key in localStorage.
let inMemoryKey = null;
function createWindow() {
// Resolve dirname equivalent in ESM
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const win = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
// Preload is colocated with the electron main files after the refactor
preload: path.join(__dirname, 'electron-preload.js'),
contextIsolation: true,
nodeIntegration: false,
}
});
const startUrl = process.env.ELECTRON_START_URL || url.pathToFileURL(path.join(process.cwd(), 'dist', 'index.html')).toString();
win.loadURL(startUrl).catch(err => {
console.error('[electron-main] Failed to load URL:', err);
});
if (process.env.ELECTRON_START_URL) {
try {
const ses = win.webContents.session;
ses.webRequest.onHeadersReceived((details, callback) => {
const headers = details.responseHeaders || {};
// Allow jsDelivr CDN for scripts used by third-party libs (dev only)
// Dev CSP: allow inline scripts, unsafe-eval and jsDelivr CDN for third-party libs (HMR and wasm loaders need inline scripts/eval)
headers['Content-Security-Policy'] = [
"default-src 'self' 'unsafe-eval' 'unsafe-inline' data:; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.jsdelivr.net; img-src 'self' data:; connect-src *; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' data: https://fonts.gstatic.com"
];
callback({ responseHeaders: headers });
});
} catch (e) {
console.warn('[electron-main] Failed to inject dev CSP:', e);
}
win.webContents.once('did-frame-finish-load', () => {
try {
win.webContents.openDevTools({ mode: 'right' });
} catch (e) {
console.warn('[electron-main] Could not open DevTools:', e);
}
});
}
win.webContents.on('did-finish-load', () => {
console.log('[electron-main] Renderer finished load; title=', win.getTitle());
});
}
app.whenReady().then(() => {
createWindow();
app.on('activate', function () {
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});
});
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') app.quit();
});
ipcMain.handle('generate-avatar', async (event, prompt) => {
try {
console.log('[electron-main] generate-avatar handler invoked');
console.log('[electron-main] prompt length:', (prompt || '').length);
// Prefer an in-memory key, then environment variables
const apiKey = inMemoryKey || process.env.GEMINI_API_KEY || process.env.API_KEY;
console.log('[electron-main] apiKey present?', !!apiKey, '(will prefer GEMINI_API_KEY if set)');
if (apiKey) {
try {
console.log('[electron-main] Calling GenAI helper...');
const imageData = await generateAvatarWithGenAI(prompt || '', apiKey);
console.log('[electron-main] GenAI helper returned image, length:', imageData?.length || 0);
return { image: imageData };
} catch (e) {
console.error('[electron-main] GenAI generation failed:', e?.message || e);
}
} else {
console.log('[electron-main] No API key present — skipping GenAI call and returning placeholder');
}
} catch (outerErr) {
console.error('[electron-main] Unexpected error in generate-avatar handler:', outerErr);
}
// Fallback placeholder image
const placeholder = `data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='1400' height='900'><rect width='100%' height='100%' fill='%230f172a' /><text x='50%' y='50%' fill='white' font-size='40' font-family='Inter' dominant-baseline='middle' text-anchor='middle'>Placeholder: ${encodeURIComponent((prompt||'').substring(0,80))}</text></svg>`;
console.log('[electron-main] Returning placeholder image (length):', placeholder.length);
return { image: placeholder };
});
// Expose simple key management for the renderer via ipc (session-only)
// No renderer-side key persistence — frontend uses localStorage exclusively now.