344 lines
11 KiB
TypeScript
344 lines
11 KiB
TypeScript
import React, { memo, useMemo, useCallback, useState } from 'react';
|
|
import { ProviderCard } from './subcomponents/ProviderCard';
|
|
import CardContainer from './subcomponents/CardContainer';
|
|
import ProviderConfigurationModal from './modal/ProviderConfigurationModal';
|
|
import type { CustomProviderConfigDto } from '@aaif/goose-sdk';
|
|
import type { ProviderDetails, UpdateCustomProviderRequest } from '../../../types/providers';
|
|
import {
|
|
acpCreateCustomProviderFromRequest,
|
|
acpGetCustomProvider,
|
|
acpDeleteCustomProvider,
|
|
acpUpdateCustomProviderFromRequest,
|
|
} from '../../../acp/providers';
|
|
import { Plus } from 'lucide-react';
|
|
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '../../ui/dialog';
|
|
import CustomProviderForm from './modal/subcomponents/forms/CustomProviderForm';
|
|
import { SwitchModelModal } from '../models/subcomponents/SwitchModelModal';
|
|
import { useModelAndProvider } from '../../ModelAndProviderContext';
|
|
import type { View } from '../../../utils/navigationUtils';
|
|
import { defineMessages, useIntl } from '../../../i18n';
|
|
|
|
const i18n = defineMessages({
|
|
addProvider: {
|
|
id: 'providerGrid.addProvider',
|
|
defaultMessage: 'Add Provider',
|
|
},
|
|
fromTemplateOrManual: {
|
|
id: 'providerGrid.fromTemplateOrManual',
|
|
defaultMessage: 'From template or manual setup',
|
|
},
|
|
editProvider: {
|
|
id: 'providerGrid.editProvider',
|
|
defaultMessage: 'Edit Provider',
|
|
},
|
|
configureProvider: {
|
|
id: 'providerGrid.configureProvider',
|
|
defaultMessage: 'Configure Provider',
|
|
},
|
|
addProviderTitle: {
|
|
id: 'providerGrid.addProviderTitle',
|
|
defaultMessage: 'Add Provider',
|
|
},
|
|
chooseModel: {
|
|
id: 'providerGrid.chooseModel',
|
|
defaultMessage: 'Choose Model',
|
|
},
|
|
});
|
|
|
|
const GridLayout = memo(function GridLayout({ children }: { children: React.ReactNode }) {
|
|
return (
|
|
<div
|
|
className="grid gap-4 [&_*]:z-20 p-1"
|
|
style={{
|
|
gridTemplateColumns: 'repeat(auto-fill, minmax(200px, 200px))',
|
|
justifyContent: 'center',
|
|
}}
|
|
>
|
|
{children}
|
|
</div>
|
|
);
|
|
});
|
|
|
|
const CustomProviderCard = memo(function CustomProviderCard({ onClick }: { onClick: () => void }) {
|
|
const intl = useIntl();
|
|
return (
|
|
<CardContainer
|
|
testId="add-custom-provider-card"
|
|
onClick={onClick}
|
|
header={null}
|
|
body={
|
|
<div className="flex flex-col items-center justify-center min-h-[200px]">
|
|
<Plus className="w-8 h-8 text-gray-400 mb-2" />
|
|
<div className="text-sm text-gray-600 dark:text-gray-400 text-center">
|
|
<div className="font-medium">{intl.formatMessage(i18n.addProvider)}</div>
|
|
<div className="text-xs text-gray-500 mt-1">
|
|
{intl.formatMessage(i18n.fromTemplateOrManual)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
grayedOut={false}
|
|
borderStyle="dashed"
|
|
/>
|
|
);
|
|
});
|
|
|
|
function ProviderCards({
|
|
providers,
|
|
isOnboarding,
|
|
refreshProviders,
|
|
setView,
|
|
onModelSelected,
|
|
}: {
|
|
providers: ProviderDetails[];
|
|
isOnboarding: boolean;
|
|
refreshProviders?: () => void;
|
|
setView?: (view: View) => void;
|
|
onModelSelected?: (model?: string) => void;
|
|
}) {
|
|
const intl = useIntl();
|
|
const [configuringProvider, setConfiguringProvider] = useState<ProviderDetails | null>(null);
|
|
const [showCustomProviderModal, setShowCustomProviderModal] = useState(false);
|
|
const [showSwitchModelModal, setShowSwitchModelModal] = useState(false);
|
|
const [switchModelProvider, setSwitchModelProvider] = useState<string | null>(null);
|
|
const [isActiveProvider, setIsActiveProvider] = useState(false);
|
|
const { getCurrentModelAndProvider } = useModelAndProvider();
|
|
const [editingProvider, setEditingProvider] = useState<{
|
|
id: string;
|
|
config: CustomProviderConfigDto;
|
|
isEditable: boolean;
|
|
providerType: string;
|
|
} | null>(null);
|
|
|
|
const handleProviderLaunchWithModelSelection = useCallback((provider: ProviderDetails) => {
|
|
setSwitchModelProvider(provider.name);
|
|
setShowSwitchModelModal(true);
|
|
}, []);
|
|
|
|
const openModal = useCallback(
|
|
(provider: ProviderDetails) => setConfiguringProvider(provider),
|
|
[]
|
|
);
|
|
|
|
const configureProviderViaModal = useCallback(
|
|
async (provider: ProviderDetails) => {
|
|
if (provider.provider_type === 'Custom') {
|
|
const result = await acpGetCustomProvider(provider.name);
|
|
|
|
if (result) {
|
|
setEditingProvider({
|
|
id: provider.name,
|
|
config: result.provider,
|
|
isEditable: result.editable,
|
|
providerType: provider.provider_type,
|
|
});
|
|
|
|
// Check if this is the active provider
|
|
try {
|
|
const providerModel = await getCurrentModelAndProvider();
|
|
setIsActiveProvider(provider.name === providerModel.provider);
|
|
} catch {
|
|
setIsActiveProvider(false);
|
|
}
|
|
|
|
setShowCustomProviderModal(true);
|
|
}
|
|
} else {
|
|
openModal(provider);
|
|
}
|
|
},
|
|
[openModal, getCurrentModelAndProvider]
|
|
);
|
|
|
|
const handleUpdateCustomProvider = useCallback(
|
|
async (data: UpdateCustomProviderRequest) => {
|
|
if (!editingProvider) return;
|
|
|
|
await acpUpdateCustomProviderFromRequest(editingProvider.id, data);
|
|
const providerId = editingProvider.id;
|
|
setShowCustomProviderModal(false);
|
|
setEditingProvider(null);
|
|
if (refreshProviders) {
|
|
refreshProviders();
|
|
}
|
|
setSwitchModelProvider(providerId);
|
|
setShowSwitchModelModal(true);
|
|
},
|
|
[editingProvider, refreshProviders]
|
|
);
|
|
|
|
const handleDeleteCustomProvider = useCallback(async () => {
|
|
if (!editingProvider) return;
|
|
|
|
await acpDeleteCustomProvider(editingProvider.id);
|
|
setShowCustomProviderModal(false);
|
|
setEditingProvider(null);
|
|
setIsActiveProvider(false);
|
|
if (refreshProviders) {
|
|
refreshProviders();
|
|
}
|
|
}, [editingProvider, refreshProviders]);
|
|
|
|
const handleCloseModal = useCallback(() => {
|
|
setShowCustomProviderModal(false);
|
|
setEditingProvider(null);
|
|
setIsActiveProvider(false);
|
|
}, []);
|
|
|
|
const onCloseProviderConfig = useCallback(() => {
|
|
setConfiguringProvider(null);
|
|
if (refreshProviders) {
|
|
refreshProviders();
|
|
}
|
|
}, [refreshProviders]);
|
|
|
|
const onProviderConfigured = useCallback(
|
|
async (provider: ProviderDetails) => {
|
|
setConfiguringProvider(null);
|
|
if (refreshProviders) {
|
|
await refreshProviders();
|
|
}
|
|
setSwitchModelProvider(provider.name);
|
|
setShowSwitchModelModal(true);
|
|
},
|
|
[refreshProviders]
|
|
);
|
|
|
|
const onCloseSwitchModelModal = useCallback(() => {
|
|
setShowSwitchModelModal(false);
|
|
}, []);
|
|
|
|
const handleSetView = useCallback(
|
|
(view: View) => {
|
|
setShowSwitchModelModal(false);
|
|
if (setView) {
|
|
setView(view);
|
|
}
|
|
},
|
|
[setView]
|
|
);
|
|
|
|
const handleCreateCustomProvider = useCallback(
|
|
async (data: UpdateCustomProviderRequest) => {
|
|
const result = await acpCreateCustomProviderFromRequest(data);
|
|
const providerId = result.provider_name;
|
|
setShowCustomProviderModal(false);
|
|
if (refreshProviders) {
|
|
await refreshProviders();
|
|
}
|
|
setSwitchModelProvider(providerId);
|
|
setShowSwitchModelModal(true);
|
|
},
|
|
[refreshProviders]
|
|
);
|
|
|
|
const providerCards = useMemo(() => {
|
|
// providers needs to be an array
|
|
const providersArray = Array.isArray(providers) ? providers : [];
|
|
// Sort providers alphabetically by display name
|
|
const sortedProviders = [...providersArray].sort((a, b) =>
|
|
a.metadata.display_name.localeCompare(b.metadata.display_name)
|
|
);
|
|
const cards = sortedProviders.map((provider) => (
|
|
<ProviderCard
|
|
key={provider.name}
|
|
provider={provider}
|
|
onConfigure={() => configureProviderViaModal(provider)}
|
|
onLaunch={() => handleProviderLaunchWithModelSelection(provider)}
|
|
isOnboarding={isOnboarding}
|
|
/>
|
|
));
|
|
|
|
cards.push(
|
|
<CustomProviderCard key="add-custom" onClick={() => setShowCustomProviderModal(true)} />
|
|
);
|
|
|
|
return cards;
|
|
}, [providers, isOnboarding, configureProviderViaModal, handleProviderLaunchWithModelSelection]);
|
|
|
|
const initialData = editingProvider && {
|
|
engine: editingProvider.config.engine,
|
|
display_name: editingProvider.config.displayName,
|
|
api_url: editingProvider.config.apiUrl,
|
|
base_path: editingProvider.config.basePath ?? undefined,
|
|
api_key: '',
|
|
models: editingProvider.config.models ?? [],
|
|
supports_streaming: editingProvider.config.supportsStreaming ?? true,
|
|
requires_auth: editingProvider.config.requiresAuth ?? true,
|
|
headers: editingProvider.config.headers ?? undefined,
|
|
catalog_provider_id: editingProvider.config.catalogProviderId ?? undefined,
|
|
};
|
|
|
|
const editable = editingProvider ? editingProvider.isEditable : true;
|
|
const title = editingProvider
|
|
? editable
|
|
? intl.formatMessage(i18n.editProvider)
|
|
: intl.formatMessage(i18n.configureProvider)
|
|
: intl.formatMessage(i18n.addProviderTitle);
|
|
return (
|
|
<>
|
|
{providerCards}
|
|
<Dialog open={showCustomProviderModal} onOpenChange={handleCloseModal}>
|
|
<DialogContent className="sm:max-w-[600px] max-h-[90vh] overflow-y-auto">
|
|
<DialogHeader>
|
|
<DialogTitle>{title}</DialogTitle>
|
|
</DialogHeader>
|
|
<CustomProviderForm
|
|
initialData={initialData}
|
|
isEditable={editable}
|
|
onSubmit={editingProvider ? handleUpdateCustomProvider : handleCreateCustomProvider}
|
|
onCancel={handleCloseModal}
|
|
onDelete={
|
|
editingProvider?.providerType === 'Custom' ? handleDeleteCustomProvider : undefined
|
|
}
|
|
isActiveProvider={isActiveProvider}
|
|
/>
|
|
</DialogContent>
|
|
</Dialog>
|
|
{configuringProvider && (
|
|
<ProviderConfigurationModal
|
|
provider={configuringProvider}
|
|
onClose={onCloseProviderConfig}
|
|
onConfigured={onProviderConfigured}
|
|
/>
|
|
)}
|
|
{showSwitchModelModal && (
|
|
<SwitchModelModal
|
|
sessionId={null}
|
|
onClose={onCloseSwitchModelModal}
|
|
setView={handleSetView}
|
|
onModelSelected={onModelSelected}
|
|
initialProvider={switchModelProvider}
|
|
titleOverride={intl.formatMessage(i18n.chooseModel)}
|
|
/>
|
|
)}
|
|
</>
|
|
);
|
|
}
|
|
|
|
export default function ProviderGrid({
|
|
providers,
|
|
isOnboarding,
|
|
refreshProviders,
|
|
setView,
|
|
onModelSelected,
|
|
}: {
|
|
providers: ProviderDetails[];
|
|
isOnboarding: boolean;
|
|
refreshProviders?: () => void;
|
|
setView?: (view: View) => void;
|
|
onModelSelected?: (model?: string) => void;
|
|
}) {
|
|
return (
|
|
<GridLayout>
|
|
<ProviderCards
|
|
providers={providers}
|
|
isOnboarding={isOnboarding}
|
|
refreshProviders={refreshProviders}
|
|
setView={setView}
|
|
onModelSelected={onModelSelected}
|
|
/>
|
|
</GridLayout>
|
|
);
|
|
}
|