ui: providers new design (#1446)
5
ui/desktop/image.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
declare module '*.png';
|
||||
declare module '*.jpg';
|
||||
declare module '*.jpeg';
|
||||
declare module '*.svg';
|
||||
declare module '*.gif';
|
||||
@@ -10,7 +10,7 @@
|
||||
"license": {
|
||||
"name": "Apache-2.0"
|
||||
},
|
||||
"version": "1.0.9"
|
||||
"version": "1.0.10"
|
||||
},
|
||||
"paths": {
|
||||
"/config": {
|
||||
|
||||
@@ -17,7 +17,7 @@ import SettingsView, { type SettingsViewOptions } from './components/settings/Se
|
||||
import SettingsViewV2 from './components/settings_v2/SettingsView';
|
||||
import MoreModelsView from './components/settings/models/MoreModelsView';
|
||||
import ConfigureProvidersView from './components/settings/providers/ConfigureProvidersView';
|
||||
import ProviderSettings from './components/settings/providers/providers/NewProviderSettingsPage';
|
||||
import ProviderSettings from './components/settings_v2/providers/ProviderSettingsPage';
|
||||
|
||||
import 'react-toastify/dist/ReactToastify.css';
|
||||
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
import React from 'react';
|
||||
import { ProviderCard } from './subcomponents/ProviderCard';
|
||||
import ProviderState from './interfaces/ProviderState';
|
||||
import OnShowModal from './callbacks/ShowModal';
|
||||
import OnAdd from './callbacks/AddProviderParameters';
|
||||
import OnDelete from './callbacks/DeleteProviderParameters';
|
||||
import OnShowSettings from './callbacks/UpdateProviderParameters';
|
||||
import OnRefresh from './callbacks/RefreshActiveProviders';
|
||||
import DefaultProviderActions from './subcomponents/actions/DefaultProviderActions';
|
||||
|
||||
function GridLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<div className="grid grid-cols-3 sm:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 2xl:grid-cols-7 gap-3 auto-rows-fr max-w-full [&_*]:z-20">
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ProviderCards({ providers }: { providers: ProviderState[] }) {
|
||||
const providerCallbacks = {
|
||||
onShowModal: OnShowModal,
|
||||
onAdd: OnAdd,
|
||||
onDelete: OnDelete,
|
||||
onShowSettings: OnShowSettings,
|
||||
onRefresh: OnRefresh,
|
||||
};
|
||||
return (
|
||||
<>
|
||||
{providers.map((provider) => (
|
||||
<ProviderCard
|
||||
key={provider.name} // helps React efficiently update and track components when rendering lists
|
||||
provider={provider}
|
||||
providerCallbacks={providerCallbacks}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default function ProviderGrid({ providers }: { providers: ProviderState[] }) {
|
||||
console.log('got these providers', providers);
|
||||
return (
|
||||
<GridLayout>
|
||||
<ProviderCards providers={providers} />
|
||||
</GridLayout>
|
||||
);
|
||||
}
|
||||
@@ -1,242 +0,0 @@
|
||||
import React from 'react';
|
||||
import ProviderDetails from './interfaces/ProviderDetails';
|
||||
import DefaultProviderActions from './subcomponents/actions/DefaultProviderActions';
|
||||
import OllamaActions from './subcomponents/actions/OllamaActions';
|
||||
|
||||
export interface ProviderRegistry {
|
||||
name: string;
|
||||
details: ProviderDetails;
|
||||
}
|
||||
|
||||
export const PROVIDER_REGISTRY: ProviderRegistry[] = [
|
||||
{
|
||||
name: 'OpenAI',
|
||||
details: {
|
||||
id: 'openai',
|
||||
name: 'OpenAI',
|
||||
description: 'Access GPT-4, GPT-3.5 Turbo, and other OpenAI models',
|
||||
parameters: [
|
||||
{
|
||||
name: 'OPENAI_API_KEY',
|
||||
is_secret: true,
|
||||
},
|
||||
],
|
||||
getActions: (provider, callbacks) => {
|
||||
const { onAdd, onDelete, onShowSettings } = callbacks || {};
|
||||
return [
|
||||
{
|
||||
id: 'default-provider-actions',
|
||||
renderButton: () => (
|
||||
<DefaultProviderActions
|
||||
name={provider.name}
|
||||
isConfigured={provider.isConfigured}
|
||||
onAdd={onAdd}
|
||||
onDelete={onDelete}
|
||||
onShowSettings={onShowSettings}
|
||||
/>
|
||||
),
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Anthropic',
|
||||
details: {
|
||||
id: 'anthropic',
|
||||
name: 'Anthropic',
|
||||
description: 'Access Claude and other Anthropic models',
|
||||
parameters: [
|
||||
{
|
||||
name: 'ANTHROPIC_API_KEY',
|
||||
is_secret: true,
|
||||
},
|
||||
],
|
||||
getActions: (provider, callbacks) => {
|
||||
const { onAdd, onDelete, onShowSettings } = callbacks || {};
|
||||
return [
|
||||
{
|
||||
id: 'default-provider-actions',
|
||||
renderButton: () => (
|
||||
<DefaultProviderActions
|
||||
name={provider.name}
|
||||
isConfigured={provider.isConfigured}
|
||||
onAdd={onAdd}
|
||||
onDelete={onDelete}
|
||||
onShowSettings={onShowSettings}
|
||||
/>
|
||||
),
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Google',
|
||||
details: {
|
||||
id: 'google',
|
||||
name: 'Google',
|
||||
description: 'Access Gemini and other Google AI models',
|
||||
parameters: [
|
||||
{
|
||||
name: 'GOOGLE_API_KEY',
|
||||
is_secret: true,
|
||||
},
|
||||
],
|
||||
getActions: (provider, callbacks) => {
|
||||
const { onAdd, onDelete, onShowSettings } = callbacks || {};
|
||||
return [
|
||||
{
|
||||
id: 'default-provider-actions',
|
||||
renderButton: () => (
|
||||
<DefaultProviderActions
|
||||
name={provider.name}
|
||||
isConfigured={provider.isConfigured}
|
||||
onAdd={onAdd}
|
||||
onDelete={onDelete}
|
||||
onShowSettings={onShowSettings}
|
||||
/>
|
||||
),
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Groq',
|
||||
details: {
|
||||
id: 'groq',
|
||||
name: 'Groq',
|
||||
description: 'Access Mixtral and other Groq-hosted models',
|
||||
parameters: [
|
||||
{
|
||||
name: 'GROQ_API_KEY',
|
||||
is_secret: true,
|
||||
},
|
||||
],
|
||||
getActions: (provider, callbacks) => {
|
||||
const { onAdd, onDelete, onShowSettings } = callbacks || {};
|
||||
return [
|
||||
{
|
||||
id: 'default-provider-actions',
|
||||
renderButton: () => (
|
||||
<DefaultProviderActions
|
||||
name={provider.name}
|
||||
isConfigured={provider.isConfigured}
|
||||
onAdd={onAdd}
|
||||
onDelete={onDelete}
|
||||
onShowSettings={onShowSettings}
|
||||
/>
|
||||
),
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Databricks',
|
||||
details: {
|
||||
id: 'databricks',
|
||||
name: 'Databricks',
|
||||
description: 'Access models hosted on your Databricks instance',
|
||||
parameters: [
|
||||
{
|
||||
name: 'DATABRICKS_HOST',
|
||||
is_secret: false,
|
||||
},
|
||||
],
|
||||
getActions: (provider, callbacks) => {
|
||||
const { onAdd, onDelete, onShowSettings } = callbacks || {};
|
||||
return [
|
||||
{
|
||||
id: 'default-provider-actions',
|
||||
renderButton: () => (
|
||||
<DefaultProviderActions
|
||||
name={provider.name}
|
||||
isConfigured={provider.isConfigured}
|
||||
onAdd={onAdd}
|
||||
onDelete={onDelete}
|
||||
onShowSettings={onShowSettings}
|
||||
/>
|
||||
),
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'OpenRouter',
|
||||
details: {
|
||||
id: 'openrouter',
|
||||
name: 'OpenRouter',
|
||||
description: 'Access a variety of AI models through OpenRouter',
|
||||
parameters: [
|
||||
{
|
||||
name: 'OPENROUTER_API_KEY',
|
||||
is_secret: true,
|
||||
},
|
||||
],
|
||||
getActions: (provider, callbacks) => {
|
||||
const { onAdd, onDelete, onShowSettings } = callbacks || {};
|
||||
return [
|
||||
{
|
||||
id: 'default-provider-actions',
|
||||
renderButton: () => (
|
||||
<DefaultProviderActions
|
||||
name={provider.name}
|
||||
isConfigured={provider.isConfigured}
|
||||
onAdd={onAdd}
|
||||
onDelete={onDelete}
|
||||
onShowSettings={onShowSettings}
|
||||
/>
|
||||
),
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Ollama',
|
||||
details: {
|
||||
id: 'ollama',
|
||||
name: 'Ollama',
|
||||
description: 'Run and use open-source models locally',
|
||||
parameters: [
|
||||
{
|
||||
name: 'OLLAMA_HOST',
|
||||
is_secret: false,
|
||||
},
|
||||
],
|
||||
getActions: (provider, callbacks) => {
|
||||
const { onAdd, onDelete, onRefresh, onShowSettings } = callbacks || {};
|
||||
return [
|
||||
{
|
||||
id: 'ollama-actions',
|
||||
renderButton: () => (
|
||||
<OllamaActions
|
||||
isConfigured={provider.isConfigured}
|
||||
ollamaMetadata={provider.metadata}
|
||||
onAdd={onAdd}
|
||||
onRefresh={onRefresh}
|
||||
onDelete={onDelete}
|
||||
onShowSettings={onShowSettings}
|
||||
/>
|
||||
),
|
||||
},
|
||||
];
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
// const ACTION_IMPLEMENTATIONS = {
|
||||
// 'default': (provider, callbacks) => [{
|
||||
// id: 'default-provider-actions',
|
||||
// renderButton: () => <DefaultProviderActions {...} />
|
||||
// }],
|
||||
//
|
||||
// 'ollama': (provider, callbacks) => [{
|
||||
// id: 'ollama-actions',
|
||||
// renderButton: () => <OllamaActions {...} />
|
||||
// }]
|
||||
// };
|
||||
@@ -1,7 +0,0 @@
|
||||
export default interface ProviderCallbacks {
|
||||
onShowModal?: () => void;
|
||||
onAdd?: () => void;
|
||||
onDelete?: () => void;
|
||||
onShowSettings?: () => void;
|
||||
onRefresh?: () => void;
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Card } from '../../../../ui/card';
|
||||
import ProviderSetupOverlay from './configuration_modal_subcomponents/ProviderSetupOverlay';
|
||||
import ProviderSetupHeader from './configuration_modal_subcomponents/ProviderSetupHeader';
|
||||
import ProviderSetupForm from './configuration_modal_subcomponents/ProviderSetupForm';
|
||||
import ProviderSetupActions from './configuration_modal_subcomponents/ProviderSetupActions';
|
||||
import ProviderConfiguationModalProps from './interfaces/ProviderConfigurationModalProps';
|
||||
|
||||
export default function ProviderConfigurationModal({
|
||||
provider,
|
||||
title,
|
||||
onSubmit,
|
||||
onCancel,
|
||||
}: ProviderConfiguationModalProps) {
|
||||
const [configValues, setConfigValues] = React.useState<{ [key: string]: string }>({});
|
||||
const headerText = title || `Setup ${provider}`;
|
||||
|
||||
const handleSubmitForm = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
onSubmit(configValues);
|
||||
};
|
||||
|
||||
return (
|
||||
<ProviderSetupOverlay>
|
||||
<Card className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[500px] bg-bgApp rounded-xl overflow-hidden shadow-none p-[16px] pt-[24px] pb-0">
|
||||
<div className="px-4 pb-0 space-y-8">
|
||||
<ProviderSetupHeader headerText={headerText} />
|
||||
|
||||
<ProviderSetupForm
|
||||
configValues={configValues}
|
||||
setConfigValues={setConfigValues}
|
||||
onSubmit={handleSubmitForm}
|
||||
provider={provider}
|
||||
/>
|
||||
|
||||
<ProviderSetupActions onCancel={onCancel} />
|
||||
</div>
|
||||
</Card>
|
||||
</ProviderSetupOverlay>
|
||||
);
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Input } from '../../../../../ui/input';
|
||||
import { Lock } from 'lucide-react';
|
||||
import { isSecretKey } from '../../../../api_keys/utils';
|
||||
import ProviderSetupFormProps from '../interfaces/ProviderSetupFormProps';
|
||||
import ParameterSchema from '../../interfaces/ParameterSchema';
|
||||
|
||||
/**
|
||||
* Renders the form with required input fields and the "lock" info row.
|
||||
* The submit/cancel buttons are in a separate ProviderSetupActions component.
|
||||
*/
|
||||
export default function ProviderSetupForm({
|
||||
configValues,
|
||||
setConfigValues,
|
||||
onSubmit,
|
||||
provider,
|
||||
}: ProviderSetupFormProps) {
|
||||
const parameters: ParameterSchema[] = provider.parameters;
|
||||
return (
|
||||
<form onSubmit={onSubmit}>
|
||||
<div className="mt-[24px] space-y-4">
|
||||
{parameters.map((parameter) => (
|
||||
<div key={parameter.name}>
|
||||
<Input
|
||||
type={parameter.is_secret ? 'password' : 'text'}
|
||||
value={configValues[parameter.name] || ''}
|
||||
onChange={(e) =>
|
||||
setConfigValues((prev) => ({
|
||||
...prev,
|
||||
[parameter.name]: e.target.value,
|
||||
}))
|
||||
}
|
||||
placeholder={parameter.name}
|
||||
className="w-full h-14 px-4 font-regular rounded-lg border shadow-none border-gray-300 bg-white text-lg placeholder:text-gray-400 font-regular text-gray-900"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
<div className="flex text-gray-600 dark:text-gray-300">
|
||||
<Lock className="w-6 h-6" />
|
||||
<span className="text-sm font-light ml-4 mt-[2px]">
|
||||
Your configuration values will be stored securely in the keychain and used only for
|
||||
making requests to {provider.name}.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{/* The action buttons are not in this form; they're in ProviderSetupActions. */}
|
||||
</form>
|
||||
);
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
interface ProviderSetupHeaderProps {
|
||||
headerText: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the header (title) for the modal.
|
||||
*/
|
||||
export default function ProviderSetupHeader({ headerText }: ProviderSetupHeaderProps) {
|
||||
return (
|
||||
<div className="flex">
|
||||
<h2 className="text-2xl font-regular text-textStandard">{headerText}</h2>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
import React from 'react';
|
||||
import { AddButton, DeleteButton, GearSettingsButton } from './ActionButtons';
|
||||
|
||||
interface ProviderActionsProps {
|
||||
name: string;
|
||||
isConfigured: boolean;
|
||||
onAdd?: () => void;
|
||||
onConfigure?: () => void;
|
||||
onDelete?: () => void;
|
||||
onShowSettings?: () => void;
|
||||
}
|
||||
|
||||
function getDefaultTooltipMessages(name: string, actionType: string) {
|
||||
switch (actionType) {
|
||||
case 'add':
|
||||
return `Configure ${name} settings`;
|
||||
case 'edit':
|
||||
return `Edit ${name} settings`;
|
||||
case 'delete':
|
||||
return `Delete ${name} settings`;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export default function DefaultProviderActions({
|
||||
name,
|
||||
isConfigured,
|
||||
onAdd,
|
||||
onDelete,
|
||||
onShowSettings,
|
||||
}: ProviderActionsProps) {
|
||||
return (
|
||||
<>
|
||||
{/*Set up an unconfigured provider */}
|
||||
{!isConfigured && (
|
||||
<AddButton
|
||||
tooltip={getDefaultTooltipMessages(name, 'add')}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onAdd?.();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{/*Edit settings of configured provider*/}
|
||||
{isConfigured && (
|
||||
<GearSettingsButton
|
||||
tooltip={getDefaultTooltipMessages(name, 'edit')}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onShowSettings?.();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{/*Delete configuration*/}
|
||||
{isConfigured && (
|
||||
<DeleteButton
|
||||
tooltip={getDefaultTooltipMessages(name, 'delete')}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onDelete?.();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
import React from 'react';
|
||||
import { AddButton, DeleteButton, GearSettingsButton, RefreshButton } from './ActionButtons';
|
||||
import OllamaMetadata from '../../interfaces/OllamaMetadata';
|
||||
|
||||
interface OllamaActionsProps {
|
||||
isConfigured: boolean;
|
||||
ollamaMetadata: OllamaMetadata;
|
||||
onRefresh?: (e: React.MouseEvent) => void;
|
||||
onAdd?: () => void;
|
||||
onDelete?: () => void;
|
||||
onShowSettings?: () => void;
|
||||
}
|
||||
|
||||
export default function OllamaActions({
|
||||
isConfigured,
|
||||
ollamaMetadata,
|
||||
onRefresh,
|
||||
onAdd,
|
||||
onDelete,
|
||||
onShowSettings,
|
||||
}: OllamaActionsProps) {
|
||||
const showHostDeleteButton = isConfigured && ollamaMetadata.location === 'host' && !onDelete;
|
||||
|
||||
const showRefreshButton = !isConfigured && onRefresh;
|
||||
|
||||
// add host url to overwrite the app url OR if not configured at all yet
|
||||
const showAddHostUrlButton =
|
||||
(isConfigured && ollamaMetadata.location === 'app' && onAdd) || (!isConfigured && onAdd);
|
||||
|
||||
const showHostUrlSettingsButton =
|
||||
isConfigured && ollamaMetadata.location === 'host' && onShowSettings;
|
||||
|
||||
// We’ll figure out which buttons to render:
|
||||
|
||||
// 1) Refresh button if not configured
|
||||
// 2) If configured via app => show "plus" to switch to host config
|
||||
// 3) If configured via host => show "X" to remove the host and "gear" to edit
|
||||
|
||||
return (
|
||||
// TODO: is this the right class name?
|
||||
<div className="flex items-center space-x-2">
|
||||
{/* (1) Refresh button if not configured */}
|
||||
{showRefreshButton && (
|
||||
<RefreshButton
|
||||
tooltip="Refresh to check if Ollama is running."
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onRefresh?.(e);
|
||||
}}
|
||||
></RefreshButton>
|
||||
)}
|
||||
|
||||
{/* (2) If configured location = 'app', show a plus button to switch / set host */}
|
||||
{showAddHostUrlButton && (
|
||||
<AddButton
|
||||
tooltip="Switch to custom OLLAMA_HOST."
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onAdd?.();
|
||||
}}
|
||||
></AddButton>
|
||||
)}
|
||||
|
||||
{/* (3) If configured location = 'host', show an X to delete or revert config */}
|
||||
{showHostDeleteButton && (
|
||||
<DeleteButton
|
||||
tooltip="Delete OLLAMA_HOST."
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onDelete?.();
|
||||
}}
|
||||
></DeleteButton>
|
||||
)}
|
||||
|
||||
{/* (4) If configured location = 'host', show a gear to view and edit config */}
|
||||
{showHostUrlSettingsButton && (
|
||||
<GearSettingsButton
|
||||
tooltip={'View and edit OLLAMA_HOST'}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onShowSettings?.();
|
||||
}}
|
||||
></GearSettingsButton>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import React from 'react';
|
||||
import { ProviderCard } from './subcomponents/ProviderCard';
|
||||
import ProviderState from './interfaces/ProviderState';
|
||||
import OnRefresh from './callbacks/RefreshActiveProviders';
|
||||
import { ProviderModalProvider, useProviderModal } from './modal/ProviderModalProvider';
|
||||
import ProviderConfigurationModal from './modal/ProviderConfiguationModal';
|
||||
|
||||
function GridLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<div className="grid grid-cols-[repeat(auto-fill,_minmax(140px,_1fr))] gap-3 [&_*]:z-20">
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ProviderCards({
|
||||
providers,
|
||||
isOnboarding,
|
||||
}: {
|
||||
providers: ProviderState[];
|
||||
isOnboarding: boolean;
|
||||
}) {
|
||||
const { openModal } = useProviderModal();
|
||||
|
||||
// Define the callbacks for provider actions
|
||||
const providerCallbacks = {
|
||||
// Replace your OnShowSettings with the modal opener
|
||||
onConfigure: (provider: ProviderState) => {
|
||||
console.log('Configure button clicked for:', provider.name);
|
||||
openModal(provider, {
|
||||
onSubmit: (values: any) => {
|
||||
console.log(`Configuring ${provider.name}:`, values);
|
||||
// Your logic to save the configuration
|
||||
},
|
||||
formProps: {},
|
||||
});
|
||||
console.log('openModal called'); // Check if this executes
|
||||
},
|
||||
onLaunch: (provider: ProviderState) => {
|
||||
OnRefresh();
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{providers.map((provider) => (
|
||||
<ProviderCard
|
||||
key={provider.name} // helps React efficiently update and track components when rendering lists
|
||||
provider={provider}
|
||||
buttonCallbacks={providerCallbacks}
|
||||
isOnboarding={isOnboarding}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default function ProviderGrid({
|
||||
providers,
|
||||
isOnboarding,
|
||||
}: {
|
||||
providers: ProviderState[];
|
||||
isOnboarding: boolean;
|
||||
}) {
|
||||
console.log('(1) Provider Grid -- is this the onboarding page?', isOnboarding);
|
||||
return (
|
||||
<GridLayout>
|
||||
<ProviderModalProvider>
|
||||
<ProviderCards providers={providers} isOnboarding={isOnboarding} />
|
||||
<ProviderConfigurationModal /> {/* This is missing! */}
|
||||
</ProviderModalProvider>
|
||||
</GridLayout>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
import React from 'react';
|
||||
import ProviderDetails from './interfaces/ProviderDetails';
|
||||
import DefaultCardButtons from './subcomponents/buttons/DefaultCardButtons';
|
||||
import ButtonCallbacks from '@/src/components/settings_v2/providers/interfaces/ButtonCallbacks';
|
||||
import ProviderState from '@/src/components/settings_v2/providers/interfaces/ProviderState';
|
||||
|
||||
// Helper function to generate default actions for most providers
|
||||
const getDefaultButtons = (
|
||||
provider: ProviderState,
|
||||
callbacks: ButtonCallbacks,
|
||||
isOnboardingPage
|
||||
) => {
|
||||
return [
|
||||
{
|
||||
id: 'default-buttons',
|
||||
renderButton: () => (
|
||||
<DefaultCardButtons
|
||||
provider={provider}
|
||||
callbacks={callbacks}
|
||||
isOnboardingPage={isOnboardingPage}
|
||||
/>
|
||||
),
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
export interface ProviderRegistry {
|
||||
name: string;
|
||||
details: ProviderDetails;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provider Registry System
|
||||
* ========================
|
||||
*
|
||||
* This registry defines all available providers and how they behave in the UI.
|
||||
* It works with a dynamic modal system to create a flexible, extensible architecture
|
||||
* for managing provider configurations.
|
||||
*
|
||||
* How the System Works:
|
||||
* --------------------
|
||||
*
|
||||
* 1. Provider Definition:
|
||||
* Each provider entry in the registry defines its core properties:
|
||||
* - Basic info (id, name, description)
|
||||
* - Parameters needed for configuration
|
||||
* - Optional custom form component
|
||||
* - Action buttons that appear on provider cards
|
||||
*
|
||||
* 2. Two-Level Configuration:
|
||||
* a) Provider Card UI - What buttons appear on each provider card
|
||||
* - Controlled by the provider's getActions() function
|
||||
* - Most providers use default buttons (configure/launch)
|
||||
* - Can be customized for special providers
|
||||
*
|
||||
* b) Modal Content - What form appears in the configuration modal
|
||||
* - A single modal component exists in the app
|
||||
* - Content changes dynamically based on the provider being configured
|
||||
* - If provider has CustomForm property, that component is rendered
|
||||
* - Otherwise, DefaultProviderForm renders based on parameters array
|
||||
*
|
||||
* 3. Modal Flow:
|
||||
* - User clicks Configure button on a provider card
|
||||
* - Button handler calls openModal() with the provider object
|
||||
* - Modal context stores the current provider and opens the modal
|
||||
* - ProviderConfigModal checks for CustomForm on the current provider
|
||||
* - Appropriate form is rendered with provider data passed as props
|
||||
*
|
||||
* Adding a New Provider:
|
||||
* ---------------------
|
||||
*
|
||||
* For a standard provider with simple configuration:
|
||||
* - Define parameters array with all required fields
|
||||
* - Use the default getActions function
|
||||
* - No need to specify a CustomForm
|
||||
*
|
||||
* For a provider needing custom configuration:
|
||||
* - Define parameters array (even if just for documentation)
|
||||
* - Create a custom form component and assign to CustomForm property
|
||||
* - Use the default or custom getActions function
|
||||
*
|
||||
* This architecture centralizes provider definitions while allowing
|
||||
* flexibility for special cases, keeping the codebase maintainable.
|
||||
*/
|
||||
|
||||
export const PROVIDER_REGISTRY: ProviderRegistry[] = [
|
||||
{
|
||||
name: 'OpenAI',
|
||||
details: {
|
||||
id: 'openai',
|
||||
name: 'OpenAI',
|
||||
description: 'Access GPT-4, GPT-3.5 Turbo, and other OpenAI models',
|
||||
parameters: [
|
||||
{
|
||||
name: 'OPENAI_API_KEY',
|
||||
is_secret: true,
|
||||
},
|
||||
],
|
||||
getActions: (provider, callbacks, isOnboardingPage) =>
|
||||
getDefaultButtons(provider, callbacks, isOnboardingPage),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Anthropic',
|
||||
details: {
|
||||
id: 'anthropic',
|
||||
name: 'Anthropic',
|
||||
description: 'Access Claude and other Anthropic models',
|
||||
parameters: [
|
||||
{
|
||||
name: 'ANTHROPIC_API_KEY',
|
||||
is_secret: true,
|
||||
},
|
||||
],
|
||||
getActions: (provider, callbacks, isOnboardingPage) =>
|
||||
getDefaultButtons(provider, callbacks, isOnboardingPage),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Google',
|
||||
details: {
|
||||
id: 'google',
|
||||
name: 'Google',
|
||||
description: 'Access Gemini and other Google AI models',
|
||||
parameters: [
|
||||
{
|
||||
name: 'GOOGLE_API_KEY',
|
||||
is_secret: true,
|
||||
},
|
||||
],
|
||||
getActions: (provider, callbacks, isOnboardingPage) =>
|
||||
getDefaultButtons(provider, callbacks, isOnboardingPage),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Groq',
|
||||
details: {
|
||||
id: 'groq',
|
||||
name: 'Groq',
|
||||
description: 'Access Mixtral and other Groq-hosted models',
|
||||
parameters: [
|
||||
{
|
||||
name: 'GROQ_API_KEY',
|
||||
is_secret: true,
|
||||
},
|
||||
],
|
||||
getActions: (provider, callbacks, isOnboardingPage) =>
|
||||
getDefaultButtons(provider, callbacks, isOnboardingPage),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Databricks',
|
||||
details: {
|
||||
id: 'databricks',
|
||||
name: 'Databricks',
|
||||
description: 'Access models hosted on your Databricks instance',
|
||||
parameters: [
|
||||
{
|
||||
name: 'DATABRICKS_HOST',
|
||||
is_secret: false,
|
||||
},
|
||||
],
|
||||
getActions: (provider, callbacks, isOnboardingPage) =>
|
||||
getDefaultButtons(provider, callbacks, isOnboardingPage),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'OpenRouter',
|
||||
details: {
|
||||
id: 'openrouter',
|
||||
name: 'OpenRouter',
|
||||
description: 'Access a variety of AI models through OpenRouter',
|
||||
parameters: [
|
||||
{
|
||||
name: 'OPENROUTER_API_KEY',
|
||||
is_secret: true,
|
||||
},
|
||||
],
|
||||
getActions: (provider, callbacks, isOnboardingPage) =>
|
||||
getDefaultButtons(provider, callbacks, isOnboardingPage),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Ollama',
|
||||
details: {
|
||||
id: 'ollama',
|
||||
name: 'Ollama',
|
||||
description: 'Run and use open-source models locally',
|
||||
parameters: [
|
||||
{
|
||||
name: 'OLLAMA_HOST',
|
||||
is_secret: false,
|
||||
},
|
||||
],
|
||||
getActions: (provider, callbacks, isOnboardingPage) =>
|
||||
getDefaultButtons(provider, callbacks, isOnboardingPage),
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { ScrollArea } from '../../../ui/scroll-area';
|
||||
import BackButton from '../../../ui/BackButton';
|
||||
import { ScrollArea } from '../../ui/scroll-area';
|
||||
import BackButton from '../../ui/BackButton';
|
||||
import ProviderGrid from './ProviderGrid';
|
||||
import ProviderState from './interfaces/ProviderState';
|
||||
|
||||
@@ -68,7 +68,7 @@ export default function ProviderSettings({ onClose }: { onClose: () => void }) {
|
||||
{/* Content Area */}
|
||||
<div className="max-w-5xl pt-4 px-8">
|
||||
<div className="relative z-10">
|
||||
<ProviderGrid providers={fakeProviderState} />
|
||||
<ProviderGrid providers={fakeProviderState} isOnboarding={true} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,6 @@
|
||||
import ProviderState from '../interfaces/ProviderState';
|
||||
|
||||
export default interface ButtonCallbacks {
|
||||
onConfigure?: (provider: ProviderState) => void;
|
||||
onLaunch?: (provider: ProviderState) => void;
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
// metadata and action builder
|
||||
import ProviderState from './ProviderState';
|
||||
import ConfigurationAction from './ConfigurationAction';
|
||||
import ParameterSchema from '../parameters/interfaces/ParameterSchema';
|
||||
import ProviderCallbacks from './ConfigurationCallbacks';
|
||||
import ParameterSchema from '../interfaces/ParameterSchema';
|
||||
import ButtonCallbacks from './ButtonCallbacks';
|
||||
|
||||
export default interface ProviderDetails {
|
||||
id: string;
|
||||
@@ -10,5 +10,9 @@ export default interface ProviderDetails {
|
||||
description: string;
|
||||
parameters: ParameterSchema[];
|
||||
getTags?: (name: string) => string[];
|
||||
getActions?: (provider: ProviderState, callbacks: ProviderCallbacks) => ConfigurationAction[];
|
||||
getActions?: (
|
||||
provider: ProviderState,
|
||||
callbacks: ButtonCallbacks,
|
||||
isOnboardingPage: boolean
|
||||
) => ConfigurationAction[];
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import ProviderSetupOverlay from './subcomponents/ProviderSetupOverlay';
|
||||
import ProviderSetupHeader from './subcomponents/ProviderSetupHeader';
|
||||
import DefaultProviderSetupForm from './subcomponents/forms/DefaultProviderSetupForm';
|
||||
import ProviderSetupActions from './subcomponents/ProviderSetupActions';
|
||||
import ProviderLogo from './subcomponents/ProviderLogo';
|
||||
import ProviderConfiguationModalProps from './interfaces/ProviderConfigurationModalProps';
|
||||
import { useProviderModal } from './ProviderModalProvider';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
export default function ProviderConfigurationModal() {
|
||||
const { isOpen, currentProvider, modalProps, closeModal } = useProviderModal();
|
||||
console.log('currentProvider', currentProvider);
|
||||
const [configValues, setConfigValues] = useState({});
|
||||
|
||||
// Reset form values when provider changes
|
||||
useEffect(() => {
|
||||
if (currentProvider) {
|
||||
// Initialize form with default values
|
||||
const initialValues = {};
|
||||
if (currentProvider.parameters) {
|
||||
currentProvider.parameters.forEach((param) => {
|
||||
initialValues[param.name] = param.defaultValue || '';
|
||||
});
|
||||
}
|
||||
setConfigValues(initialValues);
|
||||
} else {
|
||||
setConfigValues({});
|
||||
}
|
||||
}, [currentProvider]);
|
||||
|
||||
if (!isOpen || !currentProvider) return null;
|
||||
|
||||
const headerText = `Configure ${currentProvider.name}`;
|
||||
const descriptionText = `Add your generated api keys for this provider to integrate into Goose`;
|
||||
|
||||
// Use custom form component if provider specifies one, otherwise use default
|
||||
const FormComponent = currentProvider.CustomForm || DefaultProviderSetupForm;
|
||||
|
||||
const handleSubmitForm = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
// Use custom submit handler if provided in modalProps
|
||||
if (modalProps.onSubmit) {
|
||||
modalProps.onSubmit(configValues);
|
||||
} else {
|
||||
// Default submit behavior
|
||||
toast('Submitted configuration!');
|
||||
}
|
||||
|
||||
closeModal();
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
// Use custom cancel handler if provided
|
||||
if (modalProps.onCancel) {
|
||||
modalProps.onCancel();
|
||||
}
|
||||
|
||||
closeModal();
|
||||
};
|
||||
|
||||
return (
|
||||
<ProviderSetupOverlay>
|
||||
<div className="space-y-1">
|
||||
{/* Logo area - centered above title */}
|
||||
<ProviderLogo providerName={currentProvider.id} />
|
||||
{/* Title and some information - centered */}
|
||||
<ProviderSetupHeader title={headerText} body={descriptionText} />
|
||||
</div>
|
||||
|
||||
{/* Contains information used to set up each provider */}
|
||||
<FormComponent
|
||||
configValues={configValues}
|
||||
setConfigValues={setConfigValues}
|
||||
onSubmit={handleSubmitForm}
|
||||
provider={currentProvider}
|
||||
{...(modalProps.formProps || {})} // Spread any custom form props
|
||||
/>
|
||||
|
||||
<ProviderSetupActions onCancel={handleCancel} />
|
||||
</ProviderSetupOverlay>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import React, { createContext, useContext, useState } from 'react';
|
||||
|
||||
const ProviderModalContext = createContext({
|
||||
isOpen: false,
|
||||
currentProvider: null,
|
||||
modalProps: {},
|
||||
openModal: (provider, additionalProps) => {},
|
||||
closeModal: () => {},
|
||||
});
|
||||
|
||||
export const useProviderModal = () => useContext(ProviderModalContext);
|
||||
|
||||
export const ProviderModalProvider = ({ children }) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [currentProvider, setCurrentProvider] = useState(null);
|
||||
const [modalProps, setModalProps] = useState({});
|
||||
|
||||
const openModal = (provider, additionalProps = {}) => {
|
||||
setCurrentProvider(provider);
|
||||
setModalProps(additionalProps);
|
||||
setIsOpen(true);
|
||||
};
|
||||
|
||||
const closeModal = () => {
|
||||
setIsOpen(false);
|
||||
// Use a small timeout to prevent UI flicker
|
||||
setTimeout(() => {
|
||||
setCurrentProvider(null);
|
||||
setModalProps({});
|
||||
}, 200);
|
||||
};
|
||||
|
||||
return (
|
||||
<ProviderModalContext.Provider
|
||||
value={{
|
||||
isOpen,
|
||||
currentProvider,
|
||||
modalProps,
|
||||
openModal,
|
||||
closeModal,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</ProviderModalContext.Provider>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export const QUICKSTART_GUIDE_URL = 'https://block.github.io/goose/docs/quickstart';
|
||||
@@ -1,9 +1,9 @@
|
||||
import React from 'react';
|
||||
import ProviderDetails from '../../interfaces/ProviderDetails';
|
||||
import ProviderState from '../../interfaces/ProviderState';
|
||||
|
||||
export default interface ProviderSetupFormProps {
|
||||
configValues: { [key: string]: string };
|
||||
setConfigValues: React.Dispatch<React.SetStateAction<{ [key: string]: string }>>;
|
||||
onSubmit: (e: React.FormEvent) => void;
|
||||
provider: ProviderDetails;
|
||||
provider: ProviderState;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import React from 'react';
|
||||
import OpenAILogo from './icons/openai@3x.png';
|
||||
import AnthropicLogo from './icons/anthropic@3x.png';
|
||||
import GoogleLogo from './icons/google@3x.png';
|
||||
import GroqLogo from './icons/groq@3x.png';
|
||||
import OllamaLogo from './icons/ollama@3x.png';
|
||||
import DatabricksLogo from './icons/databricks@3x.png';
|
||||
import OpenRouterLogo from './icons/openrouter@3x.png';
|
||||
|
||||
// Map provider names to their logos
|
||||
const providerLogos = {
|
||||
openai: OpenAILogo,
|
||||
anthropic: AnthropicLogo,
|
||||
google: GoogleLogo,
|
||||
groq: GroqLogo,
|
||||
ollama: OllamaLogo,
|
||||
databricks: DatabricksLogo,
|
||||
openrouter: OpenRouterLogo,
|
||||
};
|
||||
|
||||
export default function ProviderLogo({ providerName }) {
|
||||
// Convert provider name to lowercase and fetch the logo
|
||||
const logoKey = providerName.toLowerCase();
|
||||
const logo = providerLogos[logoKey] || OpenAILogo; // TODO: need default icon
|
||||
|
||||
return (
|
||||
<div className="flex justify-center mb-2">
|
||||
<div className="w-12 h-12 bg-black rounded-full overflow-hidden flex items-center justify-center">
|
||||
<img src={logo} alt={`${providerName} logo`} className="w-16 h-16 object-contain" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { Button } from '../../../../../ui/button';
|
||||
import { Button } from '../../../../ui/button';
|
||||
|
||||
interface ProviderSetupActionsProps {
|
||||
onCancel: () => void;
|
||||
@@ -7,16 +7,16 @@ interface ProviderSetupActionsProps {
|
||||
|
||||
/**
|
||||
* Renders the "Submit" and "Cancel" buttons at the bottom.
|
||||
* Notice we rely on the parent's `onSubmit` in the form, so we only handle Cancel here.
|
||||
* Updated to match the design from screenshots.
|
||||
*/
|
||||
export default function ProviderSetupActions({ onCancel }: ProviderSetupActionsProps) {
|
||||
return (
|
||||
<div className="mt-[8px] -ml-8 -mr-8 pt-8">
|
||||
<div className="mt-8 -ml-8 -mr-8">
|
||||
{/* We rely on the <form> "onSubmit" for the actual Submit logic */}
|
||||
<Button
|
||||
type="submit"
|
||||
variant="ghost"
|
||||
className="w-full h-[60px] rounded-none border-t border-borderSubtle text-md hover:bg-bgSubtle text-textProminent font-regular"
|
||||
className="w-full h-[60px] rounded-none border-t border-borderSubtle text-md hover:bg-bgSubtle text-textProminent font-medium"
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
@@ -0,0 +1,29 @@
|
||||
import React from 'react';
|
||||
import { ExternalLink } from 'lucide-react';
|
||||
import { QUICKSTART_GUIDE_URL } from '../constants';
|
||||
|
||||
interface ProviderSetupHeaderProps {
|
||||
title: string;
|
||||
body: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the header (title + description + link to guide) for the modal.
|
||||
*/
|
||||
export default function ProviderSetupHeader({ title, body }: ProviderSetupHeaderProps) {
|
||||
return (
|
||||
<div className="text-center">
|
||||
<h2 className="text-xl font-medium text-textStandard mb-3">{title}</h2>
|
||||
<div className="text-lg text-gray-400 font-light mb-4">{body}</div>
|
||||
<a
|
||||
href={QUICKSTART_GUIDE_URL}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center justify-center text-textProminent text-sm"
|
||||
>
|
||||
<ExternalLink size={16} className="mr-1" />
|
||||
View quick start guide
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import { Card } from '../../../../ui/card';
|
||||
|
||||
interface ProviderSetupOverlayProps {
|
||||
children: React.ReactNode;
|
||||
@@ -10,7 +11,9 @@ interface ProviderSetupOverlayProps {
|
||||
export default function ProviderSetupOverlay({ children }: ProviderSetupOverlayProps) {
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black/20 dark:bg-white/20 backdrop-blur-sm transition-colors animate-[fadein_200ms_ease-in_forwards]">
|
||||
{children}
|
||||
<Card className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[500px] bg-bgApp rounded-xl overflow-hidden shadow-none p-[16px] pt-[24px] pb-0">
|
||||
<div className="px-4 pb-0 space-y-6">{children}</div>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import React from 'react';
|
||||
import { Input } from '../../../../../ui/input';
|
||||
import { Lock } from 'lucide-react';
|
||||
import ProviderSetupFormProps from '../../interfaces/ProviderSetupFormProps';
|
||||
import ParameterSchema from '../../../interfaces/ParameterSchema';
|
||||
import { PROVIDER_REGISTRY } from '../../../ProviderRegistry';
|
||||
|
||||
export default function DefaultProviderSetupForm({
|
||||
configValues,
|
||||
setConfigValues,
|
||||
onSubmit,
|
||||
provider,
|
||||
}: ProviderSetupFormProps) {
|
||||
const providerEntry = PROVIDER_REGISTRY.find((p) => p.name === provider.name);
|
||||
const parameters: ParameterSchema[] = providerEntry.details.parameters;
|
||||
|
||||
return (
|
||||
<form onSubmit={onSubmit}>
|
||||
<div className="mt-4 space-y-4">
|
||||
{parameters.map((parameter) => (
|
||||
<div key={parameter.name}>
|
||||
<Input
|
||||
type={parameter.is_secret ? 'password' : 'text'}
|
||||
value={configValues[parameter.name] || ''}
|
||||
onChange={(e) =>
|
||||
setConfigValues((prev) => ({
|
||||
...prev,
|
||||
[parameter.name]: e.target.value,
|
||||
}))
|
||||
}
|
||||
placeholder={parameter.name.replace(/_/g, ' ')}
|
||||
className="w-full h-14 px-4 font-regular rounded-lg border shadow-none border-gray-300 bg-white text-lg placeholder:text-gray-400 font-regular text-gray-900"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<div className="flex items-start mt-2 text-gray-600 dark:text-gray-300">
|
||||
<Lock className="w-5 h-5 mt-1" />
|
||||
<span className="text-sm font-light ml-2">Keys are stored in a secure .env file</span>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
@@ -7,9 +7,8 @@ interface CardBodyProps {
|
||||
}
|
||||
|
||||
export default function CardBody({ actions }: CardBodyProps) {
|
||||
console.log('in card body');
|
||||
return (
|
||||
<div className="space-x-2 text-center flex items-center justify-between">
|
||||
<div className="flex items-center justify-start">
|
||||
<CardActions actions={actions} />
|
||||
</div>
|
||||
);
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { ExclamationButton, GreenCheckButton } from './actions/ActionButtons';
|
||||
import { ExclamationButton, GreenCheckButton } from './buttons/CardButtons';
|
||||
import {
|
||||
ConfiguredProviderTooltipMessage,
|
||||
OllamaNotConfiguredTooltipMessage,
|
||||
@@ -28,7 +28,7 @@ function ProviderNameAndStatus({ name, isConfigured }: ProviderNameAndStatusProp
|
||||
const ollamaNotConfigured = !isConfigured && name === 'Ollama';
|
||||
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
<div className="flex items-center justify-between w-full">
|
||||
<CardTitle name={name} />
|
||||
|
||||
{/* Configured state: Green check */}
|
||||
@@ -3,15 +3,16 @@ import CardContainer from './CardContainer';
|
||||
import CardHeader from './CardHeader';
|
||||
import ProviderState from '../interfaces/ProviderState';
|
||||
import CardBody from './CardBody';
|
||||
import ProviderCallbacks from '../interfaces/ConfigurationCallbacks';
|
||||
import ButtonCallbacks from '../interfaces/ButtonCallbacks';
|
||||
import { PROVIDER_REGISTRY } from '../ProviderRegistry';
|
||||
|
||||
interface ProviderCardProps {
|
||||
provider: ProviderState;
|
||||
providerCallbacks: ProviderCallbacks;
|
||||
buttonCallbacks: ButtonCallbacks;
|
||||
isOnboarding: boolean;
|
||||
}
|
||||
|
||||
export function ProviderCard({ provider, providerCallbacks }: ProviderCardProps) {
|
||||
export function ProviderCard({ provider, buttonCallbacks, isOnboarding }: ProviderCardProps) {
|
||||
const providerEntry = PROVIDER_REGISTRY.find((p) => p.name === provider.name);
|
||||
|
||||
// Add safety check
|
||||
@@ -29,7 +30,7 @@ export function ProviderCard({ provider, providerCallbacks }: ProviderCardProps)
|
||||
console.log('provider details', providerDetails);
|
||||
|
||||
try {
|
||||
const actions = providerDetails.getActions(provider, providerCallbacks);
|
||||
const actions = providerDetails.getActions(provider, buttonCallbacks, isOnboarding);
|
||||
|
||||
return (
|
||||
<CardContainer
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import { Button } from '../../../../../ui/button';
|
||||
import { Button } from '../../../../ui/button';
|
||||
import clsx from 'clsx';
|
||||
import { TooltipWrapper } from './TooltipWrapper';
|
||||
import { Check, CircleHelp, Plus, RefreshCw, Rocket, Settings, X } from 'lucide-react';
|
||||
import { Check, CircleHelp, Plus, RefreshCw, Rocket, Sliders, X } from 'lucide-react';
|
||||
|
||||
interface ActionButtonProps extends React.ComponentProps<typeof Button> {
|
||||
/** Icon component to render, e.g. `RefreshCw` from lucide-react */
|
||||
@@ -11,11 +11,15 @@ interface ActionButtonProps extends React.ComponentProps<typeof Button> {
|
||||
tooltip?: React.ReactNode;
|
||||
/** Additional classes for styling. */
|
||||
className?: string;
|
||||
/** Text to display next to the icon */
|
||||
text?: string;
|
||||
/** Additional class for the icon specifically */
|
||||
iconClassName?: string;
|
||||
}
|
||||
|
||||
// className is the styling for the <Button/> component -- below is the default
|
||||
// Base styles for all action buttons
|
||||
const baseActionButtonClasses = `
|
||||
rounded-full h-7 w-7 p-0
|
||||
rounded-full
|
||||
bg-bgApp hover:bg-bgApp shadow-none
|
||||
text-textSubtle
|
||||
border border-borderSubtle
|
||||
@@ -24,22 +28,35 @@ const baseActionButtonClasses = `
|
||||
transition-colors
|
||||
`;
|
||||
|
||||
// Additional styles for icon-only buttons
|
||||
const iconOnlyClasses = `
|
||||
h-7 w-7 p-0
|
||||
`;
|
||||
|
||||
// Additional styles for buttons with text and icon
|
||||
const withTextClasses = `
|
||||
px-3 py-1
|
||||
`;
|
||||
|
||||
export function ActionButton({
|
||||
icon: Icon,
|
||||
size = 'sm',
|
||||
variant = 'default',
|
||||
tooltip,
|
||||
className,
|
||||
text,
|
||||
iconClassName,
|
||||
...props
|
||||
}: ActionButtonProps) {
|
||||
// Determine if this is an icon-only button or one with text
|
||||
const buttonStyle = text
|
||||
? clsx(baseActionButtonClasses, withTextClasses, className)
|
||||
: clsx(baseActionButtonClasses, iconOnlyClasses, className);
|
||||
|
||||
const ButtonElement = (
|
||||
<Button
|
||||
size={size}
|
||||
variant={variant}
|
||||
className={clsx(baseActionButtonClasses, className)}
|
||||
{...props}
|
||||
>
|
||||
{Icon && <Icon className="!size-4" />}
|
||||
<Button size={size} variant={variant} className={buttonStyle} {...props}>
|
||||
{Icon && <Icon className={clsx('!size-4', iconClassName)} />}
|
||||
{text && <span>{text}</span>}
|
||||
</Button>
|
||||
);
|
||||
|
||||
@@ -62,17 +79,14 @@ export function GreenCheckButton({
|
||||
icon={Check}
|
||||
tooltip={tooltip}
|
||||
className={`
|
||||
bg-green-100
|
||||
dark:bg-green-900/30
|
||||
text-green-600
|
||||
dark:text-green-500
|
||||
hover:bg-green-100
|
||||
hover:text-green-600
|
||||
border-none
|
||||
shadow-none
|
||||
w-5 h-5
|
||||
cursor-default
|
||||
${className} // Removed the nullish coalescing operator as default is provided
|
||||
${className}
|
||||
`}
|
||||
onClick={() => {}}
|
||||
{...props}
|
||||
@@ -84,8 +98,17 @@ export function ExclamationButton({ tooltip, className, ...props }: ActionButton
|
||||
return <ActionButton icon={CircleHelp} tooltip={tooltip} onClick={() => {}} {...props} />;
|
||||
}
|
||||
|
||||
export function GearSettingsButton({ tooltip, className, ...props }: ActionButtonProps) {
|
||||
return <ActionButton icon={Settings} tooltip={tooltip} className={className} {...props} />;
|
||||
export function ConfigureSettingsButton({ tooltip, className, ...props }: ActionButtonProps) {
|
||||
return (
|
||||
<ActionButton
|
||||
icon={Sliders}
|
||||
tooltip={tooltip}
|
||||
className={className}
|
||||
text={'Configure'}
|
||||
iconClassName="rotate-90"
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function AddButton({ tooltip, className, ...props }: ActionButtonProps) {
|
||||
@@ -101,5 +124,13 @@ export function RefreshButton({ tooltip, className, ...props }: ActionButtonProp
|
||||
}
|
||||
|
||||
export function RocketButton({ tooltip, className, ...props }: ActionButtonProps) {
|
||||
return <ActionButton icon={Rocket} tooltip={tooltip} className={className} {...props} />;
|
||||
return (
|
||||
<ActionButton
|
||||
icon={Rocket}
|
||||
tooltip={tooltip}
|
||||
className={className}
|
||||
text={'Launch'}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import React from 'react';
|
||||
import { ConfigureSettingsButton, RocketButton } from './CardButtons';
|
||||
import ButtonCallbacks from '../../interfaces/ButtonCallbacks';
|
||||
import ProviderState from '@/src/components/settings_v2/providers/interfaces/ProviderState';
|
||||
|
||||
// can define other optional callbacks as needed
|
||||
interface CardButtonsProps {
|
||||
provider: ProviderState;
|
||||
isOnboardingPage?: boolean;
|
||||
callbacks: ButtonCallbacks; // things like onConfigure, onDelete
|
||||
}
|
||||
|
||||
function getDefaultTooltipMessages(name: string, actionType: string) {
|
||||
switch (actionType) {
|
||||
case 'add':
|
||||
return `Configure ${name} settings`;
|
||||
case 'edit':
|
||||
return `Edit ${name} settings`;
|
||||
case 'delete':
|
||||
return `Delete ${name} settings`;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// This defines a group of buttons that will appear on the card
|
||||
/// Controlled by if a provider is configured and which version of the grid page we're on (onboarding vs settings page)
|
||||
/// This is the default button group
|
||||
///
|
||||
/// Settings page:
|
||||
/// - show configure button
|
||||
/// Onboarding page:
|
||||
/// - show configure button if NOT configured
|
||||
/// - show rocket launch button if configured
|
||||
///
|
||||
/// We inject what will happen if we click on a button via on<Function>
|
||||
/// - onConfigure: pop open a modal -- modal is configured dynamically
|
||||
/// - onLaunch: continue to chat window
|
||||
export default function DefaultCardButtons({
|
||||
provider,
|
||||
isOnboardingPage,
|
||||
callbacks,
|
||||
}: CardButtonsProps) {
|
||||
return (
|
||||
<>
|
||||
{/*Set up an unconfigured provider */}
|
||||
{!provider.isConfigured && (
|
||||
<ConfigureSettingsButton
|
||||
tooltip={getDefaultTooltipMessages(provider.name, 'add')}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
callbacks.onConfigure(provider);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{/*show edit tooltip instead when hovering over button for configured providers*/}
|
||||
{provider.isConfigured && !isOnboardingPage && (
|
||||
<ConfigureSettingsButton
|
||||
tooltip={getDefaultTooltipMessages(provider.name, 'edit')}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
callbacks.onConfigure(provider);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{/*show Launch button for configured providers on onboarding page*/}
|
||||
{provider.isConfigured && isOnboardingPage && (
|
||||
<RocketButton
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
callbacks.onLaunch(provider);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,11 +1,6 @@
|
||||
// TooltipWrapper.tsx
|
||||
import React from 'react';
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipTrigger,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
} from '../../../../../ui/Tooltip';
|
||||
import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from '../../../../ui/Tooltip';
|
||||
import { Portal } from '@radix-ui/react-portal';
|
||||
|
||||
interface TooltipWrapperProps {
|
||||