Als langjähriger Full-Stack-Entwickler habe ich zahlreiche Desktop-AI-Anwendungen gebaut. In diesem Tutorial zeige ich Ihnen, wie Sie mit Electron eine leistungsstarke AI-Desktop-Anwendung entwickeln, die Streaming-API-Aufrufe und lokale Caching-Strategien optimal kombiniert.

Vergleich: HolySheep vs. Offizielle API vs. Andere Relay-Dienste

Funktion HolySheep AI Offizielle API Andere Relay-Dienste
Preis (GPT-4.1) $8/MTok (85%+ günstiger) $60/MTok $15-30/MTok
Claude Sonnet 4.5 $15/MTok $90/MTok $25-50/MTok
DeepSeek V3.2 $0.42/MTok $0.55/MTok $0.50-0.60/MTok
Latenz <50ms 100-300ms 60-150ms
Streaming-Support ✓ Vollständig ✓ Vollständig Variiert
Zahlungsmethoden WeChat/Alipay/Kreditkarte Nur Kreditkarte Variiert
Kostenlose Credits ✓ Inklusive Selten

Mit Jetzt registrieren erhalten Sie sofortigen Zugang zu diesen Vorteilen!

Projektstruktur und Grundlagen

Bevor wir mit dem Code beginnen, erstellen wir die Projektstruktur:


mkdir electron-ai-assistant
cd electron-ai-assistant
npm init -y
npm install [email protected] [email protected]
npm install [email protected] [email protected]
npm install --save-dev [email protected] @types/[email protected]

Die package.json sollte如下 aussehen:

{
  "name": "electron-ai-assistant",
  "version": "1.0.0",
  "main": "dist/main.js",
  "scripts": {
    "build": "tsc",
    "start": "electron .",
    "dev": "npm run build && electron ."
  },
  "dependencies": {
    "electron-store": "^8.1.0",
    "node-fetch": "^2.7.0"
  },
  "devDependencies": {
    "electron": "^28.0.0",
    "electron-builder": "^24.9.1",
    "typescript": "^5.3.3",
    "@types/node": "^20.10.0",
    "@types/node-fetch": "^2.6.9"
  }
}

Hauptprozess mit IPC-Kommunikation

Der Electron-Hauptprozess verarbeitet API-Anfragen sicher und implementiert ein robustes Caching-System:

// src/main.ts
import { app, BrowserWindow, ipcMain } from 'electron';
import * as path from 'path';
import Store from 'electron-store';
import fetch from 'node-fetch';

interface CacheEntry {
  response: string;
  timestamp: number;
  tokenCount: number;
}

interface ApiConfig {
  baseUrl: string;
  apiKey: string;
  cacheEnabled: boolean;
  cacheTTL: number; // milliseconds
}

const store = new Store<{ config: ApiConfig; cache: Record }>({
  defaults: {
    config: {
      baseUrl: 'https://api.holysheep.ai/v1',
      apiKey: 'YOUR_HOLYSHEEP_API_KEY',
      cacheEnabled: true,
      cacheTTL: 3600000 // 1 hour
    },
    cache: {}
  }
});

let mainWindow: BrowserWindow | null = null;

function createWindow(): void {
  mainWindow = new BrowserWindow({
    width: 1200,
    height: 800,
    webPreferences: {
      nodeIntegration: false,
      contextIsolation: true,
      preload: path.join(__dirname, 'preload.js')
    },
    title: 'HolySheep AI Assistant'
  });

  mainWindow.loadFile(path.join(__dirname, '../index.html'));
  
  mainWindow.on('closed', () => {
    mainWindow = null;
  });
}

function generateCacheKey(messages: any[]): string {
  return Buffer.from(JSON.stringify(messages)).toString('base64').substring(0, 64);
}

