NoteChat Logo Notechat
Notechat is a desktop application that enables you to interact with your Apple Notes through a chat interface. Built with Electron and React, it provides a seamless experience for conversing with your notes.
π Download NoteChat Cloud Mode
We welcome contributions from the community! Feel free to open a Pull Request.
Vite is used as the development server and build tool for the React (src/ui) part of the application. Electron handles the desktop application wrapper and native system interactions (src/electron).
. βββ README.md βββ data/ # dir for dev database file βββ dist/ # Compiled Electron files βββ dist-electron/ # Compiled Electron files βββ dist-react/ # Built React application βββ electron-builder.json # Electron build configuration βββ index.html βββ package-lock.json βββ package.json # Project metadata and dependencies βββ src β βββ electron/ # Electron main process code β β βββ main.ts # Main electron process β β βββ database β β β βββ databaseService.ts # Initializes schema, handles CRUD with better-sqlite3 β β βββ noteProcessing β β β βββ CharChunker.ts # chunks note body by into max chunks, prefixes note title to chunk β β β βββ extractAndEmbedNotes.ts # extracts, chunks, embedds and saves notes β β βββ tsconfig.json # TypeScript config for electron β β βββ ollamaSetup.ts β β βββ pathResolver.ts β β βββ preload.cts β β βββ util.ts β βββ types β β βββ electron.d.ts # types from electron used in frontend β βββ ui/ # React frontend code β βββ App.tsx # Main React component β βββ assets/ # Static assets β βββ App.css β βββ index.css β βββ main.tsx β βββ constants/ β βββ components/ # reusable components βββ vite.config.ts # Vite bundler configuration βββ tsconfig.json # TypeScript config for react
# Install dependencies npm install # Start development environment React and Electron in development mode (hot reloading) npm run dev # Compiles TypeScript and builds the application npm run build # For a clean build macOS distribution (ARM64) npm run clean && npm run dist:mac
Running packaged App from Terminal
run the app from /dist
# Mount (open) a DMG file hdiutil attach dist/NoteChat-0.0.0-arm64.dmg # Navigate to the Mounted Volume cd /Volumes/NoteChat\ 0.0.0-arm64 # Run the Application ./NoteChat.app/Contents/MacOS/NoteChat
run the app from Applications folder
# run the application from terminal (to view logs) /Applications/NoteChat.app/Contents/MacOS/NoteChat # find location of app mdfind "kMDItemDisplayName == 'NoteChat'"
-
In the
preload.cts, everything attached viacontextBridgeis added to thewindowobject under the keywordelectron.electron.contextBridge.exposeInMainWorld("electron", { getStaticData: () => console.log("static"), // other methods });
In the browser's console within the app, you can execute
window.electron.getStaticData(), and it will log "static".
Here's the updated documentation with real examples from your chat application:
Electron's IPC system enables secure communication between the main process (Node.js) and renderer process (browser).
The preload.cts script acts as a bridge, exposing only specific functions and data to the renderer process through the contextBridge.
Basic Structure:
- Preload Script (preload.cts): Defines what the renderer can access
- Main Process (main.ts): Handles the actual functionality
- Renderer Process: Uses the exposed functions in React components
In main.ts, we ensure the preload.cts is run before opening the window:
const mainWindow = new BrowserWindow({ webPreferences: { preload: getPreloadPath(), }, });
The preload.cts script defines the API that will be available to the renderer process. We use TypeScript interfaces to ensure type safety:
const { contextBridge, ipcRenderer } = require("electron"); // Define the API types interface ElectronAPI { checkOllamaStatus: () => Promise<{ running: boolean; error?: string }>; startOllama: () => Promise<boolean>; } // Expose the API to the renderer process contextBridge.exposeInMainWorld("electron", { checkOllamaStatus: () => ipcRenderer.invoke("checkOllamaStatus"), startOllama: () => ipcRenderer.invoke("startOllama"), } as ElectronAPI);
In the main.ts process, we handle the IPC calls defined in the preload script. Each handler performs its specific task and returns the result:
app.on("ready", () => { // Handle Ollama status checks ipcMain.handle("checkOllamaStatus", async () => { try { const isRunning = await checkOllamaRunning(); return { running: isRunning }; } catch (error) { return { running: false, error: error.message }; } }); });
In your React components, you can now safely call these methods through the window.electron object:
function Chat() { useEffect(() => { const checkStatus = async () => { const status = await window.electron.checkOllamaStatus(); setOllamaStatus(status); }; checkStatus(); }, []); const handleStartOllama = async () => { const success = await window.electron.startOllama(); if (success) { // Handle successful start } }; }
Define the window interface in electron.d.ts to ensure TypeScript recognizes the electron API:
declare global { interface Window { electron: { checkOllamaStatus: () => Promise<{ running: boolean; error?: string }>; startOllama: () => Promise<boolean>; }; } }
You can test these IPC methods directly in the browser's DevTools console:
// Check Ollama status await window.electron.checkOllamaStatus(); // Returns: { running: true } // Start Ollama await window.electron.startOllama(); // Returns: true
This implementation provides a secure and type-safe way to communicate between processes in your Electron application. The preload script acts as a security boundary, exposing only the necessary functionality to the renderer process while maintaining the isolation between Node.js and browser contexts.
The databaseService.ts file defines a DatabaseService class that manages interactions with a SQLite database using the better-sqlite3 library. It is designed as a singleton, ensuring only one instance of the database connection is active at any time. This service handles the initialization of the database schema, ensuring necessary tables exist, and provides methods for saving notes and chunks of data. It also supports vector operations using sqlite-vec for tasks like finding similar data entries based on vector similarity.
need to load sqlite-vec extension like this:
const extensionPath = path.join( app.getAppPath().replace('app.asar', 'app.asar.unpacked'), 'node_modules/sqlite-vec-darwin-arm64/vec0.dylib' ); this.vectorDb.loadExtension(extensionPath);
because node api uses meta.url() to detect path which doesnβt work for packaged electron version since it detects app.asar as folder but its actually in app.asar.unpacked, the above fixes: `
SqliteError: dlopen(/Users/matsfunke/dev/AppName/AppName.app/Contents/Resources/app.asar/node_modules/sqlite-vec-darwin-arm64/vec0.dylib.dylib, 0x000A): tried: '/Users/matsfunke/dev/AppName/AppName.app/Contents/Resources/app.asar/node_modules/sqlite-vec-darwin-arm64/vec0.dylib.dylib' (errno=20)If you encounter the following error:
Failed to initialize application: Error: The module '.../better_sqlite3.node' was compiled against a different Node.js version using NODE_MODULE_VERSION 115.
This version of Node.js requires NODE_MODULE_VERSION 123.Follow these steps to resolve it:
# Remove existing node_modules and package-lock.json rm -rf node_modules package-lock.json # Clear npm cache npm cache clean --force # Reinstall dependencies npm install # Rebuild native modules for Electron npx electron-rebuild # Start the application npm run dev