const { app, BrowserWindow, ipcMain, dialog } = require("electron"); const pdfParse = require("pdf-parse"); const mammoth = require("mammoth"); const fs = require("fs"); const path = require("path"); const os = require("os"); const { queryOpenAI } = require("./chatgpt.js"); const axios = require("axios"); const fsExtra = require("fs-extra"); let fetch; import("node-fetch").then((module) => { fetch = module.default; }); const unzipper = require("unzipper"); let win; function promptUserForApiKey() { // Create a new window to prompt the user for the API key const promptWindow = new BrowserWindow({ // Window configuration for the prompt width: 500, height: 200, webPreferences: { nodeIntegration: true, contextIsolation: false, // Consider security implications }, }); // Handle the API key submission from the prompt window ipcMain.on("submit-api-key", (event, apiKey) => { if (apiKey) { saveApiKey(apiKey); promptWindow.close(); createWindow(); // Proceed to create the main window } else { // Handle invalid input or user cancellation promptWindow.close(); } }); } function loadApiKey() { const configPath = path.join(os.homedir(), ".config", "fabric", ".env"); if (fs.existsSync(configPath)) { const envContents = fs.readFileSync(configPath, { encoding: "utf8" }); const matches = envContents.match(/^OPENAI_API_KEY=(.*)$/m); if (matches && matches[1]) { return matches[1]; } } return null; } function saveApiKey(apiKey) { const configPath = path.join(os.homedir(), ".config", "fabric"); const envFilePath = path.join(configPath, ".env"); if (!fs.existsSync(configPath)) { fs.mkdirSync(configPath, { recursive: true }); } fs.writeFileSync(envFilePath, `OPENAI_API_KEY=${apiKey}`); process.env.OPENAI_API_KEY = apiKey; // Set for current session } function ensureFabricFoldersExist() { return new Promise(async (resolve, reject) => { const fabricPath = path.join(os.homedir(), ".config", "fabric"); const patternsPath = path.join(fabricPath, "patterns"); try { if (!fs.existsSync(fabricPath)) { fs.mkdirSync(fabricPath, { recursive: true }); } if (!fs.existsSync(patternsPath)) { fs.mkdirSync(patternsPath, { recursive: true }); await downloadAndUpdatePatterns(patternsPath); } resolve(); // Resolve the promise once everything is set up } catch (error) { console.error("Error ensuring fabric folders exist:", error); reject(error); // Reject the promise if an error occurs } }); } async function downloadAndUpdatePatterns(patternsPath) { try { const response = await axios({ method: "get", url: "https://github.com/danielmiessler/fabric/archive/refs/heads/main.zip", responseType: "arraybuffer", }); const zipPath = path.join(os.tmpdir(), "fabric.zip"); fs.writeFileSync(zipPath, response.data); console.log("Zip file written to:", zipPath); const tempExtractPath = path.join(os.tmpdir(), "fabric_extracted"); fsExtra.emptyDirSync(tempExtractPath); await fsExtra.remove(patternsPath); // Delete the existing patterns directory await fs .createReadStream(zipPath) .pipe(unzipper.Extract({ path: tempExtractPath })) .promise(); console.log("Extraction complete"); const extractedPatternsPath = path.join( tempExtractPath, "fabric-main", "patterns" ); await fsExtra.copy(extractedPatternsPath, patternsPath); console.log("Patterns successfully updated"); // Inform the renderer process that the patterns have been updated win.webContents.send("patterns-updated"); } catch (error) { console.error("Error downloading or updating patterns:", error); } } function checkApiKeyExists() { const configPath = path.join(os.homedir(), ".config", "fabric", ".env"); return fs.existsSync(configPath); } function getPatternFolders() { const patternsPath = path.join(os.homedir(), ".config", "fabric", "patterns"); return fs .readdirSync(patternsPath, { withFileTypes: true }) .filter((dirent) => dirent.isDirectory()) .map((dirent) => dirent.name); } function getPatternContent(patternName) { const patternPath = path.join( os.homedir(), ".config", "fabric", "patterns", patternName, "system.md" ); try { return fs.readFileSync(patternPath, "utf8"); } catch (error) { console.error("Error reading pattern file:", error); return ""; } } function createWindow() { win = new BrowserWindow({ width: 800, height: 600, webPreferences: { contextIsolation: true, nodeIntegration: false, preload: path.join(__dirname, "preload.js"), }, }); win.loadFile("index.html"); win.on("closed", () => { win = null; }); } ipcMain.on("process-complex-file", (event, filePath) => { const extension = path.extname(filePath).toLowerCase(); let fileProcessPromise; if (extension === ".pdf") { const dataBuffer = fs.readFileSync(filePath); fileProcessPromise = pdfParse(dataBuffer).then((data) => data.text); } else if (extension === ".docx") { fileProcessPromise = mammoth .extractRawText({ path: filePath }) .then((result) => result.value) .catch((err) => { console.error("Error processing DOCX file:", err); throw new Error("Error processing DOCX file."); }); } else { event.reply("file-response", "Error: Unsupported file type"); return; } fileProcessPromise .then((extractedText) => { // Sending the extracted text back to the frontend. event.reply("file-response", extractedText); }) .catch((error) => { // Handling any errors during file processing and sending them back to the frontend. event.reply("file-response", `Error processing file: ${error.message}`); }); }); ipcMain.on("start-query-openai", async (event, system, user) => { if (system == null || user == null) { console.error("Received null for system or user message"); event.reply("openai-response", "Error: System or user message is null."); return; } try { await queryOpenAI(system, user, (message) => { event.reply("openai-response", message); }); } catch (error) { console.error("Error querying OpenAI:", error); event.reply("no-api-key", "Error querying OpenAI."); } }); // Example of using ipcMain.handle for asynchronous operations ipcMain.handle("get-patterns", async (event) => { try { return getPatternFolders(); } catch (error) { console.error("Failed to get patterns:", error); return []; } }); ipcMain.on("update-patterns", () => { const patternsPath = path.join(os.homedir(), ".config", "fabric", "patterns"); downloadAndUpdatePatterns(patternsPath); }); ipcMain.handle("get-pattern-content", async (event, patternName) => { try { return getPatternContent(patternName); } catch (error) { console.error("Failed to get pattern content:", error); return ""; } }); ipcMain.handle("save-api-key", async (event, apiKey) => { try { const configPath = path.join(os.homedir(), ".config", "fabric"); if (!fs.existsSync(configPath)) { fs.mkdirSync(configPath, { recursive: true }); } const envFilePath = path.join(configPath, ".env"); fs.writeFileSync(envFilePath, `OPENAI_API_KEY=${apiKey}`); process.env.OPENAI_API_KEY = apiKey; return "API Key saved successfully."; } catch (error) { console.error("Error saving API key:", error); throw new Error("Failed to save API Key."); } }); app.whenReady().then(async () => { try { const apiKey = loadApiKey(); if (!apiKey) { promptUserForApiKey(); } else { process.env.OPENAI_API_KEY = apiKey; createWindow(); } await ensureFabricFoldersExist(); // Ensure fabric folders exist createWindow(); // Create the application window // After window creation, check if the API key exists if (!checkApiKeyExists()) { console.log("API key is missing. Prompting user to input API key."); // Optionally, directly invoke a function here to show a prompt in the renderer process win.webContents.send("request-api-key"); } } catch (error) { console.error("Failed to initialize fabric folders:", error); // Handle initialization failure (e.g., close the app or show an error message) } }); app.on("window-all-closed", () => { if (process.platform !== "darwin") { app.quit(); } }); app.on("activate", () => { if (win === null) { createWindow(); } });