async function callStreamingApi(
  messages: any[],
  onChunk: (chunk: string) => void,
  onComplete: () => void,
  onError: (error: Error) => void
): Promise {
  const config = store.get('config');
  const cacheKey = generateCacheKey(messages);
  
  // Cache prüfen
  if (config.cacheEnabled) {
    const cache = store.get('cache');
    const cached = cache[cacheKey];
    
    if (cached && Date.now() - cached.timestamp < config.cacheTTL) {
      console.log('Using cached response');
      for (const char of cached.response) {
        onChunk(char);
        await new Promise(r => setTimeout(r, 5)); // Streaming simulieren
      }
      onComplete();
      return;
    }
  }

  try {
    const response = await fetch(${config.baseUrl}/chat/completions, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': Bearer ${config.apiKey}
      },
      body: JSON.stringify({
        model: 'gpt-4.1',
        messages: messages,
        stream: true
      })
    });

    if (!response.ok) {
      throw new Error(API Error: ${response.status} ${response.statusText});
    }

    const reader = response.body?.getReader();
    const decoder = new TextDecoder();
    let fullResponse = '';

    if (!reader) {
      throw new Error('No response body');
    }

    while (true) {
      const { done, value } = await reader.read();
      
      if (done) break;
      
      const chunk = decoder.decode(value, { stream: true });
      const lines = chunk.split('\n');
      
      for (const line of lines) {
        if (line.startsWith('data: ')) {
          const data = line.slice(6);
          if (data === '[DONE]') continue;
          
          try {
            const parsed = JSON.parse(data);
            const content = parsed.choices?.[0]?.delta?.content || '';
            if (content) {
              fullResponse += content;
              onChunk(content);
            }
          } catch (e) {
            // Ignore parse errors for incomplete JSON
          }
        }
      }
    }

    // Ergebnis cachen
    if (config.cacheEnabled && fullResponse) {
      const cache = store.get('cache');
      cache[cacheKey] = {
        response: fullResponse,
        timestamp: Date.now(),
        tokenCount: Math.ceil(fullResponse.length / 4)
      };
      store.set('cache', cache);
    }

    onComplete();
  } catch (error) {
    onError(error as Error);
  }
}

// IPC-Handler registrieren
ipcMain.handle('api:stream', async (event, messages: any[]) => {
  return new Promise((resolve, reject) => {
    let fullResponse = '';
    
    callStreamingApi(
      messages,
      (chunk) => {
        fullResponse += chunk;
        event.sender.send('api:chunk', chunk);
      },
      () => {
        resolve({ success: true, response: fullResponse });
      },
      (error) => {
        reject(error);
      }
    );
  });
});

ipcMain.handle('config:get', () => store.get('config'));
ipcMain.handle('config:set', (_, config: ApiConfig) => {
  store.set('config', config);
  return true;
});

ipcMain.handle('cache:clear', () => {
  store.set('cache', {});
  return true;
});

app.whenReady().then(createWindow);

app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

app.on('activate', () => {
  if (mainWindow === null) {
    createWindow();
  }
});

Preload-Skript für sichere Brücke

// src/preload.ts
import { contextBridge, ipcRenderer } from 'electron';

interface ApiConfig {
  baseUrl: string;
  apiKey: string;
  cacheEnabled: boolean;
  cacheTTL: number;
}

contextBridge.exposeInMainWorld('electronAPI', {
  streamChat: (messages: any[]) => ipcRenderer.invoke('api:stream', messages),
  
  onChunk: (callback: (chunk: string) => void) => {
    const handler = (_: any, chunk: string) => callback(chunk);
    ipcRenderer.on('api:chunk', handler);
    return () => ipcRenderer.removeListener('api:chunk', handler);
  },
  
  getConfig: () => ipcRenderer.invoke('config:get'),
  setConfig: (config: ApiConfig) => ipcRenderer.invoke('config:set', config),
  
  clearCache: () => ipcRenderer.invoke('cache:clear'),
  
  showError: (title: string, content: string) => {
    const { dialog } = require('electron');
    dialog.showErrorBox(title, content);
  }
});

Frontend mit Streaming-UI

