feat: ollama check (#803)

This commit is contained in:
lily-de
2025-01-27 10:49:07 -05:00
committed by GitHub
parent b3d2378bdd
commit 405c3dc58a
7 changed files with 145 additions and 21 deletions
@@ -1,24 +1,38 @@
import { Provider, ProviderResponse } from './types';
import { getApiUrl, getSecretKey } from '../../../config';
import { special_provider_cases } from '../providers/utils';
export async function getActiveProviders(): Promise<string[]> {
try {
// Fetch the secrets settings
const secretsSettings = await getSecretsSettings();
// Check for special provider cases (e.g. ollama needs to be installed in Applications folder)
const specialCasesResults = await Promise.all(
Object.entries(special_provider_cases).map(async ([providerName, checkFunction]) => {
const isActive = await checkFunction(); // Dynamically re-check status
console.log(`Special case result for ${providerName}:`, isActive);
return isActive ? providerName : null;
})
);
// Extract active providers based on `is_set` in `secret_status` or providers with no keys
const activeProviders = Object.values(secretsSettings) // Convert object to array
.filter((provider) => {
const apiKeyStatus = Object.values(provider.secret_status || {}); // Get all key statuses
// Include providers if:
// - They have at least one key set (`is_set: true`), OR
// - They have no keys (`secret_status` is empty or undefined)
return apiKeyStatus.some((key) => key.is_set) || apiKeyStatus.length === 0;
// - They have at least one key set (`is_set: true`)
return apiKeyStatus.some((key) => key.is_set);
})
.map((provider) => provider.name || 'Unknown Provider'); // Extract provider name
return activeProviders;
// Combine active providers from secrets settings and special cases
const allActiveProviders = [
...activeProviders,
...specialCasesResults.filter((provider) => provider !== null), // Filter out null results
];
return allActiveProviders;
} catch (error) {
console.error('Failed to get active providers:', error);
return [];
@@ -109,15 +109,3 @@ export const provider_aliases = [
{ provider: 'OpenRouter', alias: 'openrouter' },
{ provider: 'Google', alias: 'google' },
];
export const recent_models = [
{ name: 'gpt-4o', provider: 'OpenAI', lastUsed: '2 hours ago' },
{ name: 'claude-3-5-sonnet-latest', provider: 'Anthropic', lastUsed: 'Yesterday' },
{ name: 'o1-mini', provider: 'OpenAI', lastUsed: '3 days ago' },
];
export const model_env_vars = {
OpenAI: 'OPENAI_MODEL',
Anthropic: 'ANTHROPIC_MODEL',
Databricks: 'DATABRICKS_MODEL',
};
@@ -1,9 +1,12 @@
import React from 'react';
import { Check, Plus, Settings, X, Rocket } from 'lucide-react';
import React, { useEffect, useState } from 'react';
import { Check, Plus, Settings, X, Rocket, RefreshCw, AlertCircle } from 'lucide-react';
import { Button } from '../../ui/button';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../../ui/Tooltip';
import { Portal } from '@radix-ui/react-portal';
import { required_keys } from '../models/hardcoded_stuff';
import { ExclamationTriangleIcon } from '@radix-ui/react-icons';
import { useActiveKeys } from '../api_keys/ActiveKeysContext';
import { getActiveProviders } from '../api_keys/utils';
// Common interfaces and helper functions
interface Provider {
@@ -27,6 +30,7 @@ interface BaseProviderCardProps {
showDelete?: boolean;
hasRequiredKeys?: boolean;
onTakeoff?: () => void;
showTakeoff?: boolean;
}
function getArticle(word: string): string {
@@ -60,9 +64,11 @@ function BaseProviderCard({
showDelete = false,
hasRequiredKeys = false,
onTakeoff,
showTakeoff,
}: BaseProviderCardProps) {
const numRequiredKeys = required_keys[name]?.length || 0;
const tooltipText = numRequiredKeys === 1 ? `Add ${name} API Key` : `Add ${name} API Keys`;
const { activeKeys, setActiveKeys } = useActiveKeys();
return (
<div className="relative h-full p-[2px] overflow-hidden rounded-[9px] group/card bg-borderSubtle hover:bg-transparent hover:duration-300">
@@ -85,6 +91,7 @@ function BaseProviderCard({
<div className="flex items-center">
<h3 className="text-base font-medium text-textStandard truncate mr-2">{name}</h3>
{/* Configured state: Green check */}
{isConfigured && (
<TooltipProvider>
<Tooltip>
@@ -98,7 +105,36 @@ function BaseProviderCard({
<p>
{hasRequiredKeys
? `You have ${getArticle(name)} ${name} API Key set in your environment`
: `${name} has no required API keys`}
: `${name} is installed and running on your machine`}
</p>
</TooltipContent>
</Portal>
</Tooltip>
</TooltipProvider>
)}
{/* Not Configured state: Red exclamation mark for Ollama */}
{!isConfigured && name === 'Ollama' && (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<div className="flex items-center justify-center w-5 h-5 rounded-full bg-bgApp hover:bg-bgApp shadow-none text-textSubtle border border-borderSubtle hover:border-borderStandard hover:text-textStandard transition-colors">
!
</div>
</TooltipTrigger>
<Portal>
<TooltipContent side="top" align="center" className="z-[9999]">
<p>
To use, the{' '}
<a
href="https://ollama.com/download"
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 underline hover:text-blue-800"
>
Ollama app
</a>{' '}
must be installed on your machine and open.
</p>
</TooltipContent>
</Portal>
@@ -106,7 +142,6 @@ function BaseProviderCard({
</TooltipProvider>
)}
</div>
<p className="text-xs text-textSubtle mt-1.5 mb-3 leading-normal overflow-y-auto max-h-[54px] ">
{description}
</p>
@@ -114,6 +149,42 @@ function BaseProviderCard({
<div className="space-x-2 text-center flex items-center justify-between">
<div className="space-x-2">
{!isConfigured && name === 'Ollama' && (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="default"
size="sm"
onClick={(e) => {
e.stopPropagation();
// Trigger a refresh of active keys
const refreshActiveKeys = async () => {
try {
const providers = await getActiveProviders(); // Re-fetch active providers
setActiveKeys(providers); // Update the context state
} catch (error) {
console.error('Error refreshing active providers:', error);
}
};
refreshActiveKeys(); // Call the refresh function
}}
className="rounded-full h-7 w-7 p-0 bg-bgApp hover:bg-bgApp shadow-none text-textSubtle border border-borderSubtle hover:border-borderStandard hover:text-textStandard transition-colors"
>
<RefreshCw className="!size-4" /> {/* Refresh icon */}
</Button>
</TooltipTrigger>
<Portal>
<TooltipContent side="top" align="center" className="z-[9999]">
<p>Re-check for active Ollama app running in the background.</p>
</TooltipContent>
</Portal>
</Tooltip>
</TooltipProvider>
)}
{/* Default "Add Keys" Button for other providers */}
{!isConfigured && onAddKeys && hasRequiredKeys && (
<TooltipProvider>
<Tooltip>
@@ -187,7 +258,7 @@ function BaseProviderCard({
</TooltipProvider>
)}
</div>
{isConfigured && onTakeoff && (
{isConfigured && onTakeoff && showTakeoff !== false && (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
@@ -228,6 +299,7 @@ interface BaseProviderGridProps {
onConfigure?: (provider: Provider) => void;
onDelete?: (provider: Provider) => void;
onTakeoff?: (provider: Provider) => void;
showTakeoff?: boolean;
}
export function BaseProviderGrid({
@@ -240,6 +312,7 @@ export function BaseProviderGrid({
onAddKeys,
onConfigure,
onDelete,
showTakeoff,
onTakeoff,
}: BaseProviderGridProps) {
return (
@@ -262,6 +335,7 @@ export function BaseProviderGrid({
showSettings={showSettings}
showDelete={showDelete}
hasRequiredKeys={hasRequiredKeys}
showTakeoff={showTakeoff}
/>
);
})}
@@ -205,6 +205,7 @@ export function ConfigureProvidersGrid() {
onAddKeys={handleAddKeys}
onConfigure={handleConfigure}
onDelete={handleDelete}
showTakeoff={false}
/>
{showSetupModal && selectedForSetup && (
@@ -0,0 +1,14 @@
export const special_provider_cases = {
Ollama: async () => await checkForOllama(), // Dynamically re-check
};
export async function checkForOllama() {
console.log('Invoking check-ollama IPC handler...');
try {
const ollamaInstalled = await window.electron.checkForOllama();
return ollamaInstalled;
} catch (error) {
console.error('Error invoking check-ollama:', error);
return false;
}
}
+32
View File
@@ -26,6 +26,7 @@ import {
saveSettings,
updateEnvironmentVariables,
} from './utils/settings';
const { exec } = require('child_process');
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
if (started) app.quit();
@@ -347,6 +348,37 @@ ipcMain.handle('select-file-or-directory', async () => {
return null;
});
ipcMain.handle('check-ollama', async () => {
try {
return new Promise((resolve, reject) => {
// Run `ps` and filter for "ollama"
exec('ps aux | grep -iw "[o]llama"', (error, stdout, stderr) => {
if (error) {
console.error('Error executing ps command:', error);
return resolve(false); // Process is not running
}
if (stderr) {
console.error('Standard error output from ps command:', stderr);
return resolve(false); // Process is not running
}
console.log('Raw stdout from ps command:', stdout);
// Trim and check if output contains a match
const trimmedOutput = stdout.trim();
console.log('Trimmed stdout:', trimmedOutput);
const isRunning = trimmedOutput.length > 0; // True if there's any output
resolve(isRunning); // Resolve true if running, false otherwise
});
});
} catch (err) {
console.error('Error checking for Ollama:', err);
return false; // Return false on error
}
});
app.whenReady().then(async () => {
// Test error feature - only enabled with GOOSE_TEST_ERROR=true
if (process.env.GOOSE_TEST_ERROR === 'true') {
+1
View File
@@ -18,6 +18,7 @@ contextBridge.exposeInMainWorld('electron', {
openInChrome: (url) => ipcRenderer.send('open-in-chrome', url),
fetchMetadata: (url) => ipcRenderer.invoke('fetch-metadata', url),
reloadApp: () => ipcRenderer.send('reload-app'),
checkForOllama: () => ipcRenderer.invoke('check-ollama'),
selectFileOrDirectory: () => ipcRenderer.invoke('select-file-or-directory'),
startPowerSaveBlocker: () => ipcRenderer.invoke('start-power-save-blocker'),
stopPowerSaveBlocker: () => ipcRenderer.invoke('stop-power-save-blocker'),