2023-11-04 03:56:27 +00:00
|
|
|
const { app, BrowserWindow, ipcMain, Tray, Menu, nativeImage, shell } = require('electron');
|
2023-09-06 19:04:18 +00:00
|
|
|
const todesktop = require("@todesktop/runtime");
|
2023-11-04 03:56:27 +00:00
|
|
|
const khojPackage = require('./package.json');
|
2023-09-06 19:04:18 +00:00
|
|
|
|
|
|
|
todesktop.init();
|
|
|
|
|
|
|
|
const fs = require('fs');
|
|
|
|
const {dialog} = require('electron');
|
|
|
|
|
|
|
|
const cron = require('cron').CronJob;
|
|
|
|
const axios = require('axios');
|
|
|
|
|
2023-11-09 21:34:27 +00:00
|
|
|
const KHOJ_URL = 'https://app.khoj.dev';
|
2023-09-06 19:04:18 +00:00
|
|
|
|
|
|
|
const Store = require('electron-store');
|
|
|
|
|
2024-04-12 05:19:58 +00:00
|
|
|
const textFileTypes = [
|
|
|
|
// Default valid file extensions supported by Khoj
|
|
|
|
'org', 'md', 'markdown', 'txt', 'html', 'xml',
|
|
|
|
// Other valid text file extensions from https://google.github.io/magika/model/config.json
|
|
|
|
'appleplist', 'asm', 'asp', 'batch', 'c', 'cs', 'css', 'csv', 'eml', 'go', 'html', 'ini', 'internetshortcut', 'java', 'javascript', 'json', 'latex', 'lisp', 'makefile', 'markdown', 'mht', 'mum', 'pem', 'perl', 'php', 'powershell', 'python', 'rdf', 'rst', 'rtf', 'ruby', 'rust', 'scala', 'shell', 'smali', 'sql', 'svg', 'symlinktext', 'txt', 'vba', 'winregistry', 'xml', 'yaml']
|
|
|
|
const binaryFileTypes = ['pdf']
|
|
|
|
const validFileTypes = textFileTypes.concat(binaryFileTypes);
|
2023-09-06 19:04:18 +00:00
|
|
|
|
|
|
|
const schema = {
|
|
|
|
files: {
|
|
|
|
type: 'array',
|
|
|
|
items: {
|
|
|
|
type: 'object',
|
|
|
|
properties: {
|
|
|
|
path: {
|
|
|
|
type: 'string'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
default: []
|
|
|
|
},
|
|
|
|
folders: {
|
|
|
|
type: 'array',
|
|
|
|
items: {
|
|
|
|
type: 'object',
|
|
|
|
properties: {
|
|
|
|
path: {
|
|
|
|
type: 'string'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
default: []
|
|
|
|
},
|
2023-10-26 19:33:03 +00:00
|
|
|
khojToken: {
|
|
|
|
type: 'string',
|
|
|
|
default: ''
|
|
|
|
},
|
2023-09-06 19:04:18 +00:00
|
|
|
hostURL: {
|
|
|
|
type: 'string',
|
|
|
|
default: KHOJ_URL
|
|
|
|
},
|
|
|
|
lastSync: {
|
|
|
|
type: 'array',
|
|
|
|
items: {
|
|
|
|
type: 'object',
|
|
|
|
properties: {
|
|
|
|
path: {
|
|
|
|
type: 'string'
|
|
|
|
},
|
|
|
|
datetime: {
|
|
|
|
type: 'string'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2023-11-09 21:34:27 +00:00
|
|
|
let syncing = false;
|
2023-12-29 04:50:48 +00:00
|
|
|
let state = {}
|
2023-10-12 01:12:12 +00:00
|
|
|
const store = new Store({ schema });
|
2023-09-06 19:04:18 +00:00
|
|
|
|
|
|
|
console.log(store);
|
|
|
|
|
|
|
|
// include the Node.js 'path' module at the top of your file
|
|
|
|
const path = require('path');
|
|
|
|
|
|
|
|
function handleSetTitle (event, title) {
|
|
|
|
const webContents = event.sender
|
|
|
|
const win = BrowserWindow.fromWebContents(webContents)
|
|
|
|
win.setTitle(title)
|
|
|
|
dialog.showOpenDialog({properties: ['openFile', 'openDirectory'] }).then(function (response) {
|
|
|
|
if (!response.canceled) {
|
|
|
|
// handle fully qualified file name
|
|
|
|
console.log(response.filePaths[0]);
|
|
|
|
} else {
|
|
|
|
console.log("no file selected");
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2023-10-12 01:12:12 +00:00
|
|
|
function filenameToMimeType (filename) {
|
|
|
|
const extension = filename.split('.').pop();
|
|
|
|
switch (extension) {
|
|
|
|
case 'pdf':
|
|
|
|
return 'application/pdf';
|
|
|
|
case 'png':
|
|
|
|
return 'image/png';
|
|
|
|
case 'jpg':
|
|
|
|
case 'jpeg':
|
|
|
|
return 'image/jpeg';
|
2023-10-17 09:37:20 +00:00
|
|
|
case 'md':
|
2023-10-12 01:12:12 +00:00
|
|
|
case 'markdown':
|
|
|
|
return 'text/markdown';
|
|
|
|
case 'org':
|
|
|
|
return 'text/org';
|
|
|
|
default:
|
2024-02-24 14:29:03 +00:00
|
|
|
console.warn(`Unknown file type: ${extension}. Defaulting to text/plain.`);
|
2023-10-12 01:12:12 +00:00
|
|
|
return 'text/plain';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-12 05:19:58 +00:00
|
|
|
function isSupportedFileType(filePath) {
|
2024-04-23 05:44:57 +00:00
|
|
|
const fileExtension = filePath.split('.').pop().toLowerCase();
|
2024-04-12 05:19:58 +00:00
|
|
|
return validFileTypes.includes(fileExtension);
|
2024-04-02 20:19:15 +00:00
|
|
|
}
|
|
|
|
|
2024-04-12 06:19:25 +00:00
|
|
|
function processDirectory(filesToPush, folder) {
|
2024-04-23 05:31:20 +00:00
|
|
|
try {
|
|
|
|
const files = fs.readdirSync(folder.path, { withFileTypes: true });
|
|
|
|
|
|
|
|
for (const file of files) {
|
|
|
|
const filePath = path.join(file.path, file.name || '');
|
|
|
|
// Skip hidden files and folders
|
|
|
|
if (file.name.startsWith('.')) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// Add supported files to index
|
|
|
|
if (file.isFile() && isSupportedFileType(filePath)) {
|
|
|
|
console.log(`Add ${file.name} in ${file.path} for indexing`);
|
|
|
|
filesToPush.push(filePath);
|
|
|
|
}
|
|
|
|
// Recursively process subdirectories
|
|
|
|
if (file.isDirectory()) {
|
|
|
|
processDirectory(filesToPush, {'path': filePath});
|
|
|
|
}
|
2023-12-03 19:03:29 +00:00
|
|
|
}
|
2024-04-23 05:31:20 +00:00
|
|
|
} catch (err) {
|
|
|
|
if (err.code === 'EACCES') {
|
|
|
|
console.error(`Access denied to ${folder.path}`);
|
|
|
|
} else if (err.code === 'ENOENT') {
|
|
|
|
console.error(`${folder.path} does not exist`);
|
|
|
|
} else {
|
|
|
|
console.error(`An error occurred while reading directory: ${error.message}`);
|
2023-12-03 19:03:29 +00:00
|
|
|
}
|
2024-04-23 05:31:20 +00:00
|
|
|
return;
|
2023-12-03 19:03:29 +00:00
|
|
|
}
|
2024-04-23 05:31:20 +00:00
|
|
|
|
2023-12-03 19:03:29 +00:00
|
|
|
}
|
|
|
|
|
2024-04-12 06:19:25 +00:00
|
|
|
function pushDataToKhoj (regenerate = false) {
|
2023-11-07 11:17:42 +00:00
|
|
|
// Don't sync if token or hostURL is not set or if already syncing
|
2023-11-09 01:59:02 +00:00
|
|
|
if (store.get('khojToken') === '' || store.get('hostURL') === '' || syncing === true) {
|
2023-11-07 11:17:42 +00:00
|
|
|
const win = BrowserWindow.getAllWindows()[0];
|
|
|
|
if (win) win.webContents.send('update-state', state);
|
|
|
|
return;
|
|
|
|
} else {
|
2023-11-09 01:59:02 +00:00
|
|
|
syncing = true;
|
2023-11-07 11:17:42 +00:00
|
|
|
}
|
|
|
|
|
2023-09-06 19:04:18 +00:00
|
|
|
let filesToPush = [];
|
2023-10-12 01:12:12 +00:00
|
|
|
const files = store.get('files') || [];
|
|
|
|
const folders = store.get('folders') || [];
|
|
|
|
state = { completed: true }
|
2023-09-06 19:04:18 +00:00
|
|
|
|
2023-10-18 08:00:41 +00:00
|
|
|
// Collect paths of all configured files to index
|
2023-10-12 01:12:12 +00:00
|
|
|
for (const file of files) {
|
2024-04-25 09:49:59 +00:00
|
|
|
// Remove files that no longer exist
|
|
|
|
if (!fs.existsSync(file.path)) {
|
|
|
|
console.error(`${file.path} does not exist`);
|
|
|
|
continue;
|
|
|
|
}
|
2023-10-12 01:12:12 +00:00
|
|
|
filesToPush.push(file.path);
|
2023-09-06 19:04:18 +00:00
|
|
|
}
|
2023-10-12 01:12:12 +00:00
|
|
|
|
2023-10-18 08:00:41 +00:00
|
|
|
// Collect paths of all indexable files in configured folders
|
2023-10-12 01:12:12 +00:00
|
|
|
for (const folder of folders) {
|
2024-04-25 09:49:59 +00:00
|
|
|
// Remove folders that no longer exist
|
|
|
|
if (!fs.existsSync(folder.path)) {
|
|
|
|
console.error(`${folder.path} does not exist`);
|
|
|
|
continue;
|
|
|
|
}
|
2024-04-12 06:19:25 +00:00
|
|
|
processDirectory(filesToPush, folder);
|
2023-09-06 19:04:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const lastSync = store.get('lastSync') || [];
|
2024-01-03 17:38:20 +00:00
|
|
|
const filesDataToPush = [];
|
2023-10-12 01:12:12 +00:00
|
|
|
for (const file of filesToPush) {
|
2023-09-06 19:04:18 +00:00
|
|
|
const stats = fs.statSync(file);
|
2023-09-12 23:35:07 +00:00
|
|
|
if (!regenerate) {
|
2023-10-18 08:00:41 +00:00
|
|
|
// Only push files that have been modified since last sync
|
2023-09-12 23:35:07 +00:00
|
|
|
if (stats.mtime.toISOString() < lastSync.find((syncedFile) => syncedFile.path === file)?.datetime) {
|
|
|
|
continue;
|
|
|
|
}
|
2023-09-06 19:04:18 +00:00
|
|
|
}
|
2023-09-12 23:35:07 +00:00
|
|
|
|
2023-10-18 08:00:41 +00:00
|
|
|
// Collect all updated or newly created files since last sync to index on Khoj server
|
2023-09-06 19:04:18 +00:00
|
|
|
try {
|
2023-10-17 20:05:50 +00:00
|
|
|
let encoding = binaryFileTypes.includes(file.split('.').pop()) ? "binary" : "utf8";
|
|
|
|
let mimeType = filenameToMimeType(file) + (encoding === "utf8" ? "; charset=UTF-8" : "");
|
|
|
|
let fileContent = Buffer.from(fs.readFileSync(file, { encoding: encoding }), encoding);
|
|
|
|
let fileObj = new Blob([fileContent], { type: mimeType });
|
2024-01-03 17:38:20 +00:00
|
|
|
filesDataToPush.push({blob: fileObj, path: file});
|
2023-09-06 19:04:18 +00:00
|
|
|
state[file] = {
|
|
|
|
success: true,
|
|
|
|
}
|
|
|
|
} catch (err) {
|
|
|
|
console.error(err);
|
|
|
|
state[file] = {
|
|
|
|
success: false,
|
|
|
|
error: err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-18 08:00:41 +00:00
|
|
|
// Mark deleted files for removal from index on Khoj server
|
2023-09-06 19:04:18 +00:00
|
|
|
for (const syncedFile of lastSync) {
|
|
|
|
if (!filesToPush.includes(syncedFile.path)) {
|
2023-10-12 01:12:12 +00:00
|
|
|
fileObj = new Blob([""], { type: filenameToMimeType(syncedFile.path) });
|
2024-01-03 17:38:20 +00:00
|
|
|
filesDataToPush.push({blob: fileObj, path: syncedFile.path});
|
2023-09-06 19:04:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-18 08:00:41 +00:00
|
|
|
// Send collected files to Khoj server for indexing
|
2024-01-03 17:38:20 +00:00
|
|
|
const hostURL = store.get('hostURL') || KHOJ_URL;
|
|
|
|
const headers = { 'Authorization': `Bearer ${store.get("khojToken")}` };
|
|
|
|
let requests = [];
|
|
|
|
|
|
|
|
// Request indexing files on server. With upto 1000 files in each request
|
|
|
|
for (let i = 0; i < filesDataToPush.length; i += 1000) {
|
|
|
|
const filesDataGroup = filesDataToPush.slice(i, i + 1000);
|
|
|
|
const formData = new FormData();
|
|
|
|
filesDataGroup.forEach(fileData => { formData.append('files', fileData.blob, fileData.path) });
|
|
|
|
let request = axios.post(`${hostURL}/api/v1/index/update?force=${regenerate}&client=desktop`, formData, { headers });
|
|
|
|
requests.push(request);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Wait for requests batch to finish
|
|
|
|
Promise
|
|
|
|
.all(requests)
|
|
|
|
.then(responses => {
|
|
|
|
const lastSync = filesToPush
|
|
|
|
.filter(file => responses.find(response => response.data.includes(file)))
|
|
|
|
.map(file => ({ path: file, datetime: new Date().toISOString() }));
|
|
|
|
store.set('lastSync', lastSync);
|
|
|
|
})
|
|
|
|
.catch(error => {
|
|
|
|
console.error(error);
|
|
|
|
state["completed"] = false;
|
2024-02-26 19:53:36 +00:00
|
|
|
if (error?.response?.status === 429 && (BrowserWindow.getAllWindows().find(win => win.webContents.getURL().includes('config')))) {
|
2024-01-09 17:02:06 +00:00
|
|
|
state["error"] = `Looks like you're out of space to sync your files. <a href="https://app.khoj.dev/config">Upgrade your plan</a> to unlock more space.`;
|
2024-02-26 19:53:36 +00:00
|
|
|
const win = BrowserWindow.getAllWindows().find(win => win.webContents.getURL().includes('config'));
|
|
|
|
if (win) win.webContents.send('needsSubscription', true);
|
2024-01-09 17:02:06 +00:00
|
|
|
} else if (error?.code === 'ECONNREFUSED') {
|
|
|
|
state["error"] = `Could not connect to Khoj server. Ensure you can connect to it at ${error.address}:${error.port}.`;
|
|
|
|
} else {
|
2024-04-02 20:19:15 +00:00
|
|
|
currentTime = new Date();
|
2024-01-09 17:02:06 +00:00
|
|
|
state["error"] = `Sync was unsuccessful at ${currentTime.toLocaleTimeString()}. Contact team@khoj.dev to report this issue.`;
|
2024-01-03 17:38:20 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
.finally(() => {
|
2023-10-18 08:00:41 +00:00
|
|
|
// Syncing complete
|
2023-11-09 01:59:02 +00:00
|
|
|
syncing = false;
|
2024-02-11 10:35:28 +00:00
|
|
|
const win = BrowserWindow.getAllWindows().find(win => win.webContents.getURL().includes('config'));
|
|
|
|
if (win) {
|
2024-01-09 17:02:06 +00:00
|
|
|
win.webContents.send('update-state', state);
|
|
|
|
}
|
2024-01-03 17:38:20 +00:00
|
|
|
});
|
2023-09-06 19:04:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pushDataToKhoj();
|
|
|
|
|
2023-10-03 18:43:19 +00:00
|
|
|
async function handleFileOpen (type) {
|
|
|
|
let { canceled, filePaths } = {canceled: true, filePaths: []};
|
|
|
|
if (type === 'file') {
|
2024-04-09 10:37:18 +00:00
|
|
|
({ canceled, filePaths } = await dialog.showOpenDialog({properties: ['openFile' ], filters: [{ name: "Valid Khoj Files", extensions: validFileTypes }] }));
|
2023-10-03 18:43:19 +00:00
|
|
|
} else if (type === 'folder') {
|
|
|
|
({ canceled, filePaths } = await dialog.showOpenDialog({properties: ['openDirectory' ]}));
|
|
|
|
}
|
2023-09-06 19:04:18 +00:00
|
|
|
if (!canceled) {
|
|
|
|
const files = store.get('files') || [];
|
|
|
|
const folders = store.get('folders') || [];
|
|
|
|
|
|
|
|
for (const filePath of filePaths) {
|
|
|
|
console.log(filePath);
|
|
|
|
if (fs.existsSync(filePath)) {
|
|
|
|
const stats = fs.statSync(filePath);
|
|
|
|
if (stats.isFile()) {
|
|
|
|
console.log(`${filePath} is a file.`);
|
|
|
|
|
|
|
|
if (files.find((file) => file.path === filePath)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
files.push({path: filePath});
|
|
|
|
store.set('files', files);
|
|
|
|
} else if (stats.isDirectory()) {
|
|
|
|
console.log(`${filePath} is a directory.`);
|
|
|
|
|
|
|
|
if (folders.find((folder) => folder.path === filePath)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
folders.push({path: filePath});
|
|
|
|
store.set('folders', folders);
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
console.log(`${filePath} does not exist.`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return {
|
|
|
|
files: store.get('files'),
|
|
|
|
folders: store.get('folders')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-26 19:33:03 +00:00
|
|
|
async function getToken () {
|
|
|
|
return store.get('khojToken');
|
|
|
|
}
|
|
|
|
|
|
|
|
async function setToken (event, token) {
|
|
|
|
store.set('khojToken', token);
|
|
|
|
return store.get('khojToken');
|
|
|
|
}
|
|
|
|
|
2023-09-06 19:04:18 +00:00
|
|
|
async function getFiles () {
|
|
|
|
return store.get('files');
|
|
|
|
}
|
|
|
|
|
|
|
|
async function getFolders () {
|
|
|
|
return store.get('folders');
|
|
|
|
}
|
|
|
|
|
|
|
|
async function setURL (event, url) {
|
2023-11-01 00:59:53 +00:00
|
|
|
// Sanitize the URL. Remove trailing slash if present. Add http:// if not present.
|
|
|
|
url = url.replace(/\/$/, "");
|
|
|
|
if (!url.match(/^[a-zA-Z]+:\/\//)) {
|
|
|
|
url = `http://${url}`;
|
|
|
|
}
|
|
|
|
|
2023-09-06 19:04:18 +00:00
|
|
|
store.set('hostURL', url);
|
|
|
|
return store.get('hostURL');
|
|
|
|
}
|
|
|
|
|
|
|
|
async function getURL () {
|
|
|
|
return store.get('hostURL');
|
|
|
|
}
|
|
|
|
|
|
|
|
async function removeFile (event, filePath) {
|
|
|
|
const files = store.get('files');
|
|
|
|
const newFiles = files.filter((file) => file.path !== filePath);
|
|
|
|
store.set('files', newFiles);
|
|
|
|
return newFiles;
|
|
|
|
}
|
|
|
|
|
|
|
|
async function removeFolder (event, folderPath) {
|
|
|
|
const folders = store.get('folders');
|
|
|
|
const newFolders = folders.filter((folder) => folder.path !== folderPath);
|
|
|
|
store.set('folders', newFolders);
|
|
|
|
return newFolders;
|
|
|
|
}
|
|
|
|
|
2023-09-12 23:35:07 +00:00
|
|
|
async function syncData (regenerate = false) {
|
2023-09-06 19:04:18 +00:00
|
|
|
try {
|
2024-04-12 06:19:25 +00:00
|
|
|
pushDataToKhoj(regenerate);
|
2023-09-06 19:04:18 +00:00
|
|
|
const date = new Date();
|
|
|
|
console.log('Pushing data to Khoj at: ', date);
|
|
|
|
} catch (err) {
|
|
|
|
console.error(err);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-07 11:37:16 +00:00
|
|
|
async function deleteAllFiles () {
|
|
|
|
try {
|
|
|
|
store.set('files', []);
|
|
|
|
store.set('folders', []);
|
2024-04-12 06:19:25 +00:00
|
|
|
pushDataToKhoj(true);
|
2023-11-07 11:37:16 +00:00
|
|
|
const date = new Date();
|
|
|
|
console.log('Pushing data to Khoj at: ', date);
|
|
|
|
} catch (err) {
|
|
|
|
console.error(err);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-07 15:28:43 +00:00
|
|
|
// Fetch user info from Khoj server
|
|
|
|
async function getUserInfo() {
|
|
|
|
const getUserInfoURL = `${store.get('hostURL') || KHOJ_URL}/api/v1/user?client=desktop`;
|
|
|
|
const headers = { 'Authorization': `Bearer ${store.get('khojToken')}` };
|
|
|
|
try {
|
|
|
|
let response = await axios.get(getUserInfoURL, { headers });
|
|
|
|
return response.data;
|
|
|
|
} catch (err) {
|
|
|
|
console.error(err);
|
|
|
|
}
|
|
|
|
}
|
2023-11-07 11:37:16 +00:00
|
|
|
|
2023-11-03 09:46:39 +00:00
|
|
|
let firstRun = true;
|
2023-10-26 19:33:03 +00:00
|
|
|
let win = null;
|
2024-04-07 06:21:54 +00:00
|
|
|
let titleBarStyle = process.platform === 'win32' ? 'default' : 'hidden';
|
2023-11-03 03:40:35 +00:00
|
|
|
const createWindow = (tab = 'chat.html') => {
|
2023-10-26 19:33:03 +00:00
|
|
|
win = new BrowserWindow({
|
2023-09-06 19:04:18 +00:00
|
|
|
width: 800,
|
|
|
|
height: 800,
|
2023-11-03 09:46:39 +00:00
|
|
|
show: false,
|
2024-04-07 06:21:54 +00:00
|
|
|
titleBarStyle: titleBarStyle,
|
|
|
|
autoHideMenuBar: true,
|
2023-09-06 19:04:18 +00:00
|
|
|
webPreferences: {
|
|
|
|
preload: path.join(__dirname, 'preload.js'),
|
|
|
|
nodeIntegration: true,
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2024-04-12 06:19:25 +00:00
|
|
|
const job = new cron('0 */10 * * * *', function() {
|
2023-09-06 19:04:18 +00:00
|
|
|
try {
|
2024-04-12 06:19:25 +00:00
|
|
|
pushDataToKhoj();
|
2023-09-06 19:04:18 +00:00
|
|
|
const date = new Date();
|
|
|
|
console.log('Pushing data to Khoj at: ', date);
|
|
|
|
win.webContents.send('update-state', state);
|
|
|
|
} catch (err) {
|
|
|
|
console.error(err);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
win.setResizable(true);
|
|
|
|
win.setOpacity(0.95);
|
2023-11-03 09:46:39 +00:00
|
|
|
win.setBackgroundColor('#f5f4f3');
|
2023-09-06 19:04:18 +00:00
|
|
|
win.setHasShadow(true);
|
|
|
|
|
2024-02-24 22:22:06 +00:00
|
|
|
// Open external links in link handler registered on OS (e.g. browser)
|
|
|
|
win.webContents.setWindowOpenHandler(async ({ url }) => {
|
2024-03-13 20:59:22 +00:00
|
|
|
let shouldOpen = { response: 0 };
|
2024-02-24 22:22:06 +00:00
|
|
|
|
2024-03-13 20:59:22 +00:00
|
|
|
if (!url.startsWith(store.get('hostURL'))) {
|
|
|
|
// Confirm before opening external links
|
2024-02-24 22:22:06 +00:00
|
|
|
const confirmNotice = `Do you want to open this link? It will be handled by an external application.\n\n${url}`;
|
|
|
|
shouldOpen = await dialog.showMessageBox({
|
|
|
|
type: 'question',
|
|
|
|
buttons: ['Yes', 'No'],
|
|
|
|
defaultId: 1,
|
|
|
|
title: 'Confirm',
|
|
|
|
message: confirmNotice,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// If user confirms, let OS link handler open the link in appropriate app
|
|
|
|
if (shouldOpen.response === 0) shell.openExternal(url);
|
|
|
|
|
|
|
|
// Do not open external links within the app
|
|
|
|
return { action: 'deny' };
|
|
|
|
});
|
|
|
|
|
2023-09-06 19:04:18 +00:00
|
|
|
job.start();
|
|
|
|
|
2023-10-26 19:33:03 +00:00
|
|
|
win.loadFile(tab)
|
2023-11-03 09:46:39 +00:00
|
|
|
|
|
|
|
if (firstRun === true) {
|
|
|
|
firstRun = false;
|
|
|
|
|
|
|
|
// Create splash screen
|
2023-12-29 04:50:48 +00:00
|
|
|
let splash = new BrowserWindow({width: 400, height: 400, transparent: true, frame: false, alwaysOnTop: true});
|
2023-11-04 05:20:11 +00:00
|
|
|
splash.setOpacity(1.0);
|
2023-11-03 09:46:39 +00:00
|
|
|
splash.setBackgroundColor('#d16b4e');
|
|
|
|
splash.loadFile('splash.html');
|
|
|
|
|
|
|
|
// Show splash screen on app load
|
|
|
|
win.once('ready-to-show', () => {
|
|
|
|
setTimeout(function(){ splash.close(); win.show(); }, 4500);
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
// Show main window directly if not first run
|
|
|
|
win.once('ready-to-show', () => { win.show(); });
|
|
|
|
}
|
2023-09-06 19:04:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
app.whenReady().then(() => {
|
|
|
|
ipcMain.on('set-title', handleSetTitle);
|
|
|
|
|
2023-10-03 18:43:19 +00:00
|
|
|
ipcMain.handle('handleFileOpen', (event, type) => {
|
|
|
|
return handleFileOpen(type);
|
|
|
|
});
|
2023-09-06 19:04:18 +00:00
|
|
|
|
|
|
|
ipcMain.on('update-state', (event, arg) => {
|
|
|
|
console.log(arg);
|
|
|
|
event.reply('update-state', arg);
|
|
|
|
});
|
|
|
|
|
2023-11-26 06:31:23 +00:00
|
|
|
ipcMain.on('needsSubscription', (event, arg) => {
|
|
|
|
console.log(arg);
|
|
|
|
event.reply('needsSubscription', arg);
|
|
|
|
});
|
|
|
|
|
2023-11-16 06:33:50 +00:00
|
|
|
ipcMain.on('navigate', (event, page) => {
|
|
|
|
win.loadFile(page);
|
|
|
|
});
|
|
|
|
|
|
|
|
ipcMain.on('navigateToWebApp', (event, page) => {
|
|
|
|
shell.openExternal(`${store.get('hostURL')}/${page}`);
|
|
|
|
});
|
|
|
|
|
2023-09-06 19:04:18 +00:00
|
|
|
ipcMain.handle('getFiles', getFiles);
|
|
|
|
ipcMain.handle('getFolders', getFolders);
|
|
|
|
|
|
|
|
ipcMain.handle('removeFile', removeFile);
|
|
|
|
ipcMain.handle('removeFolder', removeFolder);
|
|
|
|
|
|
|
|
ipcMain.handle('setURL', setURL);
|
|
|
|
ipcMain.handle('getURL', getURL);
|
|
|
|
|
2023-10-26 19:33:03 +00:00
|
|
|
ipcMain.handle('setToken', setToken);
|
|
|
|
ipcMain.handle('getToken', getToken);
|
2024-04-07 15:28:43 +00:00
|
|
|
ipcMain.handle('getUserInfo', getUserInfo);
|
2023-10-26 19:33:03 +00:00
|
|
|
|
2023-09-12 23:35:07 +00:00
|
|
|
ipcMain.handle('syncData', (event, regenerate) => {
|
|
|
|
syncData(regenerate);
|
|
|
|
});
|
2023-11-07 11:37:16 +00:00
|
|
|
ipcMain.handle('deleteAllFiles', deleteAllFiles);
|
2023-09-06 19:04:18 +00:00
|
|
|
|
2024-04-07 16:18:21 +00:00
|
|
|
createWindow();
|
|
|
|
|
2023-09-06 19:04:18 +00:00
|
|
|
|
|
|
|
app.setAboutPanelOptions({
|
|
|
|
applicationName: "Khoj",
|
2023-11-04 03:56:27 +00:00
|
|
|
applicationVersion: khojPackage.version,
|
|
|
|
version: khojPackage.version,
|
|
|
|
authors: "Saba Imran, Debanjum Singh Solanky and contributors",
|
2023-09-06 19:04:18 +00:00
|
|
|
website: "https://khoj.dev",
|
2023-11-04 03:56:27 +00:00
|
|
|
copyright: "GPL v3",
|
|
|
|
iconPath: path.join(__dirname, 'assets', 'icons', 'favicon-128x128.png')
|
2023-09-06 19:04:18 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
app.on('ready', async() => {
|
|
|
|
try {
|
|
|
|
const result = await todesktop.autoUpdater.checkForUpdates();
|
|
|
|
if (result.updateInfo) {
|
2024-04-09 10:33:25 +00:00
|
|
|
console.log("Desktop app update found:", result.updateInfo.version);
|
2023-09-06 19:04:18 +00:00
|
|
|
todesktop.autoUpdater.restartAndInstall();
|
|
|
|
}
|
|
|
|
} catch (e) {
|
2024-04-09 10:33:25 +00:00
|
|
|
console.warn("Desktop app update check failed:", e);
|
2023-09-06 19:04:18 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
app.on('activate', () => {
|
|
|
|
if (BrowserWindow.getAllWindows().length === 0) createWindow()
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
app.on('window-all-closed', () => {
|
|
|
|
if (process.platform !== 'darwin') app.quit()
|
|
|
|
})
|
2023-10-26 19:33:03 +00:00
|
|
|
|
2023-11-04 03:56:27 +00:00
|
|
|
/*
|
|
|
|
** About Page
|
|
|
|
*/
|
|
|
|
|
|
|
|
let aboutWindow;
|
|
|
|
|
|
|
|
function openAboutWindow() {
|
|
|
|
if (aboutWindow) { aboutWindow.focus(); return; }
|
|
|
|
|
|
|
|
aboutWindow = new BrowserWindow({
|
|
|
|
width: 400,
|
|
|
|
height: 400,
|
2024-04-07 06:21:54 +00:00
|
|
|
titleBarStyle: titleBarStyle,
|
|
|
|
autoHideMenuBar: true,
|
2023-11-04 03:56:27 +00:00
|
|
|
show: false,
|
|
|
|
webPreferences: {
|
|
|
|
preload: path.join(__dirname, 'preload.js'),
|
|
|
|
nodeIntegration: true,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
aboutWindow.loadFile('about.html');
|
|
|
|
|
|
|
|
// Pass OS, Khoj version to About page
|
|
|
|
aboutWindow.webContents.on('did-finish-load', () => {
|
|
|
|
aboutWindow.webContents.send('appInfo', { version: khojPackage.version, platform: process.platform });
|
|
|
|
});
|
|
|
|
|
|
|
|
// Open links in external browser
|
|
|
|
aboutWindow.webContents.setWindowOpenHandler(({ url }) => {
|
|
|
|
shell.openExternal(url);
|
|
|
|
return { action: 'deny' };
|
|
|
|
});
|
|
|
|
|
|
|
|
aboutWindow.once('ready-to-show', () => { aboutWindow.show(); });
|
|
|
|
aboutWindow.on('closed', () => { aboutWindow = null; });
|
|
|
|
}
|
|
|
|
|
2023-10-26 19:33:03 +00:00
|
|
|
/*
|
|
|
|
** System Tray Icon
|
|
|
|
*/
|
|
|
|
|
|
|
|
let tray
|
|
|
|
|
|
|
|
openWindow = (page) => {
|
|
|
|
if (BrowserWindow.getAllWindows().length === 0) {
|
|
|
|
createWindow(page);
|
|
|
|
} else {
|
|
|
|
win.loadFile(page); win.show();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
app.whenReady().then(() => {
|
2023-12-03 07:52:01 +00:00
|
|
|
const iconPath = path.join(__dirname, './assets/icons/favicon-20x20.png')
|
|
|
|
const icon = nativeImage.createFromPath(iconPath)
|
2023-10-26 19:33:03 +00:00
|
|
|
tray = new Tray(icon)
|
|
|
|
|
|
|
|
const contextMenu = Menu.buildFromTemplate([
|
|
|
|
{ label: 'Chat', type: 'normal', click: () => { openWindow('chat.html'); }},
|
2023-11-03 03:40:35 +00:00
|
|
|
{ label: 'Search', type: 'normal', click: () => { openWindow('search.html') }},
|
2023-10-26 19:33:03 +00:00
|
|
|
{ label: 'Configure', type: 'normal', click: () => { openWindow('config.html') }},
|
|
|
|
{ type: 'separator' },
|
2023-11-04 03:56:27 +00:00
|
|
|
{ label: 'About Khoj', type: 'normal', click: () => { openAboutWindow(); } },
|
2023-10-26 19:33:03 +00:00
|
|
|
{ label: 'Quit', type: 'normal', click: () => { app.quit() } }
|
|
|
|
])
|
|
|
|
|
|
|
|
tray.setToolTip('Khoj')
|
|
|
|
tray.setContextMenu(contextMenu)
|
|
|
|
})
|