ui: fix onboarding & allow for key deletion (#1928)
This commit is contained in:
@@ -123,7 +123,14 @@ pub async fn remove_config(
|
||||
|
||||
let config = Config::global();
|
||||
|
||||
match config.delete(&query.key) {
|
||||
// Check if the secret flag is true and call the appropriate method
|
||||
let result = if query.is_secret {
|
||||
config.delete_secret(&query.key)
|
||||
} else {
|
||||
config.delete(&query.key)
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok(_) => Ok(Json(format!("Removed key {}", query.key))),
|
||||
Err(_) => Err(StatusCode::NOT_FOUND),
|
||||
}
|
||||
|
||||
@@ -37,5 +37,5 @@ export async function getProviderMetadata(
|
||||
if (!matches) {
|
||||
throw Error(`No match for provider: ${providerName}`);
|
||||
}
|
||||
return matches[0].metadata;
|
||||
return matches.metadata;
|
||||
}
|
||||
|
||||
@@ -42,6 +42,21 @@ const ProviderCards = memo(function ProviderCards({
|
||||
[openModal, refreshProviders]
|
||||
);
|
||||
|
||||
const deleteProviderConfigViaModal = useCallback(
|
||||
(provider: ProviderDetails) => {
|
||||
openModal(provider, {
|
||||
onDelete: () => {
|
||||
// Only refresh if the function is provided
|
||||
if (refreshProviders) {
|
||||
refreshProviders();
|
||||
}
|
||||
},
|
||||
formProps: {},
|
||||
});
|
||||
},
|
||||
[openModal, refreshProviders]
|
||||
);
|
||||
|
||||
// We don't need an intermediate function here
|
||||
// Just pass the onProviderLaunch directly
|
||||
|
||||
@@ -52,6 +67,7 @@ const ProviderCards = memo(function ProviderCards({
|
||||
key={provider.name}
|
||||
provider={provider}
|
||||
onConfigure={() => configureProviderViaModal(provider)}
|
||||
onDelete={() => deleteProviderConfigViaModal(provider)}
|
||||
onLaunch={() => onProviderLaunch(provider)}
|
||||
isOnboarding={isOnboarding}
|
||||
/>
|
||||
@@ -87,6 +103,5 @@ export default memo(function ProviderGrid({
|
||||
),
|
||||
[providers, isOnboarding, refreshProviders, onProviderLaunch]
|
||||
);
|
||||
|
||||
return <GridLayout>{modalProviderContent}</GridLayout>;
|
||||
});
|
||||
|
||||
@@ -79,15 +79,14 @@ export default function ProviderSettings({ onClose, isOnboarding }: ProviderSett
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="h-screen w-full">
|
||||
<div className="h-screen w-full flex flex-col">
|
||||
<div className="relative flex items-center h-[36px] w-full bg-bgSubtle"></div>
|
||||
{isOnboarding && (
|
||||
<div className="group/logo">
|
||||
<WelcomeGooseLogo className="h-16 w-16 md:h-20 md:w-20 text-black dark:text-white" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<ScrollArea className="h-full w-full">
|
||||
<ScrollArea className="flex-1 w-full">
|
||||
{isOnboarding && (
|
||||
<div className="group/logo flex justify-left pl-8">
|
||||
<WelcomeGooseLogo className="h-16 w-16 md:h-20 md:w-20 text-black dark:text-white" />
|
||||
</div>
|
||||
)}
|
||||
<div className="px-8 pt-6 pb-4">
|
||||
{/* Only show back button if not in onboarding mode */}
|
||||
{!isOnboarding && <BackButton onClick={onClose} />}
|
||||
@@ -96,7 +95,7 @@ export default function ProviderSettings({ onClose, isOnboarding }: ProviderSett
|
||||
</h1>
|
||||
{isOnboarding && (
|
||||
<p className="text-s text-textSubtle max-w-2xl pt-2">
|
||||
Select an AI model provider to get started with goose. You’ll need to use API keys
|
||||
Select an AI model provider to get started with goose. You'll need to use API keys
|
||||
generated by each provider which will be encrypted and stored locally. You can change
|
||||
your provider at any time in settings.
|
||||
</p>
|
||||
|
||||
+94
-17
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import Modal from '../../../../components/Modal';
|
||||
import ProviderSetupHeader from './subcomponents/ProviderSetupHeader';
|
||||
import DefaultProviderSetupForm from './subcomponents/forms/DefaultProviderSetupForm';
|
||||
@@ -10,6 +10,7 @@ import { DefaultSubmitHandler } from './subcomponents/handlers/DefaultSubmitHand
|
||||
import OllamaSubmitHandler from './subcomponents/handlers/OllamaSubmitHandler';
|
||||
import OllamaForm from './subcomponents/forms/OllamaForm';
|
||||
import { useConfig } from '../../../ConfigContext';
|
||||
import { AlertTriangle } from 'lucide-react';
|
||||
|
||||
const customSubmitHandlerMap = {
|
||||
provider_name: OllamaSubmitHandler, // example
|
||||
@@ -20,15 +21,31 @@ const customFormsMap = {
|
||||
};
|
||||
|
||||
export default function ProviderConfigurationModal() {
|
||||
const { upsert } = useConfig();
|
||||
const [validationErrors, setValidationErrors] = useState({});
|
||||
const { upsert, remove } = useConfig();
|
||||
const { isOpen, currentProvider, modalProps, closeModal } = useProviderModal();
|
||||
const [configValues, setConfigValues] = useState({});
|
||||
const [showDeleteConfirmation, setShowDeleteConfirmation] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen && currentProvider) {
|
||||
// Reset form state when the modal opens with a new provider
|
||||
setConfigValues({});
|
||||
setValidationErrors({});
|
||||
setShowDeleteConfirmation(false);
|
||||
}
|
||||
}, [isOpen, currentProvider]);
|
||||
|
||||
if (!isOpen || !currentProvider) return null;
|
||||
|
||||
const headerText = `Configure ${currentProvider.metadata.display_name}`;
|
||||
const descriptionText = `Add your API key(s) for this provider to integrate into Goose`;
|
||||
const isConfigured = currentProvider.is_configured;
|
||||
const headerText = showDeleteConfirmation
|
||||
? `Delete configuration for ${currentProvider.metadata.display_name}`
|
||||
: `Configure ${currentProvider.metadata.display_name}`;
|
||||
|
||||
const descriptionText = showDeleteConfirmation
|
||||
? 'This will permanently delete the current provider configuration.'
|
||||
: `Add your API key(s) for this provider to integrate into Goose`;
|
||||
|
||||
const SubmitHandler = customSubmitHandlerMap[currentProvider.name] || DefaultSubmitHandler;
|
||||
const FormComponent = customFormsMap[currentProvider.name] || DefaultProviderSetupForm;
|
||||
@@ -80,6 +97,9 @@ export default function ProviderConfigurationModal() {
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
// Reset delete confirmation state
|
||||
setShowDeleteConfirmation(false);
|
||||
|
||||
// Use custom cancel handler if provided
|
||||
if (modalProps.onCancel) {
|
||||
modalProps.onCancel();
|
||||
@@ -88,30 +108,87 @@ export default function ProviderConfigurationModal() {
|
||||
closeModal();
|
||||
};
|
||||
|
||||
const handleDelete = () => {
|
||||
setShowDeleteConfirmation(true);
|
||||
};
|
||||
|
||||
const handleConfirmDelete = async () => {
|
||||
try {
|
||||
// Remove the provider configuration
|
||||
// get the keys
|
||||
const params = currentProvider.metadata.config_keys;
|
||||
|
||||
// go through the keys are remove them
|
||||
for (const param of params) {
|
||||
console.log('param', param.name, 'secret', param.secret);
|
||||
await remove(param.name, param.secret);
|
||||
}
|
||||
|
||||
// Call onDelete callback if provided
|
||||
// This should trigger the refreshProviders function
|
||||
if (modalProps.onDelete) {
|
||||
modalProps.onDelete(currentProvider.name);
|
||||
}
|
||||
|
||||
// Reset the delete confirmation state before closing
|
||||
setShowDeleteConfirmation(false);
|
||||
|
||||
// Close the modal
|
||||
// Close the modal after deletion and callback
|
||||
closeModal();
|
||||
} catch (error) {
|
||||
console.error('Failed to delete provider:', error);
|
||||
// Keep modal open if there's an error
|
||||
}
|
||||
};
|
||||
// Function to determine which icon to display
|
||||
const getModalIcon = () => {
|
||||
if (showDeleteConfirmation) {
|
||||
return <AlertTriangle className="text-red-500" size={24} />;
|
||||
}
|
||||
return <ProviderLogo providerName={currentProvider.name} />;
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
onClose={closeModal}
|
||||
footer={<ProviderSetupActions onCancel={handleCancel} onSubmit={handleSubmitForm} />}
|
||||
footer={
|
||||
<ProviderSetupActions
|
||||
onCancel={handleCancel}
|
||||
onSubmit={handleSubmitForm}
|
||||
onDelete={handleDelete}
|
||||
showDeleteConfirmation={showDeleteConfirmation}
|
||||
onConfirmDelete={handleConfirmDelete}
|
||||
onCancelDelete={() => setShowDeleteConfirmation(false)}
|
||||
canDelete={isConfigured}
|
||||
providerName={currentProvider.metadata.display_name}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<div className="space-y-1">
|
||||
{/* Logo area - centered above title */}
|
||||
<ProviderLogo providerName={currentProvider.name} />
|
||||
{/* Logo area or warning icon */}
|
||||
<div>{getModalIcon()}</div>
|
||||
{/* Title and some information - centered */}
|
||||
<ProviderSetupHeader title={headerText} body={descriptionText} />
|
||||
</div>
|
||||
|
||||
{/* Contains information used to set up each provider */}
|
||||
<FormComponent
|
||||
configValues={configValues}
|
||||
setConfigValues={setConfigValues}
|
||||
provider={currentProvider}
|
||||
validationErrors={validationErrors}
|
||||
{...(modalProps.formProps || {})} // Spread any custom form props
|
||||
/>
|
||||
{/* Only show the form when NOT in delete confirmation mode */}
|
||||
{!showDeleteConfirmation ? (
|
||||
<>
|
||||
{/* Contains information used to set up each provider */}
|
||||
<FormComponent
|
||||
configValues={configValues}
|
||||
setConfigValues={setConfigValues}
|
||||
provider={currentProvider}
|
||||
validationErrors={validationErrors}
|
||||
{...(modalProps.formProps || {})} // Spread any custom form props
|
||||
/>
|
||||
|
||||
{currentProvider.metadata.config_keys && currentProvider.metadata.config_keys.length > 0 && (
|
||||
<SecureStorageNotice />
|
||||
)}
|
||||
{currentProvider.metadata.config_keys &&
|
||||
currentProvider.metadata.config_keys.length > 0 && <SecureStorageNotice />}
|
||||
</>
|
||||
) : null}
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { ProviderDetails } from '../../../../api/types.gen';
|
||||
interface ModalProps {
|
||||
onSubmit?: (values: any) => void;
|
||||
onCancel?: () => void;
|
||||
onDelete?: (values: any) => void;
|
||||
formProps?: any;
|
||||
}
|
||||
|
||||
|
||||
-9
@@ -1,9 +0,0 @@
|
||||
// used both for initial config and editing config
|
||||
import ProviderDetails from '../../interfaces/ProviderDetails';
|
||||
|
||||
export default interface ProviderConfiguationModalProps {
|
||||
provider: ProviderDetails;
|
||||
title?: string;
|
||||
onSubmit: (configValues: { [key: string]: string }) => void;
|
||||
onCancel: () => void;
|
||||
}
|
||||
+56
-4
@@ -1,19 +1,71 @@
|
||||
import React from 'react';
|
||||
import { Button } from '../../../../ui/button';
|
||||
import { Trash2 } from 'lucide-react';
|
||||
|
||||
interface ProviderSetupActionsProps {
|
||||
onCancel: () => void;
|
||||
onSubmit: (e: any) => void;
|
||||
onDelete?: () => void;
|
||||
showDeleteConfirmation?: boolean;
|
||||
onConfirmDelete?: () => void;
|
||||
onCancelDelete?: () => void;
|
||||
canDelete?: boolean;
|
||||
providerName?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the "Submit" and "Cancel" buttons at the bottom.
|
||||
* Updated to match the design from screenshots.
|
||||
* Renders the action buttons at the bottom of the provider modal.
|
||||
* Includes submit, cancel, and delete functionality with confirmation.
|
||||
*/
|
||||
export default function ProviderSetupActions({ onCancel, onSubmit }: ProviderSetupActionsProps) {
|
||||
export default function ProviderSetupActions({
|
||||
onCancel,
|
||||
onSubmit,
|
||||
onDelete,
|
||||
showDeleteConfirmation,
|
||||
onConfirmDelete,
|
||||
onCancelDelete,
|
||||
canDelete,
|
||||
providerName,
|
||||
}: ProviderSetupActionsProps) {
|
||||
// If we're showing delete confirmation, render the delete confirmation buttons
|
||||
if (showDeleteConfirmation) {
|
||||
return (
|
||||
<>
|
||||
<div className="w-full px-6 py-4 bg-red-900/20 border-t border-red-500/30">
|
||||
<p className="text-red-400 text-sm mb-2">
|
||||
Are you sure you want to delete the configuration parameters for {providerName}? This
|
||||
action cannot be undone.
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
onClick={onConfirmDelete}
|
||||
className="w-full h-[60px] rounded-none border-b border-borderSubtle bg-transparent hover:bg-red-900/20 text-red-500 font-medium text-md"
|
||||
>
|
||||
<Trash2 className="h-4 w-4 mr-2" /> Confirm Delete
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={onCancelDelete}
|
||||
className="w-full h-[60px] rounded-none hover:bg-bgSubtle text-textSubtle hover:text-textStandard text-md font-regular"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// Regular buttons (with delete if applicable)
|
||||
return (
|
||||
<div className="-ml-8 -mr-8">
|
||||
{/* We rely on the <form> "onSubmit" for the actual Submit logic */}
|
||||
{canDelete && onDelete && (
|
||||
<Button
|
||||
type="button"
|
||||
onClick={onDelete}
|
||||
className="w-full h-[60px] rounded-none border-t border-borderSubtle bg-transparent hover:bg-bgSubtle text-red-500 font-medium text-md"
|
||||
>
|
||||
<Trash2 className="h-4 w-4 mr-2" /> Delete Provider
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
type="submit"
|
||||
variant="ghost"
|
||||
|
||||
+10
-5
@@ -13,12 +13,14 @@ export default function DefaultProviderSetupForm({
|
||||
configValues,
|
||||
setConfigValues,
|
||||
provider,
|
||||
validationErrors,
|
||||
validationErrors = {},
|
||||
}: DefaultProviderSetupFormProps) {
|
||||
const parameters = provider.metadata.config_keys || [];
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const { read } = useConfig();
|
||||
|
||||
console.log('configValues default form', configValues);
|
||||
|
||||
// Initialize values when the component mounts or provider changes
|
||||
useEffect(() => {
|
||||
const loadConfigValues = async () => {
|
||||
@@ -66,8 +68,8 @@ export default function DefaultProviderSetupForm({
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
loadConfigValues();
|
||||
}, [provider.name, parameters, setConfigValues, read]);
|
||||
loadConfigValues().then();
|
||||
}, []);
|
||||
|
||||
// Filter parameters to only show required ones
|
||||
const requiredParameters = useMemo(() => {
|
||||
@@ -89,6 +91,7 @@ export default function DefaultProviderSetupForm({
|
||||
return <div className="text-center py-4">Loading configuration values...</div>;
|
||||
}
|
||||
|
||||
console.log('required params', requiredParameters);
|
||||
return (
|
||||
<div className="mt-4 space-y-4">
|
||||
{requiredParameters.length === 0 ? (
|
||||
@@ -109,8 +112,10 @@ export default function DefaultProviderSetupForm({
|
||||
}))
|
||||
}
|
||||
placeholder={getPlaceholder(parameter)}
|
||||
className={`w-full h-14 px-4 font-regular rounded-lg border shadow-none ${
|
||||
validationErrors[parameter.name] ? 'border-red-500' : 'border-gray-300'
|
||||
className={`w-full h-14 px-4 font-regular rounded-lg shadow-none ${
|
||||
validationErrors[parameter.name]
|
||||
? 'border-2 border-red-500'
|
||||
: 'border border-gray-300'
|
||||
} bg-white text-lg placeholder:text-gray-400 font-regular text-gray-900`}
|
||||
required={true}
|
||||
/>
|
||||
|
||||
@@ -34,23 +34,33 @@ export default function CardContainer({
|
||||
}: CardContainerProps) {
|
||||
return (
|
||||
<div
|
||||
className={`relative h-full p-[2px] overflow-hidden rounded-[9px] group/card bg-borderSubtle ${
|
||||
!grayedOut ? 'hover:bg-transparent hover:duration-300' : ''
|
||||
}`}
|
||||
className={`relative h-full p-[2px] overflow-hidden rounded-[9px] group/card
|
||||
${
|
||||
grayedOut
|
||||
? 'bg-borderSubtle hover:bg-gray-700'
|
||||
: 'bg-borderSubtle hover:bg-transparent hover:duration-300'
|
||||
}`}
|
||||
onClick={!grayedOut ? onClick : undefined}
|
||||
style={{
|
||||
cursor: !grayedOut && onClick ? 'pointer' : 'default',
|
||||
opacity: !grayedOut ? '1' : '0.5',
|
||||
}}
|
||||
>
|
||||
{!grayedOut && <GlowingRing />}
|
||||
<div
|
||||
className={`relative bg-bgApp rounded-lg p-3 transition-all duration-200 h-[160px] flex flex-col justify-between ${
|
||||
!grayedOut ? 'hover:border-borderStandard' : ''
|
||||
}`}
|
||||
className={`relative bg-bgApp rounded-lg p-3 transition-all duration-200 h-[160px] flex flex-col justify-between
|
||||
${
|
||||
grayedOut
|
||||
? 'border border-borderSubtle'
|
||||
: 'border border-borderSubtle hover:border-borderStandard'
|
||||
}`}
|
||||
>
|
||||
<HeaderContainer>{header}</HeaderContainer>
|
||||
{body}
|
||||
{/* Apply opacity only to the header when grayed out */}
|
||||
<div style={{ opacity: grayedOut ? '0.5' : '1' }}>
|
||||
<HeaderContainer>{header}</HeaderContainer>
|
||||
</div>
|
||||
|
||||
{/* Body always at full opacity */}
|
||||
<div>{body}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -9,6 +9,7 @@ type ProviderCardProps = {
|
||||
provider: ProviderDetails;
|
||||
onConfigure: () => void;
|
||||
onLaunch: () => void;
|
||||
onDelete: () => void;
|
||||
isOnboarding: boolean;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user