mirror of
https://github.com/aaif-goose/goose.git
synced 2026-06-02 06:14:27 +02:00
feat: ollama check (#803)
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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') {
|
||||
|
||||
@@ -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'),
|
||||
|
||||
Reference in New Issue
Block a user