Desktop UI for deleting custom providers (#7042)

This commit is contained in:
David Katz
2026-02-06 14:15:17 -05:00
committed by GitHub
parent 7ed5b4cf86
commit 0853e9fc11
2 changed files with 102 additions and 9 deletions
@@ -11,6 +11,7 @@ import { Plus } from 'lucide-react';
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '../../ui/dialog'; import { Dialog, DialogContent, DialogHeader, DialogTitle } from '../../ui/dialog';
import CustomProviderForm from './modal/subcomponents/forms/CustomProviderForm'; import CustomProviderForm from './modal/subcomponents/forms/CustomProviderForm';
import { SwitchModelModal } from '../models/subcomponents/SwitchModelModal'; import { SwitchModelModal } from '../models/subcomponents/SwitchModelModal';
import { useModelAndProvider } from '../../ModelAndProviderContext';
import type { View } from '../../../utils/navigationUtils'; import type { View } from '../../../utils/navigationUtils';
const GridLayout = memo(function GridLayout({ children }: { children: React.ReactNode }) { const GridLayout = memo(function GridLayout({ children }: { children: React.ReactNode }) {
@@ -65,10 +66,13 @@ function ProviderCards({
const [showCustomProviderModal, setShowCustomProviderModal] = useState(false); const [showCustomProviderModal, setShowCustomProviderModal] = useState(false);
const [showSwitchModelModal, setShowSwitchModelModal] = useState(false); const [showSwitchModelModal, setShowSwitchModelModal] = useState(false);
const [switchModelProvider, setSwitchModelProvider] = useState<string | null>(null); const [switchModelProvider, setSwitchModelProvider] = useState<string | null>(null);
const [isActiveProvider, setIsActiveProvider] = useState(false);
const { getCurrentModelAndProvider } = useModelAndProvider();
const [editingProvider, setEditingProvider] = useState<{ const [editingProvider, setEditingProvider] = useState<{
id: string; id: string;
config: DeclarativeProviderConfig; config: DeclarativeProviderConfig;
isEditable: boolean; isEditable: boolean;
providerType: string;
} | null>(null); } | null>(null);
const handleProviderLaunchWithModelSelection = useCallback((provider: ProviderDetails) => { const handleProviderLaunchWithModelSelection = useCallback((provider: ProviderDetails) => {
@@ -92,14 +96,24 @@ function ProviderCards({
id: provider.name, id: provider.name,
config: result.data.config, config: result.data.config,
isEditable: result.data.is_editable, isEditable: result.data.is_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); setShowCustomProviderModal(true);
} }
} else { } else {
openModal(provider); openModal(provider);
} }
}, },
[openModal] [openModal, getCurrentModelAndProvider]
); );
const handleUpdateCustomProvider = useCallback( const handleUpdateCustomProvider = useCallback(
@@ -124,9 +138,26 @@ function ProviderCards({
[editingProvider, refreshProviders] [editingProvider, refreshProviders]
); );
const handleDeleteCustomProvider = useCallback(async () => {
if (!editingProvider) return;
const { removeCustomProvider } = await import('../../../api');
await removeCustomProvider({
path: { id: editingProvider.id },
throwOnError: true,
});
setShowCustomProviderModal(false);
setEditingProvider(null);
setIsActiveProvider(false);
if (refreshProviders) {
refreshProviders();
}
}, [editingProvider, refreshProviders]);
const handleCloseModal = useCallback(() => { const handleCloseModal = useCallback(() => {
setShowCustomProviderModal(false); setShowCustomProviderModal(false);
setEditingProvider(null); setEditingProvider(null);
setIsActiveProvider(false);
}, []); }, []);
const onCloseProviderConfig = useCallback(() => { const onCloseProviderConfig = useCallback(() => {
@@ -178,8 +209,10 @@ function ProviderCards({
const providerCards = useMemo(() => { const providerCards = useMemo(() => {
// providers needs to be an array // providers needs to be an array
const providersArray = Array.isArray(providers) ? providers : []; const providersArray = Array.isArray(providers) ? providers : [];
// Sort providers alphabetically by name // Sort providers alphabetically by display name
const sortedProviders = [...providersArray].sort((a, b) => a.name.localeCompare(b.name)); const sortedProviders = [...providersArray].sort((a, b) =>
a.metadata.display_name.localeCompare(b.metadata.display_name)
);
const cards = sortedProviders.map((provider) => ( const cards = sortedProviders.map((provider) => (
<ProviderCard <ProviderCard
key={provider.name} key={provider.name}
@@ -222,6 +255,10 @@ function ProviderCards({
isEditable={editable} isEditable={editable}
onSubmit={editingProvider ? handleUpdateCustomProvider : handleCreateCustomProvider} onSubmit={editingProvider ? handleUpdateCustomProvider : handleCreateCustomProvider}
onCancel={handleCloseModal} onCancel={handleCloseModal}
onDelete={
editingProvider?.providerType === 'Custom' ? handleDeleteCustomProvider : undefined
}
isActiveProvider={isActiveProvider}
/> />
</DialogContent> </DialogContent>
</Dialog>{' '} </Dialog>{' '}
@@ -4,10 +4,13 @@ import { Select } from '../../../../../ui/Select';
import { Button } from '../../../../../ui/button'; import { Button } from '../../../../../ui/button';
import { SecureStorageNotice } from '../SecureStorageNotice'; import { SecureStorageNotice } from '../SecureStorageNotice';
import { UpdateCustomProviderRequest } from '../../../../../../api'; import { UpdateCustomProviderRequest } from '../../../../../../api';
import { Trash2, AlertTriangle } from 'lucide-react';
interface CustomProviderFormProps { interface CustomProviderFormProps {
onSubmit: (data: UpdateCustomProviderRequest) => void; onSubmit: (data: UpdateCustomProviderRequest) => void;
onCancel: () => void; onCancel: () => void;
onDelete?: () => Promise<void>;
isActiveProvider?: boolean;
initialData: UpdateCustomProviderRequest | null; initialData: UpdateCustomProviderRequest | null;
isEditable?: boolean; isEditable?: boolean;
} }
@@ -15,6 +18,8 @@ interface CustomProviderFormProps {
export default function CustomProviderForm({ export default function CustomProviderForm({
onSubmit, onSubmit,
onCancel, onCancel,
onDelete,
isActiveProvider = false,
initialData, initialData,
isEditable, isEditable,
}: CustomProviderFormProps) { }: CustomProviderFormProps) {
@@ -26,6 +31,7 @@ export default function CustomProviderForm({
const [requiresApiKey, setRequiresApiKey] = useState(false); const [requiresApiKey, setRequiresApiKey] = useState(false);
const [supportsStreaming, setSupportsStreaming] = useState(true); const [supportsStreaming, setSupportsStreaming] = useState(true);
const [validationErrors, setValidationErrors] = useState<Record<string, string>>({}); const [validationErrors, setValidationErrors] = useState<Record<string, string>>({});
const [showDeleteConfirmation, setShowDeleteConfirmation] = useState(false);
useEffect(() => { useEffect(() => {
if (initialData) { if (initialData) {
@@ -257,12 +263,62 @@ export default function CustomProviderForm({
</> </>
)} )}
<SecureStorageNotice /> <SecureStorageNotice />
<div className="flex justify-end space-x-2 pt-4">
<Button type="button" variant="outline" onClick={onCancel}> {showDeleteConfirmation ? (
Cancel <div className="pt-4 space-y-3">
</Button> {isActiveProvider ? (
<Button type="submit">{initialData ? 'Update Provider' : 'Create Provider'}</Button> <div className="px-4 py-3 bg-yellow-600/20 border border-yellow-500/30 rounded">
</div> <p className="text-yellow-500 text-sm flex items-start">
<AlertTriangle className="h-4 w-4 mr-2 mt-0.5 flex-shrink-0" />
<span>
You cannot delete this provider while it's currently in use. Please switch to a
different model first.
</span>
</p>
</div>
) : (
<div className="px-4 py-3 bg-red-900/20 border border-red-500/30 rounded">
<p className="text-red-400 text-sm">
Are you sure you want to delete this custom provider? This will permanently remove
the provider and its stored API key. This action cannot be undone.
</p>
</div>
)}
<div className="flex justify-end space-x-2">
<Button
type="button"
variant="outline"
onClick={() => setShowDeleteConfirmation(false)}
>
Cancel
</Button>
{!isActiveProvider && (
<Button type="button" variant="destructive" onClick={onDelete}>
<Trash2 className="h-4 w-4 mr-2" />
Confirm Delete
</Button>
)}
</div>
</div>
) : (
<div className="flex justify-end space-x-2 pt-4">
{initialData && onDelete && (
<Button
type="button"
variant="outline"
className="text-red-500 hover:text-red-600 mr-auto"
onClick={() => setShowDeleteConfirmation(true)}
>
<Trash2 className="h-4 w-4 mr-2" />
Delete Provider
</Button>
)}
<Button type="button" variant="outline" onClick={onCancel}>
Cancel
</Button>
<Button type="submit">{initialData ? 'Update Provider' : 'Create Provider'}</Button>
</div>
)}
</form> </form>
); );
} }