How the Local Inference Client Interacts with the Electron Main Process in Open-Generative-AI

The local inference client communicates with the Electron main process through a secure IPC bridge that exposes window.localAI to the renderer, using ipcRenderer.invoke for request-response patterns and ipcRenderer.on for streaming progress events.

The Open-Generative-AI repository implements a three-layer architecture that separates the React frontend from privileged Node.js operations. This design allows the same UI code to run in web-only mode while enabling powerful local inference when wrapped in Electron.

Architecture Overview

The IPC stack consists of three distinct layers that handle communication between the renderer and main process:

This structure ensures that the renderer process never accesses Node.js APIs directly, maintaining Electron's security model while enabling complex operations like downloading the sd-cli binary and spawning image generation tasks.

The Preload Bridge

The electron/preload.js file creates the secure connection point between frontend and backend. It uses contextBridge.exposeInMainWorld to inject a localAI object containing methods that wrap ipcRenderer.

// electron/preload.js
onDownloadProgress: (callback) => {
  const listener = (_, data) => callback(data);
  ipcRenderer.on('local-ai:download-progress', listener);
  return () => ipcRenderer.removeListener('local-ai:download-progress', listener);
},

The bridge exposes functions like listModels(), downloadModel(), and generate(), each mapping to specific IPC channels prefixed with local-ai:.

The Renderer Client

The src/lib/localInferenceClient.js file provides the frontend interface. It first checks for the bridge's presence to determine if the app is running in Electron or web mode.

// src/lib/localInferenceClient.js
export const isLocalAIAvailable = () =>
  typeof window !== 'undefined' && !!window.localAI?.isElectron;

When available, the client forwards method calls to window.localAI, which then triggers the underlying IPC mechanisms. This abstraction allows components to call localAI.listModels() without knowing the implementation details.

Main Process Handlers

The electron/lib/localInference.js file registers IPC handlers using ipcMain.handle and manages the actual execution of local inference tasks. The register() function sets up listeners for all supported operations.

// electron/lib/localInference.js
function register() {
  ipcMain.handle('local-ai:list-models', () => listModels());
}

For long-running operations, the main process pushes progress updates to the renderer using webContents.send. During binary downloads, it emits events on the local-ai:download-progress channel with payload containing id, phase, and progress fields.

Communication Flows

Request-Response Pattern

Synchronous operations follow a standard invoke/handle cycle:

  1. Renderer calls window.localAI.listModels()
  2. Preload bridge executes ipcRenderer.invoke('local-ai:list-models')
  3. Main process handler executes listModels() and returns the array
  4. Promise resolves in the renderer with available model data

Event-Driven Updates

For asynchronous progress tracking, the system uses one-way event emission:

  • Main process calls mainWindow?.webContents.send('local-ai:download-progress', data)
  • Preload bridge receives via ipcRenderer.on and forwards to the registered callback
  • Renderer client unsubscribes using the cleanup function returned during subscription

Implementation Example

Here is a complete React component demonstrating the client usage:

import { localAI } from '@/lib/localInferenceClient';

// Check that the Electron bridge is present
if (!localAI) {
  console.warn('Running in web-only mode – local inference unavailable.');
}

// Load the model list on mount
useEffect(() => {
  async function load() {
    const models = await localAI.listModels();
    setModels(models);
  }
  load();
}, []);

// Subscribe to download progress
useEffect(() => {
  const unsub = localAI.onDownloadProgress((data) => {
    console.log('Download progress:', data);
    // data: { id, phase, progress }
  });
  return unsub;
}, []);

// Trigger a generation
async function generateImage(prompt) {
  const result = await localAI.generate({
    model: 'z-image-1',
    prompt,
    steps: 30,
    guidance_scale: 7.5,
    aspect_ratio: '1:1',
  });
  // result: { url: 'data:image/png;base64,...', seed }
  setImageSrc(result.url);
}

Summary

  • The local inference client relies on electron/preload.js to safely expose IPC capabilities to the renderer process.
  • Availability detection via isLocalAIAvailable() ensures graceful degradation in web-only environments.
  • Request handling uses ipcMain.handle in electron/lib/localInference.js for operations like listModels() and generate().
  • Progress streaming employs webContents.send from the main process to push real-time updates through the preload bridge.
  • Binary management occurs entirely in the main process, which downloads the sd-cli executable and manages model files via electron/lib/modelCatalog.js.

Frequently Asked Questions

How does the renderer know if local inference is available?

The renderer checks window.localAI?.isElectron via the isLocalAIAvailable() function in src/lib/localInferenceClient.js. This boolean indicates whether the preload bridge injected the API, confirming the app is running inside Electron rather than a standard browser.

Can the frontend directly access the filesystem or spawn processes?

No. According to Electron's security architecture implemented in this repository, the renderer process cannot access Node.js APIs directly. All filesystem operations and binary spawning occur in electron/lib/localInference.js within the main process, which receives sanitized parameters through IPC channels and returns results via promises.

What happens if a user downloads a model while generating an image?

The main process handles both operations concurrently. Download progress events stream via local-ai:download-progress while generation updates use local-ai:progress. Both channels operate independently, allowing the UI to display multiple progress indicators simultaneously without blocking the main thread.

Where is the sd-cli binary actually downloaded and executed?

The electron/lib/localInference.js file manages the binary lifecycle. It downloads the sd-cli executable to the application's data directory, verifies its presence, and spawns it as a child process when generate() is called, capturing stdout to stream progress back to the renderer.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →