Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions env.example
Original file line number Diff line number Diff line change
@@ -1,7 +1,21 @@
# Google Gemini API Configuration
# Get your API key from: https://makersuite.google.com/app/apikey
# ── AI Provider ──────────────────────────────────────────────────────────────
# Choose your LLM backend: 'gemini' (cloud, default) or 'ollama' (local)
LLM_PROVIDER=gemini

# ── Google Gemini (cloud) ─────────────────────────────────────────────────────
# Required when LLM_PROVIDER=gemini
# Get your API key from: https://aistudio.google.com/
GEMINI_API_KEY=your_gemini_api_key_here

# ── Ollama (local) ────────────────────────────────────────────────────────────
# Required when LLM_PROVIDER=ollama
# Install Ollama from https://ollama.ai, then:
# ollama pull llama3.2 # text model
# ollama pull llava # vision model (for screenshot analysis)
OLLAMA_BASE_URL=http://localhost:11434
OLLAMA_MODEL=llama3.2
OLLAMA_VISION_MODEL=llava

# Speech Recognition Configuration
# Choose one provider: azure or whisper
SPEECH_PROVIDER=whisper
Expand Down
47 changes: 45 additions & 2 deletions main.js
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,19 @@ class ApplicationController {
}
});

ipcMain.handle("get-llm-provider", () => {
return { provider: llmService._getProviderName() };
});

ipcMain.handle("set-llm-provider", (event, provider) => {
const p = String(provider).toLowerCase();
process.env.LLM_PROVIDER = p;
const config = require('./src/core/config');
config.set('llm.provider', p);
logger.info('LLM provider switched', { provider: p });
return { provider: p };
});