<!-- index.html -->
<!DOCTYPE html>
<html lang="de">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>HolySheep AI Assistant</title>
  <style>
    * { box-sizing: border-box; margin: 0; padding: 0; }
    
    body {
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
      background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
      color: #fff;
      height: 100vh;
      display: flex;
      flex-direction: column;
    }
    
    header {
      background: rgba(255,255,255,0.1);
      padding: 15px 30px;
      display: flex;
      justify-content: space-between;
      align-items: center;
      border-bottom: 1px solid rgba(255,255,255,0.1);
    }
    
    h1 { font-size: 1.5rem; }
    
    .settings-btn {
      background: #4a90d9;
      border: none;
      color: white;
      padding: 10px 20px;
      border-radius: 8px;
      cursor: pointer;
      font-size: 0.9rem;
    }
    
    .settings-btn:hover { background: #3a7fc9; }
    
    #chat-container {
      flex: 1;
      overflow-y: auto;
      padding: 30px;
      display: flex;
      flex-direction: column;
      gap: 20px;
    }
    
    .message {
      max-width: 80%;
      padding: 15px 20px;
      border-radius: 15px;
      line-height: 1.6;
      animation: fadeIn 0.3s ease;
    }
    
    @keyframes fadeIn {
      from { opacity: 0; transform: translateY(10px); }
      to { opacity: 1; transform: translateY(0); }
    }
    
    .user-message {
      background: #4a90d9;
      align-self: flex-end;
      border-bottom-right-radius: 5px;
    }
    
    .assistant-message {
      background: rgba(255,255,255,0.15);
      align-self: flex-start;
      border-bottom-left-radius: 5px;
    }
    
    .typing-indicator {
      display: flex;
      gap: 5px;
      padding: 15px 20px;
      background: rgba(255,255,255,0.15);
      border-radius: 15px;
      width: fit-content;
    }
    
    .typing-indicator span {
      width: 8px;
      height: 8px;
      background: #fff;
      border-radius: 50%;
      animation: bounce 1.4s infinite ease-in-out;
    }
    
    .typing-indicator span:nth-child(1) { animation-delay: 0s; }
    .typing-indicator span:nth-child(2) { animation-delay: 0.2s; }
    .typing-indicator span:nth-child(3) { animation-delay: 0.4s; }
    
    @keyframes bounce {
      0%, 80%, 100% { transform: scale(0.6); opacity: 0.5; }
      40% { transform: scale(1); opacity: 1; }
    }
    
    #input-container {
      padding: 20px 30px;
      background: rgba(255,255,255,0.05);
      border-top: 1px solid rgba(255,255,255,0.1);
      display: flex;
      gap: 15px;
    }
    
    #message-input {
      flex: 1;
      padding: 15px 20px;
      border: 1px solid rgba(255,255,255,0.2);
      border-radius: 25px;
      background: rgba(255,255,255,0.1);
      color: #fff;
      font-size: 1rem;
      resize: none;
    }
    
    #message-input:focus {
      outline: none;
      border-color: #4a90d9;
    }
    
    #send-btn {
      background: #4a90d9;
      border: none;
      color: white;
      padding: 15px 30px;
      border-radius: 25px;
      cursor: pointer;
      font-size: 1rem;
      font-weight: bold;
      transition: background 0.3s;
    }
    
    #send-btn:hover { background: #3a7fc9; }
    #send-btn:disabled { background: #666; cursor: not-allowed; }
    
    .modal {
      display: none;
      position: fixed;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      background: rgba(0,0,0,0.7);
      justify-content: center;
      align-items: center;
      z-index: 1000;
    }
    
    .modal.active { display: flex; }
    
    .modal-content {
      background: #1a1a2e;
      padding: 30px;
      border-radius: 15px;
      width: 500px;
      max-width: 90%;
    }
    
    .modal-content h2 { margin-bottom: 20px; }
    
    .form-group {
      margin-bottom: 15px;
    }
    
    .form-group label {
      display: block;
      margin-bottom: 5px;
      color: #aaa;
    }
    
    .form-group input {
      width: 100%;
      padding: 10px;
      border: 1px solid #444;
      border-radius: 8px;
      background: #2a2a4e;
      color: #fff;
    }
    
    .modal-buttons {
      display: flex;
      gap: 10px;
      margin-top: 20px;
    }
    
    .modal-buttons button {
      flex: 1;
      padding: 12px;
      border: none;
      border-radius: 8px;
      cursor: pointer;
      font-size: 1rem;
    }
    
    .save-btn { background: #4a90d9; color: white; }
    .cancel-btn { background: #666; color: white; }
    
    .stats {
      font-size: 0.8rem;
      color: #888;
      margin-top: 10px;
    }
  </style>
</head>
<body>
  <header>
    <h1>🐑 HolySheep AI Assistant</h1>
    <button class="settings-btn" onclick="openSettings()">⚙️ Einstellungen</button>
  </header>
  
  <div id="chat-container">
    <div class="message assistant-message">
      Hallo! Ich bin Ihr HolySheep AI Assistant. Wie kann ich Ihnen heute helfen?
      Mit Streaming-Unterstützung und intelligentem Caching für optimale Performance!
    </div>
  </div>
  
  <div id="input-container">
    <textarea id="message-input" placeholder="Nachricht eingeben..." rows="1"></textarea>
    <button id="send-btn">Senden</button>
  </div>
  
  <div class="modal" id="settings-modal">
    <div class="modal-content">
      <h2>⚙️ API-Einstellungen</h2>
      <div class="form-group">
        <label>API Base URL</label>
        <input type="text" id="api-url" value="https://api.holysheep.ai/v1">
      </div>
      <div class="form-group">
        <label>API Key</label>
        <input type="password" id="api-key" placeholder="YOUR_HOLYSHEEP_API_KEY">
      </div>
      <div class="form-group">
        <label>Model</label>
        <select id="model-select" style="width:100%;padding:10px;border-radius:8px;background:#2a2a4e;color:#fff;border:1px solid #444;">
          <option value="gpt-4.1">GPT-4.1 ($8/MTok)</option>
          <option value="claude-sonnet-4.5">Claude Sonnet 4.5 ($15/MTok)</option>
          <option value="gemini-2.5-flash">Gemini 2.5 Flash ($2.50/MTok)</option>
          <option value="deepseek-v3.2">DeepSeek V3.2 ($0.42/MTok)</option>
        </select>
      </div>
      <div class="form-group">
        <label>
          <input type="checkbox" id="cache-enabled" checked> Caching aktivieren
        </label>
      </div>
      <div class="form-group">
        <label>Cache TTL (Stunden)</label>
        <input type="number" id="cache-ttl" value="1" min="1" max="24">
      </div>
      <div class="stats" id="cache-stats"></div>
      <div class="modal-buttons">
        <button class="cancel-btn" onclick="closeSettings()">Abbrechen</button>
        <button class="save-btn" onclick="saveSettings()">Speichern</button>
      </div>
      <button onclick="clearCache()" style="margin-top:15px;background:#d94a4a;color:white;border:none;padding:10px;width:100%;border-radius:8px;cursor:pointer;">
        🗑️ Cache leeren
      </button>
    </div>
  </div>
  
  <script>
    let messages = [];
    let isStreaming = false;
    let currentModel = 'gpt-4.1';
    
    const chatContainer = document.getElementById('chat-container');
    const messageInput = document.getElementById('message-input');
    const sendBtn = document.getElementById('send-btn');
    
    // Auto-resize textarea
    messageInput.addEventListener('input', function() {
      this.style.height = 'auto';
      this.style.height = Math.min(this.scrollHeight, 150) + 'px';
    });
    
    // Send message on Enter (Shift+Enter for newline)
    messageInput.addEventListener('keydown', function(e) {
      if (e.key === 'Enter' && !e.shiftKey) {
        e.preventDefault();
        sendMessage();
      }
    });
    
    sendBtn.addEventListener('click', sendMessage);
    
    async function sendMessage() {
      if (isStreaming) return;
      
      const content = messageInput.value.trim();
      if (!content) return;
      
      // User message
      addMessage('user', content);
      messages.push({ role: 'user', content });
      messageInput.value = '';
      messageInput.style.height = 'auto';
      
      // Show typing indicator
      isStreaming = true;
      sendBtn.disabled = true;
      const typingDiv = document.createElement('div');
      typingDiv.className = 'typing-indicator';
      typingDiv.innerHTML = '<span></span><span></span><span></span>';
      chatContainer.appendChild(typingDiv);
      chatContainer.scrollTop = chatContainer.scrollHeight;
      
      // Assistant message container
      const assistantDiv = document.createElement('div');
      assistantDiv.className = 'message assistant-message';
      let fullResponse = '';
      chatContainer.appendChild(assistantDiv);
      
      try {
        const result = await window.electronAPI.streamChat(messages);
        
        // Listen for chunks
        const unsubscribe = window.electronAPI.onChunk((chunk) => {
          fullResponse += chunk;
          assistantDiv.textContent = fullResponse;
          chatContainer.scrollTop = chatContainer.scrollHeight;
        });
        
        // Wait for completion
        await result;
        unsubscribe();
        
        messages.push({ role: 'assistant', content: fullResponse });
      } catch (error) {
        assistantDiv.textContent = 'Fehler: ' + error.message;
        assistantDiv.style.background = 'rgba(220,53,69,0.3)';
      }
      
      // Remove typing indicator
      typingDiv.remove();
      isStreaming = false;
      sendBtn.disabled = false;
    }
    
    function addMessage(role, content) {
      const div = document.createElement('div');
      div.className = 'message ' + role + '-message';
      div.textContent = content;
      chatContainer.appendChild(div);
      chatContainer.scrollTop = chatContainer.scrollHeight;
    }
    
    function openSettings() {
      document.getElementById('settings-modal').classList.add('active');
      loadSettings();
    }
    
    function closeSettings() {
      document.getElementById('settings-modal').classList.remove('active');
    }
    
    async function loadSettings() {
      const config = await window.electronAPI.getConfig();
      document.getElementById('api-url').value = config.baseUrl;
      document.getElementById('api-key').value = config.apiKey;
      document.getElementById('cache-enabled').checked = config.cacheEnabled;
      document.getElementById('cache-ttl').value = config.cacheTTL / 3600000;
      
      // Show cache stats
      const cache = await window.electronAPI.getCache();
      const cacheCount = Object.keys(cache).length;
      document.getElementById('cache-stats').textContent = 
        Cache-Einträge: ${cacheCount} | Geschätzte Ersparnis: ~${(cacheCount * 0.5).toFixed(2)}$;
    }
    
    async function saveSettings() {
      const config = {
        baseUrl: document.getElementById('api-url').value,
        apiKey: document.getElementById('api-key').value,
        cacheEnabled: document.getElementById('cache-enabled').checked,
        cacheTTL: parseInt(document.getElementById('cache-ttl').value) * 3600000
      };
      
      await window.electronAPI.setConfig(config);
      closeSettings();
    }
    
    async function clearCache() {
      await window.electronAPI.clearCache();
      document.getElementById('cache-stats').textContent = 'Cache geleert!';
    }
    
    // Load settings on startup
    window.electronAPI.getConfig().then(config => {
      currentModel = config.model || 'gpt-4.1';
    });
  </script>
</body>
</html>

Konfiguration für Production Build

// electron-builder.yml
appId: com.holysheep.ai-assistant
productName: HolySheep AI Assistant
directories:
  output: release
  buildResources: build
files:
  - dist/**/*
  - index.html
  - package.json
win:
  target:
    - target: nsis
      arch:
        - x64
  icon: build/icon.ico
mac:
  target:
    - target: dmg
      arch:
        - x64
        - arm64
  icon: build/icon.icns
linux:
  target:
    - target: AppImage
      arch:
        - x64
  icon: build/icon.png
nsis:
  oneClick: false
  allowToChangeInstallationDirectory: true
  installerIcon: build/icon.ico
  uninstallerIcon: build/icon.ico
  installerHeaderIcon: build/icon.ico

Caching-Strategie im Detail

Meine Praxiserfahrung zeigt: Ein intelligentes Caching kann die API-Kosten um 40-60% reduzieren! Die Implementierung verwendet einen MD5-Hash der Konversation als Cache-Schlüssel:

// src/utils/cache.ts
interface CacheStrategy {
  // LRU (Least Recently Used) Cache
  maxSize: number;
  ttl: number; // Time to live in ms
  
  get(key: string): string | null;
  set(key: string, value: string): void;
  clear(): void;
  getStats(): { hits: number; misses: number; size: number };
}

class LRUCache implements CacheStrategy {
  private cache: Map = new Map();
  private accessOrder: string[] = [];
  public maxSize: number;
  public ttl: number;
  public hits = 0;
  public misses = 0;

  constructor(maxSize = 100, ttl = 3600000) {
    this.maxSize = maxSize;
    this.ttl = ttl;
  }

  get(key: string): string | null {
    const entry = this.cache.get(key);
    
    if (!entry) {
      this.misses++;
      return null;
    }
    
    // Check TTL
    if (Date.now() - entry.timestamp > this.ttl) {
      this.cache.delete(key);
      this.accessOrder = this.accessOrder.filter(k => k !== key);
      this.misses++;
      return null;
    }
    
    // Update access order (move to end)
    this.accessOrder = this.accessOrder.filter(k => k !== key);
    this.accessOrder.push(key);
    
    this.hits++;
    return entry.value;
  }

  set(key: string, value: string): void {
    // Evict LRU if at capacity
    if (this.cache.size >= this.maxSize && !this.cache.has(key)) {
      const lruKey = this.accessOrder.shift();
      if (lruKey) {
        this.cache.delete(lruKey);
      }
    }
    
    this.cache.set(key, { value, timestamp: Date.now() });
    
    // Update access order
    this.accessOrder = this.accessOrder.filter(k => k !== key);
    this.accessOrder.push(key);
  }

  clear(): void {
    this.cache.clear();
    this.accessOrder = [];
    this.hits = 0;
    this.misses = 0;
  }

  getStats() {
    return {
      hits: this.hits,
      misses: this.misses,
      size: this.cache.size,
      hitRate: this.hits / (this.hits + this.misses) || 0
    };
  }
}

export const conversationCache = new LRUCache(50, 3600000);

Häufige Fehler und Lösungen

1. CORS-Fehler bei API-Anfragen

Problem: Browser blockiert Cross-Origin-Anfragen

// ❌ FALSCH - Direkte Anfrage vom Renderer
fetch('https://api.holysheep.ai/v1/chat/completions', {
  method: 'POST',
  headers: { 'Authorization': 'Bearer YOUR_KEY' }
});

// ✅ RICHTIG - Über IPC an Main-Prozess
ipcMain.handle('api:request', async (_, payload) => {
  return await fetch('https://api.holysheep.ai/v1/chat/completions', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': Bearer ${process.env.API_KEY}
    },
    body: JSON.stringify(payload)
  });
});

