feat: update ui for ollama host (#912)

This commit is contained in:
Yingjie He
2025-01-30 09:24:52 -08:00
committed by GitHub
parent aea45dfaf2
commit 654e5c6e63
14 changed files with 125 additions and 159 deletions
+1 -1
View File
@@ -385,7 +385,7 @@ export default function ChatWindow() {
}, []);
const storeSecret = async (key: string, value: string) => {
const response = await fetch(getApiUrl('/secrets/store'), {
const response = await fetch(getApiUrl('/configs/store'), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@@ -3,10 +3,10 @@ export interface ProviderResponse {
name?: string;
description?: string;
models?: string[];
secret_status: Record<string, SecretDetails>;
config_status: Record<string, ConfigDetails>;
}
export interface SecretDetails {
export interface ConfigDetails {
key: string;
is_set: boolean;
location?: string;
@@ -1,6 +1,5 @@
import { Provider, ProviderResponse } from './types';
import { getApiUrl, getSecretKey } from '../../../config';
import { special_provider_cases } from '../providers/utils';
export function isSecretKey(keyName: string): boolean {
// Ollama and Databricks use host name right now and it should not be stored as secret.
@@ -10,21 +9,12 @@ export function isSecretKey(keyName: string): boolean {
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;
})
);
const configSettings = await getConfigSettings();
// Extract active providers based on `is_set` in `secret_status` or providers with no keys
const activeProviders = Object.values(secretsSettings) // Convert object to array
const activeProviders = Object.values(configSettings) // Convert object to array
.filter((provider) => {
const apiKeyStatus = Object.values(provider.secret_status || {}); // Get all key statuses
const apiKeyStatus = Object.values(provider.config_status || {}); // Get all key statuses
// Include providers if:
// - They have at least one key set (`is_set: true`)
@@ -32,25 +22,20 @@ export async function getActiveProviders(): Promise<string[]> {
})
.map((provider) => provider.name || 'Unknown Provider'); // Extract provider name
// Combine active providers from secrets settings and special cases
const allActiveProviders = [
...activeProviders,
...specialCasesResults.filter((provider) => provider !== null), // Filter out null results
];
return allActiveProviders;
return activeProviders;
} catch (error) {
console.error('Failed to get active providers:', error);
return [];
}
}
export async function getSecretsSettings(): Promise<Record<string, ProviderResponse>> {
export async function getConfigSettings(): Promise<Record<string, ProviderResponse>> {
const providerList = await getProvidersList();
// Extract the list of IDs
const providerIds = providerList.map((provider) => provider.id);
// Fetch secrets state (set/unset) using the provider IDs
const response = await fetch(getApiUrl('/secrets/providers'), {
// Fetch configs state (set/unset) using the provider IDs
const response = await fetch(getApiUrl('/configs/providers'), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@@ -43,7 +43,7 @@ export function ConfigureBuiltInExtensionModal({
const value = envValues[envKey];
if (!value) continue;
const storeResponse = await fetch(getApiUrl('/secrets/store'), {
const storeResponse = await fetch(getApiUrl('/configs/store'), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@@ -45,7 +45,7 @@ export function ConfigureExtensionModal({
const value = envValues[envKey];
if (!value) continue;
const storeResponse = await fetch(getApiUrl('/secrets/store'), {
const storeResponse = await fetch(getApiUrl('/configs/store'), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@@ -59,7 +59,7 @@ export function ManualExtensionModal({ isOpen, onClose, onSubmit }: ManualExtens
try {
// Store environment variables as secrets
for (const envVar of envVars) {
const storeResponse = await fetch(getApiUrl('/secrets/store'), {
const storeResponse = await fetch(getApiUrl('/configs/store'), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@@ -112,35 +112,6 @@ function BaseProviderCard({
</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>
</Tooltip>
</TooltipProvider>
)}
</div>
<p className="text-xs text-textSubtle mt-1.5 mb-3 leading-normal overflow-y-auto max-h-[54px] ">
{description}
@@ -149,41 +120,6 @@ 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>
@@ -251,7 +187,7 @@ function BaseProviderCard({
</TooltipTrigger>
<Portal>
<TooltipContent side="top" align="center" className="z-[9999]">
<p>Remove {name} API Key</p>
<p>Remove {name} API Key or Host</p>
</TooltipContent>
</Portal>
</Tooltip>
@@ -90,7 +90,7 @@ export function ConfigureProvidersGrid() {
// Delete existing key if provider is already configured
const isUpdate = providers.find((p) => p.id === selectedForSetup)?.isConfigured;
if (isUpdate) {
const deleteResponse = await fetch(getApiUrl('/secrets/delete'), {
const deleteResponse = await fetch(getApiUrl('/configs/delete'), {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
@@ -108,7 +108,7 @@ export function ConfigureProvidersGrid() {
// Store new key
const isSecret = isSecretKey(keyName);
const storeResponse = await fetch(getApiUrl('/secrets/store'), {
const storeResponse = await fetch(getApiUrl('/configs/store'), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@@ -162,23 +162,28 @@ export function ConfigureProvidersGrid() {
return;
}
const isSecret = isSecretKey(keyName);
const toastInfo = isSecret ? 'API key' : 'host';
try {
// Check if the selected provider is currently active
if (currentModel?.provider === providerToDelete.name) {
toast.error(
`Cannot delete the API key for ${providerToDelete.name} because it's the provider of the current model (${currentModel.name}). Please switch to a different model first.`
`Cannot delete the ${toastInfo} for ${providerToDelete.name} because it's the provider of the current model (${currentModel.name}). Please switch to a different model first.`
);
setIsConfirmationOpen(false);
return;
}
const deleteResponse = await fetch(getApiUrl('/secrets/delete'), {
const deleteResponse = await fetch(getApiUrl('/configs/delete'), {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
'X-Secret-Key': getSecretKey(),
},
body: JSON.stringify({ key: keyName }),
body: JSON.stringify({
key: keyName,
isSecret,
}),
});
if (!deleteResponse.ok) {
@@ -188,13 +193,13 @@ export function ConfigureProvidersGrid() {
}
console.log('Key deleted successfully.');
toast.success(`Successfully deleted API key for ${providerToDelete.name}`);
toast.success(`Successfully deleted ${toastInfo} for ${providerToDelete.name}`);
const updatedKeys = await getActiveProviders();
setActiveKeys(updatedKeys);
} catch (error) {
console.error('Error deleting key:', error);
toast.error(`Unable to delete API key for ${providerToDelete.name}`);
toast.error(`Unable to delete ${toastInfo} for ${providerToDelete.name}`);
}
setIsConfirmationOpen(false);
};
@@ -233,7 +238,7 @@ export function ConfigureProvidersGrid() {
{isConfirmationOpen && providerToDelete && (
<ConfirmationModal
message={`Are you sure you want to delete the API key for ${providerToDelete.name}? This action cannot be undone.`}
message={`Are you sure you want to delete the API key or host for ${providerToDelete.name}? This action cannot be undone.`}
onConfirm={confirmDelete}
onCancel={() => setIsConfirmationOpen(false)}
/>
@@ -1,14 +0,0 @@
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;
}
}
@@ -83,7 +83,7 @@ export function ProviderGrid({ onSubmit }: ProviderGridProps) {
try {
if (selectedId && providers.find((p) => p.id === selectedId)?.isConfigured) {
const deleteResponse = await fetch(getApiUrl('/secrets/delete'), {
const deleteResponse = await fetch(getApiUrl('/configs/delete'), {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
@@ -100,7 +100,7 @@ export function ProviderGrid({ onSubmit }: ProviderGridProps) {
}
const isSecret = isSecretKey(keyName);
const storeResponse = await fetch(getApiUrl('/secrets/store'), {
const storeResponse = await fetch(getApiUrl('/configs/store'), {
method: 'POST',
headers: {
'Content-Type': 'application/json',