ipcMain.handle("set-gemini-api-key", (event, apiKey) => {
llmService.updateApiKey(apiKey);
return llmService.getStats();
Expand All @@ -455,6 +468,16 @@ class ApplicationController {
return llmService.getStats();
});

ipcMain.handle("get-ollama-status", () => {
const ollamaService = require('./src/services/ollama.service');
return ollamaService.getStats();
});

ipcMain.handle("test-ollama-connection", async () => {
const ollamaService = require('./src/services/ollama.service');
return await ollamaService.testConnection();
});

// Window binding IPC handlers
ipcMain.handle("set-window-binding", (event, enabled) => {
return windowManager.setWindowBinding(enabled);
Expand Down Expand Up @@ -1073,13 +1096,19 @@ class ApplicationController {

getSettings() {
return {
codingLanguage: this.codingLanguage || "cpp", // Default to C++
codingLanguage: this.codingLanguage || "cpp",
activeSkill: this.activeSkill || "dsa",
appIcon: this.appIcon || "terminal",
selectedIcon: this.appIcon || "terminal",
// pass through env-derived settings for UI convenience (masked)
azureConfigured: !!process.env.AZURE_SPEECH_KEY && !!process.env.AZURE_SPEECH_REGION,
speechAvailable: this.speechAvailable
speechAvailable: this.speechAvailable,
// LLM provider
llmProvider: process.env.LLM_PROVIDER || "gemini",
// Ollama settings (safe to expose — no credentials)
ollamaBaseUrl: process.env.OLLAMA_BASE_URL || "http://localhost:11434",
ollamaModel: process.env.OLLAMA_MODEL || "llama3.2",
ollamaVisionModel: process.env.OLLAMA_VISION_MODEL || "llava"
};
}

Expand All @@ -1104,6 +1133,20 @@ class ApplicationController {
this.appIcon = settings.appIcon;
}

// LLM provider switch
if (settings.llmProvider) {
const p = String(settings.llmProvider).toLowerCase();
process.env.LLM_PROVIDER = p;
const config = require('./src/core/config');
config.set('llm.provider', p);
logger.info('LLM provider updated via settings', { provider: p });
}

// Ollama settings
if (settings.ollamaBaseUrl) process.env.OLLAMA_BASE_URL = settings.ollamaBaseUrl;
if (settings.ollamaModel) process.env.OLLAMA_MODEL = settings.ollamaModel;
if (settings.ollamaVisionModel) process.env.OLLAMA_VISION_MODEL = settings.ollamaVisionModel;

// Handle icon change specifically
if (settings.selectedIcon) {
this.appIcon = settings.selectedIcon;
Expand Down
8 changes: 8 additions & 0 deletions preload.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,18 @@ contextBridge.exposeInMainWorld('electronAPI', {
sendChatMessage: (text) => ipcRenderer.invoke('send-chat-message', text),
getSkillPrompt: (skillName) => ipcRenderer.invoke('get-skill-prompt', skillName),

// LLM provider selection
getLlmProvider: () => ipcRenderer.invoke('get-llm-provider'),
setLlmProvider: (provider) => ipcRenderer.invoke('set-llm-provider', provider),

// Gemini LLM configuration
setGeminiApiKey: (apiKey) => ipcRenderer.invoke('set-gemini-api-key', apiKey),
getGeminiStatus: () => ipcRenderer.invoke('get-gemini-status'),
testGeminiConnection: () => ipcRenderer.invoke('test-gemini-connection'),

// Ollama configuration
getOllamaStatus: () => ipcRenderer.invoke('get-ollama-status'),
testOllamaConnection: () => ipcRenderer.invoke('test-ollama-connection'),

// Settings
showSettings: () => ipcRenderer.invoke('show-settings'),
Expand Down
53 changes: 53 additions & 0 deletions settings.html
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,25 @@
</div>

<div class="settings-section">
<div class="settings-section-title">
<i class="fas fa-brain"></i>
AI Provider
</div>
<div class="settings-section-content">
<div class="settings-item">
<div>
<div class="settings-item-label">AI Backend</div>
<div class="settings-item-description">Gemini uses the cloud; Ollama runs models locally</div>
</div>
<select class="input-field" id="llmProvider">
<option value="gemini">Google Gemini (cloud)</option>
<option value="ollama">Ollama (local)</option>
</select>
</div>
</div>
</div>

<div class="settings-section" id="geminiSection">
<div class="settings-section-title">
<i class="fas fa-robot"></i>
Gemini Settings
Expand All @@ -427,6 +446,40 @@
</div>
</div>
</div>

<div class="settings-section" id="ollamaSection">
<div class="settings-section-title">
<i class="fas fa-server"></i>
Ollama Settings
</div>
<div class="settings-section-content">
<div class="settings-item">
<div>
<div class="settings-item-label">Ollama Base URL</div>
<div class="settings-item-description">URL where Ollama is running</div>
</div>
<input type="text" class="input-field" id="ollamaBaseUrl" placeholder="http://localhost:11434">
</div>
<div class="settings-item">
<div>
<div class="settings-item-label">Text Model</div>
<div class="settings-item-description">Model for text / chat (e.g. llama3.2, mistral, phi4)</div>
</div>
<input type="text" class="input-field" id="ollamaModel" placeholder="llama3.2">
</div>
<div class="settings-item">
<div>
<div class="settings-item-label">Vision Model</div>
<div class="settings-item-description">Model for screenshot analysis (e.g. llava, llava-phi3)</div>
</div>
<input type="text" class="input-field" id="ollamaVisionModel" placeholder="llava">
</div>
<div class="settings-note">
Ollama must be running locally. Install from <code>ollama.ai</code> and pull models first:<br>
<code>ollama pull llama3.2 &amp;&amp; ollama pull llava</code>
</div>
</div>
</div>

<div class="settings-section">
<div class="settings-section-title">
Expand Down
12 changes: 12 additions & 0 deletions src/core/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class ConfigManager {
},

llm: {
provider: process.env.LLM_PROVIDER || 'gemini', // 'gemini' | 'ollama'
gemini: {
model: 'gemini-2.5-flash',
maxRetries: 3,
Expand All @@ -51,6 +52,17 @@ class ConfigManager {
topP: 0.9,
maxOutputTokens: 4096
}
},
ollama: {
baseUrl: process.env.OLLAMA_BASE_URL || 'http://localhost:11434',
model: process.env.OLLAMA_MODEL || 'llama3.2',
visionModel: process.env.OLLAMA_VISION_MODEL || 'llava',
timeout: 120000,
fallbackEnabled: true,
generation: {
temperature: 0.7,
maxOutputTokens: 4096
}
}
},

Expand Down
Loading