mirror of
https://github.com/enricoros/big-AGI.git
synced 2026-05-10 21:50:14 -07:00
Friction: Model Wizard: also warn if some keys are not saved
This commit is contained in:
@@ -40,6 +40,7 @@ export function ModelsConfiguratorModal(props: {
|
||||
// state
|
||||
// const [showAllServices, setShowAllServices] = React.useState<boolean>(false);
|
||||
const [tab, setTab] = React.useState<TabValue>(MODELS_WIZARD_ENABLE_INITIALLY && !modelsServices.length ? 'wizard' : 'setup');
|
||||
const [unsavedWizardProviders, setUnsavedWizardProviders] = React.useState<Set<string>>(new Set());
|
||||
const showAllServices = false;
|
||||
|
||||
// external state
|
||||
@@ -83,6 +84,17 @@ export function ModelsConfiguratorModal(props: {
|
||||
const handleShowWizard = React.useCallback(() => setTab('wizard'), []);
|
||||
// const handleToggleDefaults = React.useCallback(() => setTab(tab => tab === 'defaults' ? 'setup' : 'defaults'), []);
|
||||
|
||||
// callback for wizard to report unsaved provider changes
|
||||
const handleWizardProviderUnsavedChange = React.useCallback((providerId: string, hasUnsaved: boolean) => {
|
||||
setUnsavedWizardProviders(prev => {
|
||||
const next = new Set(prev);
|
||||
if (hasUnsaved) next.add(providerId);
|
||||
else next.delete(providerId);
|
||||
// only update if actually changed
|
||||
return next.size !== prev.size || (hasUnsaved && !prev.has(providerId)) ? next : prev;
|
||||
});
|
||||
}, []);
|
||||
|
||||
|
||||
// start button
|
||||
const startButton = React.useMemo(() => {
|
||||
@@ -117,25 +129,35 @@ export function ModelsConfiguratorModal(props: {
|
||||
const wizardButtons = React.useMemo(() => {
|
||||
if (!isTabWizard) return undefined;
|
||||
|
||||
const hasUnsavedChanges = unsavedWizardProviders.size > 0;
|
||||
// const tooltipTitle = !hasLLMs ? 'Please save at least one API key to continue'
|
||||
// : hasUnsavedChanges ? 'You have unsaved changes - click Save first'
|
||||
// : '';
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', width: '100%', gap: 1, justifyContent: 'space-between' }}>
|
||||
<Box sx={{ display: 'flex', width: '100%', gap: 1, justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
{startButton}
|
||||
<TooltipOutlined
|
||||
title={!hasLLMs ? 'Please save at least one API key to continue' : ''}
|
||||
placement='top'
|
||||
|
||||
{/* unsaved warning */}
|
||||
{hasUnsavedChanges && (
|
||||
<Typography color='warning' level='body-sm' ml='auto'>
|
||||
{isMobile ? 'Unsaved' : `You have ${unsavedWizardProviders.size} unsaved change${ unsavedWizardProviders.size > 1 ? 's' : '' }`}
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
{/* "Done" button */}
|
||||
<Button
|
||||
variant='solid'
|
||||
color='neutral'
|
||||
disabled={!hasLLMs || hasUnsavedChanges}
|
||||
onClick={optimaActions().closeModels}
|
||||
sx={{ ml: 'auto', minWidth: 100 }}
|
||||
>
|
||||
<Button
|
||||
color='neutral'
|
||||
disabled={!hasLLMs}
|
||||
onClick={optimaActions().closeModels}
|
||||
sx={{ ml: 'auto', minWidth: 100 }}
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
</TooltipOutlined>
|
||||
Done
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
}, [isTabWizard, startButton, hasLLMs]);
|
||||
}, [hasLLMs, unsavedWizardProviders, isMobile, isTabWizard, startButton]);
|
||||
|
||||
|
||||
// Explainer section
|
||||
@@ -239,7 +261,15 @@ export function ModelsConfiguratorModal(props: {
|
||||
>
|
||||
|
||||
{isTabWizard && <Divider />}
|
||||
{isTabWizard && <ModelsWizard isMobile={isMobile} onSkip={optimaActions().closeModels} onSwitchToAdvanced={handleShowAdvanced} onSwitchToWhy={handleShowExplainerAgain} />}
|
||||
{isTabWizard && (
|
||||
<ModelsWizard
|
||||
isMobile={isMobile}
|
||||
onSkip={optimaActions().closeModels}
|
||||
onSwitchToAdvanced={handleShowAdvanced}
|
||||
onSwitchToWhy={handleShowExplainerAgain}
|
||||
onProviderUnsavedChange={handleWizardProviderUnsavedChange}
|
||||
/>
|
||||
)}
|
||||
|
||||
{isTabSetup && <ModelsServiceSelector modelsServices={modelsServices} selectedServiceId={activeServiceId} setSelectedServiceId={setConfServiceId} onSwitchToWizard={handleShowWizard} />}
|
||||
{isTabSetup && <Divider sx={activeService ? undefined : { visibility: 'hidden' }} />}
|
||||
|
||||
@@ -90,6 +90,7 @@ function WizardProviderSetup(props: {
|
||||
provider: WizardProvider,
|
||||
isFirst: boolean,
|
||||
isHidden: boolean,
|
||||
onUnsavedChange: (providerId: string, hasUnsaved: boolean) => void,
|
||||
}) {
|
||||
|
||||
const { cat: providerCat, vendor: providerVendor, settingsKey: providerSettingsKey, omit: providerOmit } = props.provider;
|
||||
@@ -134,6 +135,22 @@ function WizardProviderSetup(props: {
|
||||
const autoCompleteId = isLocal ? `${providerVendor.id}-host` : `${providerVendor.id}-key`;
|
||||
|
||||
|
||||
// wrapped setter that notifies parent of unsaved state
|
||||
|
||||
const { onUnsavedChange } = props;
|
||||
|
||||
const handleLocalValueChange = React.useCallback((newValue: string) => {
|
||||
// set locally
|
||||
setLocalValue(newValue);
|
||||
|
||||
// notify parent of unsaved state
|
||||
if (providerOmit || !onUnsavedChange) return;
|
||||
const hasUnsaved = newValue !== (serviceKeyValue || '');
|
||||
const hasValue = !!newValue.trim();
|
||||
onUnsavedChange(providerVendor.id, hasUnsaved && hasValue);
|
||||
}, [onUnsavedChange, providerOmit, providerVendor.id, serviceKeyValue]);
|
||||
|
||||
|
||||
// handlers
|
||||
|
||||
|
||||
@@ -149,6 +166,10 @@ function WizardProviderSetup(props: {
|
||||
const newKey = localValue?.trim() ?? '';
|
||||
updateServiceSettings(vendorServiceId, { [providerSettingsKey]: newKey });
|
||||
|
||||
// notify parent that changes are now saved
|
||||
if (!providerOmit)
|
||||
onUnsavedChange(providerVendor.id, false);
|
||||
|
||||
// if the key is empty, remove the models
|
||||
if (!newKey) {
|
||||
setUpdateError(null);
|
||||
@@ -170,7 +191,7 @@ function WizardProviderSetup(props: {
|
||||
}
|
||||
setIsLoading(false);
|
||||
|
||||
}, [localValue, providerSettingsKey, providerVendor, valueName]);
|
||||
}, [localValue, onUnsavedChange, providerOmit, providerSettingsKey, providerVendor, valueName]);
|
||||
|
||||
|
||||
// memoed components
|
||||
@@ -232,7 +253,7 @@ function WizardProviderSetup(props: {
|
||||
autoCompleteId={autoCompleteId}
|
||||
value={localValue ?? ''}
|
||||
placeholder={`${vendorName} ${valueName}`}
|
||||
onChange={setLocalValue}
|
||||
onChange={handleLocalValueChange}
|
||||
required={false}
|
||||
/>
|
||||
</Box>
|
||||
@@ -262,6 +283,7 @@ export function ModelsWizard(props: {
|
||||
onSkip?: () => void,
|
||||
onSwitchToAdvanced?: () => void,
|
||||
onSwitchToWhy?: () => void,
|
||||
onProviderUnsavedChange: (providerId: string, hasUnsaved: boolean) => void,
|
||||
}) {
|
||||
|
||||
// state
|
||||
@@ -296,6 +318,7 @@ export function ModelsWizard(props: {
|
||||
provider={provider}
|
||||
isFirst={!index}
|
||||
isHidden={provider.cat !== activeCategory}
|
||||
onUnsavedChange={props.onProviderUnsavedChange}
|
||||
/>
|
||||
))}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user