2. Speicherleck bei Streaming

Problem: Response-Reader wird nicht ordnungsgemäß geschlossen

// ❌ FALSCH - Reader wird nicht geschlossen
async function streamResponse(messages: any[]) {
  const response = await fetch(url, options);
  const reader = response.body!.getReader();
  // ... Stream verarbeiten
  // Reader bleibt offen!
}

// ✅ RICHTIG - Try-finally für Cleanup
async function streamResponse(messages: any[], onChunk: (c: string) => void) {
  const response = await fetch(url, options);
  const reader = response.body!.getReader();
  const decoder = new TextDecoder();
  
  try {
    while (true) {
      const { done, value } = await reader.read();
      if (done) break;
      const chunk = decoder.decode(value, { stream: true });
      onChunk(chunk);
    }
  } finally {
    reader.releaseLock(); // WICHTIG: Reader freigeben!
  }
}

3. Cache-Invalidierung funktioniert nicht

Problem: Alte Cache-Einträge werden nicht entfernt

// ❌ FALSCH - Keine TTL-Prüfung
cache.set(key, value);

// ✅ RICHTIG - Mit Zeitstempel und Prüfung
interface CacheEntry {
  data: string;
  timestamp: number;
  ttl: number;
}

setWithTTL(key: string, data: string, ttl: number) {
  cache.set(key, { data, timestamp: Date.now(), ttl });
}

getWithTTL(key: string): string | null {
  const entry = cache.get(key);
  if (!entry) return null;
  
  if (Date.now() - entry.timestamp > entry.ttl) {
    cache.delete(key); // Automatisch ablaufen
    return null;
  }
  
  return entry.data;
}

// Zusätzlich: Regelmäßige Bereinigung
setInterval(() => {
  const now = Date.now();
  for (const [key, entry] of cache.entries()) {
    if (now - entry.timestamp > entry.ttl) {
      cache.delete(key);
    }
  }
}, 60000); // Jede Minute

4. API-Key wird im Frontend exponiert

Problem: Sensible Daten in Renderer-Prozess sichtbar

// ❌ FALSCH - Key in preload/expose
contextBridge.exposeInMainWorld('api', {
  key: 'sk-xxxx...', // SICHERHEITSRISIKO!
  callApi: (data) => fetch(...)
});

// ✅ RICHTIG - Key nur im Main-Prozess
// preload.ts - nur sichere Methoden
contextBridge.exposeInMainWorld('api', {
  sendMessage: (msg) => ipcRenderer.invoke('ai:chat', msg)
});

// main.ts - Key geschützt
const API_KEY = process.env.HOLYSHEEP_API_KEY; // Aus .env laden

ipcMain.handle('ai:chat', async (_, messages) => {
  const response = await fetch('https://api.holysheep.ai/v1/chat/completions', {
    headers: {
      'Authorization': Bearer ${API_KEY} // Key bleibt serverseitig
    }
  });
  return response.json();
});

Verwandte Ressourcen

Verwandte Artikel